Compare commits

..
854 Commits
Author SHA1 Message Date
Hundrec 1b13c6c53b Bump version to 0.8.2
Build and Upload nightly game builds / gather-changes (push) Canceled after 0s
Build and Upload nightly game builds / gather-tags (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (macos, macos) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (windows, windows) (push) Canceled after 0s
Build and Upload nightly game builds / docker-image (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (html5) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (linux) (push) Canceled after 0s
2026-02-20 22:54:45 -07:00
HundrecandAbnormal 036de96143 Changelog for 0.8.2
Co-Authored-By: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
2026-02-20 22:50:57 -07:00
ComedyLostandHundrec 0b046cae9c Fix the Freeplay crash when scrolling too fast 2026-02-20 19:50:18 -07:00
Hundrec a4c5341b3b Revert Freeplay capsule timer clearing change 2026-02-20 17:04:30 -07:00
Hyper_andHundrec 5cbfab4f99 fix: Fix a plugin not initializing cameras on the correct frontend
Kevin and Michael found alive and well emoji whatthehappy
2026-02-19 03:14:51 -07:00
Hundrec baf887155b Bump assets for one minor chart adjustment 2026-02-18 01:24:29 -07:00
ImVeryBadandHundrec a5bbe94871 Add more default imports used by mods 2026-02-17 01:06:38 -07:00
ImVeryBadandHundrec ab26b28130 Remove spammy traces from some classes. 2026-02-17 01:06:38 -07:00
RagdollClashandHundrec 2606031ed2 Add an extra pixel to the cutout bitmaps 2026-02-16 14:23:00 -07:00
RagdollClashandHundrec 72f9d32a2f Account for offset when adding the fake cutouts 2026-02-16 14:23:00 -07:00
AbnormalPoofandHundrec 4b87bbf1cd Use a newer export for Title Screen text that got lost in rebasing 2026-02-16 14:23:00 -07:00
AbnormalPoofandHundrec c9802fdcf2 Fix confirm-hold not playing for pixel icons in Character Select... again 2026-02-16 14:22:56 -07:00
Hundrec e7f252953d Two quick chart fixes 2026-02-15 21:48:14 -07:00
Hundrec 7f299d8348 Revert "Use MenuList camera for Touch Detection instead"
This reverts commit 0400f4bd4a.
2026-02-15 21:01:28 -07:00
9d83d05c96 Blacklist lime.utils.AssetLibrary from scripts
Co-authored-by: charlesisfeline <62410083+charlesisfeline@users.noreply.github.com>
2026-02-15 21:01:28 -07:00
PurSnakeandHundrec d6919683c3 Remove dead objects from Freeplay capsule batching 2026-02-15 21:01:28 -07:00
EliteMasterEricandHundrec 879955c4a2 Format a few additional files. 2026-02-15 21:01:28 -07:00
AbnormalPoofandHundrec 6026e0b908 Run haxe-formatter on every file in source/ 2026-02-15 21:01:26 -07:00
32ef5404d8 fix: ResultsState - Start with song name hidden until the top bar shows up
Co-authored-by: Trayfellow <48515908+trayfellow@users.noreply.github.com>
2026-02-13 20:34:11 -07:00
AbnormalPoofandHundrec 3544a36de0 Rework Monster and Monster (Christmas) to use frame labels 2026-02-13 20:34:01 -07:00
RaltyandHundrec 06a1894946 Use MenuList camera for Touch Detection instead 2026-02-13 20:33:58 -07:00
AbnormalPoofandHundrec 40bb5b7a74 Rework Daddy Dearest to use frame labels 2026-02-13 20:33:41 -07:00
RagdollClashandHundrec 062c2fa331 Prevent goBack() from running if mustNotExit is true
Fixes mobile exiting GameOverSubState regardless of said variable.
2026-02-13 20:33:39 -07:00
AbnormalPoofandHundrec 49846dbc13 Re-export the Title Screen text to texture atlas 2026-02-13 20:33:39 -07:00
Karim AkraandHundrec e5a47aa74d Prevent a weird crash when freeplay is loaded 2026-02-13 20:33:20 -07:00
Karim AkraandHundrec 77fffdf370 Improve scrolling in freeplay for different framerates 2026-02-13 20:33:20 -07:00
EliteMasterEricandHundrec eaad0f2450 Additional readability changes. 2026-02-13 20:33:20 -07:00
77faf38d61 Cleanup GRhythmUtil.mirrorNoteDirection() along with a few other changes... ...like accounting for Strumline.KEY_COUNT which is now public, removing an unused temporary variable, and formatting GRhythmUtil.hx
Co-authored-by: ImVeryBad <nightbear843@yahoo.com>
Co-authored-by: ADA-Funni <codingking263@gmail.com>
2026-02-13 20:33:04 -07:00
gamerbrossandHundrec 134d220298 Fix chart editor vocals waveform with tempo changes 2026-02-13 20:33:04 -07:00
KoloandHundrec d4610f5f16 hide the tooltips when killing notes/events 2026-02-13 20:33:04 -07:00
AbnormalPoofandHundrec f5129b3283 Run oxipng on every image in assets/ 2026-02-13 20:32:51 -07:00
AbnormalPoofandHundrec ee6583fa82 Bump flixel-animate to fix an issue with the character select locks 2026-02-13 20:32:40 -07:00
AbnormalPoofandHundrec af72895e54 Blend with every camera underneath 2026-02-13 20:32:29 -07:00
AbnormalPoofandHundrec 8d17ad3dd1 Add cross-camera blending to the blend shader 2026-02-13 20:32:23 -07:00
EliteMasterEricandHundrec 723dd81e2b Modify several checkstyle and hxformat rules. 2026-02-13 20:32:11 -07:00
AbnormalPoofandHundrec 5c45d24e2e Bump polymod to fix BF DJ's cartoons 2026-02-11 02:05:24 -07:00
AbnormalPoofandHundrec ffe72b89a7 Re-export both Freeplay DJs to use frame labels 2026-02-11 02:05:24 -07:00
AbnormalPoofandHundrec bc92d6cebf Add useAnimatePosition and make BF and Pico use it 2026-02-11 02:05:20 -07:00
AbnormalPoofandHundrec b4bc3ca6cf Frame label support for Freeplay DJs 2026-02-11 02:05:20 -07:00
AbnormalPoofandHundrec 8439272d00 Optimize Character Select atlases 2026-02-11 02:05:02 -07:00
AbnormalPoofandHundrec c5a7442415 Change a final to a var in BF DJ's script 2026-02-11 02:05:00 -07:00
Hundrec 31b7fdf02b Re-export every chart + song metadata in the game
The new 0.8.2 json2object library exports charts consistently!
I've re-exported all 61 songs in the game to ensure that future chart changes have readable diffs!
2026-02-10 18:55:38 -07:00
dafe4fe918 Fix a few comment typos
Very important, thank you

Co-Authored-By: charlesisfeline <62410083+charlesisfeline@users.noreply.github.com>
2026-02-10 18:42:20 -07:00
Hundrec 6737146571 Fix ctrl-click on hold note crashing Chart Editor 2026-02-10 18:41:38 -07:00
Hundrec 33d1fd578e Bump Polymod again to fix error on Web 2026-02-06 15:00:35 -07:00
MAJigsaw77andHundrec 0c9edf93c3 Fix multiple script errors cause by the Polymod update. 2026-02-06 15:00:26 -07:00
AbnormalPoofandHundrec 241f140a98 Bump polymod to the latest commit 2026-02-06 15:00:24 -07:00
EliteMasterEricandHundrec 0311c7373e Remove ScriptedObject since it's deprecated anyway (scripts don't have to extend anything anymore) 2026-02-06 15:00:20 -07:00
EliteMasterEricandHundrec ca95fa78c4 Update polymod to the latest experimental. 2026-02-06 15:00:20 -07:00
EliteMasterEricandHundrec 7fa5d77db7 Switch from .init() to .scriptInit() 2026-02-06 15:00:20 -07:00
AbnormalPoofandHundrec dcc3baae26 Re-export Blazin' characters using BTA 2026-02-03 00:56:16 -07:00
ccb270612c Quad batch capsules to prevent a memory leak
Co-Authored-By: Hyper_ <40342021+NotHyper-474@users.noreply.github.com>
Co-Authored-By: Mario Hernando <97055307+MaybeMaru@users.noreply.github.com>
2026-02-02 21:35:06 -07:00
EliteMasterEricandHundrec 404b601012 An extensive review and commenting of various code style enforcements. 2026-02-02 21:35:06 -07:00
AbnormalPoofandHundrec 02d83a766d Re-export the bottom boppers in Week 5 Erect to texture atlas 2026-02-02 21:34:49 -07:00
AbnormalPoofandHundrec f40da353a2 Re-export Pico (Christmas) to texture atlas 2026-02-02 21:34:47 -07:00
ComedyLostandHundrec c0c550e209 Fix Freeplay difficulty graphics not accounting for modded difficulties/variations 2026-02-02 03:26:46 -07:00
AbnormalPoofandHundrec 29ac2ad58b Fix an issue where the sprite didn't respect render texture or stage matrix settings 2026-02-02 03:26:46 -07:00
AbnormalPoofandHundrec 9b0b273004 Adjusted offsets for Pico (Playable) 2026-02-01 03:33:53 -07:00
AbnormalPoofandHundrec 17f120f5ee Fix an issue where multiple characters couldn't be used if they had multiple animate atlases 2026-02-01 03:33:50 -07:00
VirtuGuyandHundrec 284cfc7ebc Use pointer cursor for moveable UI components 2026-02-01 01:23:40 -07:00
Hundrec aa5d30eecc Remove 30% threshold for Chart Editor Results 2026-02-01 01:23:40 -07:00
AbnormalPoofandHundrec c11ecfcb6f Bump polymod to fix in-game cutscenes being unskippable 2026-02-01 01:23:40 -07:00
AbnormalPoofandHundrec aa251af116 Fix Boyfriend (Christmas) being default Boyfriend 2026-01-31 21:15:26 -07:00
AbnormalPoofandHundrec ea7bc750d2 Fix Pico (Playable) throwing null object reference when loaded twice 2026-01-31 21:15:23 -07:00
AbnormalPoofandHundrec a043512f0e Revert a screen bounds fix now that they're fixed in flixel-animate proper 2026-01-31 21:15:23 -07:00
AbnormalPoofandHundrec 5805c310c1 Rework some flickers to use FlxTween.flicker instead of FlxFlicker 2026-01-31 20:22:51 -07:00
AbnormalPoofandHundrec bf0135d48e Bump polymod to the latest experimental commit 2026-01-31 20:10:23 -07:00
AbnormalPoofandHundrec e1f950d3e6 Bump flixel-animate to the latest version 2026-01-31 20:10:23 -07:00
AbnormalPoofandHundrec 018780620d Bump openfl to fix an issue with the Week 6 note splashes 2026-01-31 20:10:23 -07:00
Hundrec ba2f8df30b Fix up 2hot 2026-01-31 17:57:02 -07:00
Hundrec ba77fc6677 Batch of chart fixes and adjustments for 0.8.2
Includes 24 changed charts
View assets commit for diffs
2026-01-31 17:23:19 -07:00
LasercarandHundrec ff3a5c4b9b Cancel screenshot spam timer in pre-state switch 2026-01-31 00:07:57 -07:00
MaybeMaruandHundrec 3aef0e5432 Disable Character Select fade shader when unused 2026-01-31 00:07:57 -07:00
MightyTheArmiddiloandHundrec 83fb754e8e Save the Playtest Results Preference 2026-01-31 00:07:35 -07:00
AbnormalPoofandHundrec 5042448e98 Re-export Nene (Christmas) to texture atlas 2026-01-31 00:07:31 -07:00
AbnormalPoofandHundrec bca18170e7 Disable debug draw for the perspective floor in Spaghetti 2026-01-31 00:07:19 -07:00
EliteMasterEricandHundrec 524468135d Cleanup HScript to stop using Polymod to access other scripted classes (since that can be done directly now). 2026-01-31 00:07:17 -07:00
AbnormalPoofandHundrec de5fe9b950 Fix A-Bot's visualizer showing full bars for a single frame when entering a song 2026-01-29 22:49:19 -07:00
AbnormalPoofandHundrec 17ed98ee4d Re-export Pico (Playable) and Pico (Dark) to texture atlases 2026-01-29 22:27:42 -07:00
AbnormalPoofandHundrec 1b6c2d1f93 Reset the symbol graphic when the Album is destroyed 2026-01-29 22:27:38 -07:00
MAJigsaw77andHundrec 7c90aee994 Fix paths on Windows Subsystem for Linux. 2026-01-29 22:27:38 -07:00
AbnormalPoofandHundrec 96bdfac0b9 Adjust some character scripts 2026-01-29 22:27:22 -07:00
Karim AkraandHundrec 359661b549 FIx the game position on low resolution 4:3 displays 2026-01-29 22:08:00 -07:00
AbnormalPoofandHundrec 1e43902fff Rework 3 Nene variants to use the script extending system 2026-01-29 22:08:00 -07:00
AbnormalPoofandHundrec 84a79f6833 Tweak the blacklist a bit more and add back modOptions map 2026-01-29 22:08:00 -07:00
EliteMasterEricandHundrec bcd3b03a75 Update Polymod again (HScript is now built-in) 2026-01-29 22:07:59 -07:00
EliteMasterEricandHundrec 1ff79a7825 Modified funkin.Save blacklist, and added a new function specifically for retrieving and saving modded preferences in a standard way. 2026-01-29 22:07:59 -07:00
EliteMasterEricandHundrec c3cdddc46e Bump Polymod to the latest version 2026-01-29 22:07:59 -07:00
EliteMasterEricandHundrec ac2f1f602b Tweak FlxSave to use field blacklisting. 2026-01-29 22:07:59 -07:00
Hundrec 40ea803c56 Also process all events other than playAnimation 2026-01-29 22:07:59 -07:00
EliteMasterEricandHundrec dce987b345 Add the processOldEvents parameter to song events, and enable it for FocusCamera. 2026-01-29 22:07:59 -07:00
AbnormalPoofandHundrec 5804aa100d Re-export Pico's GREAT animation to not have huge black and white boxes in the spritemap 2026-01-29 22:07:59 -07:00
HundrecandHyper_ f1276a64a0 Fix crash when saving a chart
Co-Authored-By: Hyper_ <40342021+nothyper-474@users.noreply.github.com>
2026-01-29 22:07:59 -07:00
AbnormalPoofandHundrec 503bbaaa46 Small fix to make the blend shader a lot more accurate 2026-01-29 22:07:59 -07:00
AbnormalPoofandHundrec 0dbd8439bd Remove blend baking from some atlases in the game 2026-01-29 22:07:59 -07:00
LasercarandHundrec 40e5d1a4c1 chart editor - popup when time change added at grid playhead 2026-01-29 22:07:59 -07:00
HundrecandVirtuGuy 58803cd221 Don't smooth the edges of Stage Editor crosshair
Co-Authored-By: VirtuGuy <103977942+virtuguy@users.noreply.github.com>
2026-01-29 22:07:59 -07:00
ff0a226f53 Fix Stage Editor load dialog showing as save
Co-Authored-By: Hyper_ <40342021+nothyper-474@users.noreply.github.com>
Co-Authored-By: Pāvels Rimašs <69017727+anysad@users.noreply.github.com>
2026-01-29 22:07:59 -07:00
LasercarandHundrec 1d19286f6b More Stage Editor selection functionality 2026-01-29 22:07:59 -07:00
KoloandHundrec 5cf114143c reset gameover/pause/countdown stuff after leaving editors 2026-01-29 22:07:59 -07:00
AbnormalPoofandHundrec 8331a1fa7f Re-export the can to restore the unused sparkling effect 2026-01-29 22:07:59 -07:00
db7bee7902 Add support for khronos blend modes, enabling 14 new blend modes to work properly
Co-authored-by: Hyper_ <40342021+NotHyper-474@users.noreply.github.com>
Co-authored-by: ACrazyTown <47027981+ACrazyTown@users.noreply.github.com>
Co-authored-by: PurSnake <56314743+PurSnake@users.noreply.github.com>
2026-01-29 22:07:59 -07:00
Hundrec 2ba0f37a5a Update LICENSE to 2026 2026-01-29 22:07:59 -07:00
Karim AkraandHundrec 31f873ffc2 add ios assets path to clearAssets 2026-01-29 22:07:59 -07:00
Karim AkraandHundrec f7b5d5c62f Lock the game's controls onto keyboard mode as long as a keyboard/gamepad device is connected 2026-01-29 22:07:59 -07:00
ImVeryBadandHundrec 94570d8c7c Center waveforms on health icons and adjust icon X-offsets in Chart Editor 2026-01-29 22:07:59 -07:00
PurSnakeandHundrec f6036343c9 Change FlxPieDial to FlxRadialGauge and display it more nicely visually 2026-01-29 22:07:59 -07:00
MightyTheArmiddiloandHundrec cc8060db5b Fix hold cover positioning 2026-01-29 22:07:59 -07:00
boopboyandHundrec 511f4d5b64 Make musicSuffix accept any pixel suffix 2026-01-29 22:07:58 -07:00
ComedyLostandHundrec b04a7708b1 Clear Freeplay previews when switching selection
Makes it harder to crash Freeplay when scrolling really fast
2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec dd512d02e7 Fix Missing Album Title Showing Flixel Icon 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec ff89c09b6f Fix album roll after character select exit 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec 429e018749 Fix capsules going offset in ranks 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec babe37a0f0 Fix last BPM digit for capsules 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec 122df21223 Fix freeplay pixel icon not selecting 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec 016c53061c Fix soundtray appearing under stickers 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec 429704676a Apply alpha to onion skin after character change 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec cda1ca6897 Fix crash when closing game within stage editor 2026-01-29 22:07:58 -07:00
KoloandHundrec 8954dee6d6 don't use the default names for stage editor stuff 2026-01-29 22:07:58 -07:00
Hyper_andHundrec b71e12ec5c Fix 2nd measure not being rendered and previous measure check being useless 2026-01-29 22:07:58 -07:00
KoloandHundrec c2055a322d add support for decimals in cam bop offset and rate 2026-01-29 22:07:58 -07:00
KadeandHundrec 583700006c less confusing instructions 2026-01-29 22:07:58 -07:00
Kade-gtihubandHundrec 928c2fcc19 QOL and fixes for OffsetMenu 2026-01-29 22:07:58 -07:00
Hundrec 3c913d29e3 Bump assets 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec 52e015ad14 Fix strange menu scrolling behavior on web 2026-01-29 22:07:58 -07:00
EliteMasterEricandHundrec bef8cf9a0e I got a crash in the Crash Handler when the crash happened before FlxGame initialized. 2026-01-29 22:07:58 -07:00
VirtuGuyandHundrec eae4cb2160 Fix char select lock anim not playing on mobile 2026-01-29 22:07:58 -07:00
Hundrec 955c94ed79 Properly credit contributor in a Changelog entry 2026-01-29 22:07:58 -07:00
KoloandHundrec 95109daedb use character's camera focus point to determine the game over cam position 2026-01-29 22:07:58 -07:00
MoonDroidandHundrec 00f28fdb79 Remove duped function and add extra docs 2026-01-29 22:07:57 -07:00
LasercarandHundrec aad014a0ba Mirror notes Chart Editor command 2026-01-29 22:07:57 -07:00
ImVeryBadandHundrec 4917bd0591 Ignore the .idea folder from mods. 2026-01-29 22:07:57 -07:00
charlesisfelineandHundrec 54943b22f8 Remove unused ButtonRemapSubstate 2026-01-29 22:07:57 -07:00
TrofemandHundrec ec6e26c298 Update example mod's API version to 0.8.0 2026-01-29 22:07:57 -07:00
3db958a76b A bunch of typo fixes
Co-Authored-By: charlesisfeline <62410083+charlesisfeline@users.noreply.github.com>
Co-Authored-By: TechnikTil <techniktil@tilnotdrip.org>
Co-Authored-By: MightyTheArmiddilo <132609476+mightythearmiddilo@users.noreply.github.com>
Co-Authored-By: BaranMuzu <104278499+baranmuzu@users.noreply.github.com>
2026-01-29 22:07:57 -07:00
Karim AkraandHundrec f5f2a2ac4c replace the AVM events with ad load/dismiss events and suspend the audio context instead of each audio manually 2026-01-29 22:07:57 -07:00
Cameron TaylorandHundrec 26fe00b559 fix a few of the scripts to point from art/ -> scripts/ when needed 2026-01-29 22:07:57 -07:00
Cameron TaylorandHundrec 899d793e1c move some scripts from art/ submodule to main funkin repo 2026-01-29 22:07:57 -07:00
AbnormalPoofandHundrec 430e2b9dcc Removed an unused TitleOutline shader 2026-01-29 22:07:57 -07:00
AbnormalPoofandHundrec 9189c35585 Title screen cleanup 2026-01-29 22:07:57 -07:00
Hundrec 0855b4da4e Bump assets submod 2026-01-29 22:07:57 -07:00
EliteMasterEricandHundrec ad9c6581ff Update docs for SongLoadScriptEvent so people know what it does. 2026-01-29 22:07:57 -07:00
Kade-gtihubandHundrec 293d303c16 Add Mirror to PlayState Params 2026-01-29 22:07:57 -07:00
PurSnakeandHundrec e4a4197482 Ease Previews 2026-01-29 22:07:57 -07:00
2886e29d58 Add currentStage var to clean up scripts
Co-Authored-By: Kade <26305836+Kade-github@users.noreply.github.com>
Co-Authored-By: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
2026-01-29 22:07:57 -07:00
EliteMasterEricandHundrec 5868cb1d78 Update json2object to respect @:order when exporting + skip default values
* @:order is only for Maps, not Arrays (IDK how this compiled before lol?)
* Don't use @:default on version number.
* Don't use @:default for ChartManifestData
* Don't use @:default for generatedBy
2026-01-13 21:02:24 -07:00
Hundrec 188870dbe6 Clean assets submodule
Build and Upload nightly game builds / gather-changes (push) Canceled after 0s
Build and Upload nightly game builds / gather-tags (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (macos, macos) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (windows, windows) (push) Canceled after 0s
Build and Upload nightly game builds / docker-image (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (html5) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (linux) (push) Canceled after 0s
2026-01-10 00:42:34 -07:00
Hundrec e2c90dced3 Bump docs and template versions to 0.8.1 2026-01-10 00:42:34 -07:00
Hundrec 9fe64c0ec3 Changelog for 0.8.1 and version bump 2026-01-04 03:33:27 -05:00
EliteMasterEricandHundrec 75873d2b92 "scoreable = false" should stop hold notes from GRANTING points too. 2026-01-04 03:15:31 -05:00
EliteMasterEricandHundrec cd2cc9b6a2 Fix "scoreable = false" not working on hold notes. 2026-01-04 03:15:31 -05:00
EliteMasterEricandHundrec 3bf345247e Play the note animation on hit for Non-scoreable notes. 2026-01-04 03:15:31 -05:00
ComedyLostandHundrec 16f3e038d8 Change the script event order in PlayState 2026-01-04 03:15:31 -05:00
2cd7be00af Non scoreable notes for Spaghetti
Co-Authored-By: Eric <4635334+EliteMasterEric@users.noreply.github.com>
2026-01-04 03:15:31 -05:00
Hundrec 92df70c864 Changelog for 0.8.0 - LE SSERAFIM Collab Update 2025-12-30 11:47:49 -05:00
Karim AkraandHundrec 10816add64 Fix sserafim special backing card text cuttinf off on wide res 2025-12-30 07:42:54 -05:00
MAJigsaw77andKarim Akra cd58eebf96 Fix some weird crash on Android Webview from firelab. 2025-12-30 13:43:50 +02:00
Cameron Taylor b04ad86537 assets submod: fabs freeplay scroll work 2025-12-30 02:10:00 -05:00
fabsandCameron Taylor 6d2caca39c allow BGScrollingText text to be edited 2025-12-30 02:09:04 -05:00
EliteMasterEricandHundrec 9472fac99d Add release leaderboard and medal ID 2025-12-29 22:34:08 -05:00
sector-aandCameron Taylor 0c5b5900a5 Prevent updateSelected from being repeatedly called 2025-12-29 20:55:21 -05:00
EliteMasterEricandKade c63935a34a Update polymod to latest 2025-12-29 09:50:58 -08:00
EliteMasterEricandHundrec f4f94eafce Leaderboard fix (plus additional logging cleanup) 2025-12-29 11:07:58 -05:00
EliteMasterEricandHundrec c546e8a709 Implement Eat It Up medal. 2025-12-29 11:07:58 -05:00
MoonDroidandKarim Akra bd533da585 Add assets fixes for Mobile's Spaghetti 2025-12-29 15:06:56 +02:00
sector-aandKarim Akra 609d56613a Add haptics to spaghetti 2025-12-29 14:52:02 +02:00
Karim AkraandKarim Akra 3675df76f5 Exclude sakra spritemap from astc due to rendering artifacts 2025-12-29 14:47:20 +02:00
MoonDroidandKarim Akra 26dd6eac3f Update astc exclusions for new LE-SSERAFIM changes 2025-12-29 14:47:20 +02:00
AbnormalPoofandHundrec eb5eb6b1eb Fix the health icon showing up briefly on restart + Fix BF and GF repeating their anims on restart 2025-12-29 06:56:49 -05:00
AbnormalPoofandHundrec bc79de3c4d Add the Rift of the Necrodancer collab trailer to Attract Mode 2025-12-29 05:47:41 -05:00
Hundrec 2a57c57c11 Change NEW song markings in scripts 2025-12-29 04:13:55 -05:00
AbnormalPoofandHundrec 9e0b51efc6 Don't play the intro cutscene while playtesting in the Chart Editor 2025-12-29 04:13:55 -05:00
AbnormalPoofandHundrec 43abb25114 Remove some unused reference images and rename the stage to Diner 2025-12-29 04:13:55 -05:00
AbnormalPoofandHundrec 8feac720ac Reapply the shader to BF on song restart 2025-12-29 04:13:55 -05:00
AbnormalPoofandHundrec 8ac9209c3b Implement the game over animation for BF in Spaghetti 2025-12-29 04:13:55 -05:00
EliteMasterEricandHundrec 18a1f51397 Fix the default ease direction not working for some events. 2025-12-29 03:29:47 -05:00
EliteMasterEricandHundrec a12ea5d020 Cleaned up documentation for ScriptedSongEvent 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec 413b9503a5 Code cleanup for ZoomCamera song event 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec 8fe9297a37 Code cleanup for SetHealthIcon song event 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec bfb4ad5743 Code cleanup for SetCameraBop song event 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec f400ad99b8 Code cleanup for ScrollSpeed song event 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec 029c3614a6 Code cleanup for PlayAnimation song event 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec 5c5b8f5e0c Code cleanup for FocusCamera song event 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec 9354895f73 Rewrite song event tooltip logic to include default values, plus cleaned up a bunch of adjacent code. 2025-12-29 03:23:37 -05:00
EliteMasterEricandHundrec b502d5dcc6 Reworked logic to make sure measure numbers don't display if the last measure is really short. 2025-12-29 02:31:50 -05:00
EliteMasterEricandHundrec dffd6eee25 Remove "early" cropping code to guarantee Measure 1 always renders. 2025-12-29 02:31:50 -05:00
Cameron Taylor f2bf74b4d1 assets submod: fabs work final stretch 2025-12-29 01:55:23 -05:00
Cameron Taylor 38e7874b42 assets submod: feat j-hope of bts 2025-12-29 00:51:20 -05:00
Cameron Taylor bc200f3677 story menu tracklist spacing tweak 2025-12-29 00:50:28 -05:00
Cameron Taylor f7cc0ffd27 asset submod: mp3 files 2025-12-27 20:15:55 -05:00
Cameron Taylor 91429de35d assets submod 2025-12-27 20:10:50 -05:00
AbnormalPoofandHundrec 7bf3830c62 Fix a script error thrown on HTML5 if any song with Pico (Playable) outside of Weekend 1 was played 2025-12-27 03:10:55 -05:00
AbnormalPoofandHundrec 0bdc79715e Fix blood blend mode on Nene's knife 2025-12-26 21:11:06 -05:00
AbnormalPoofandHundrec cef369110b Fix sserafim-chaewon disappearing if the camera zoomed in too much 2025-12-26 20:57:41 -05:00
AbnormalPoofandHundrec 63f42de828 Fix Otis script error when pressing F5 2025-12-26 20:42:05 -05:00
AbnormalPoofandHundrec f862274a41 Only initialize the timemap for pico-speaker and otis-speaker if debug if off 2025-12-26 20:42:05 -05:00
Cameron Taylor 7197c28f62 update difficulty ratings for spaghetti (fixes FUNK-4119) 2025-12-26 15:10:34 -05:00
Cameron Taylor 8640a2a18b assets submod fabs spaghetti 2025-12-26 15:03:46 -05:00
EliteMasterEricandCameron Taylor e248562592 Delete the test suite. 2025-12-26 14:48:14 -05:00
AbnormalPoofandKade 1d2a47aea0 Revert polymod back to 39b1d179b0ebabb43234fb02604d7a91250d7fc4 due to performance issues with Spaghetti on HTML5 2025-12-24 23:16:19 -08:00
MAJigsaw77andEric 5c338f8f5c Fix FlxMacro from breaking the completion server. 2025-12-24 17:39:06 -05:00
MoonDroidandEric dafd087783 Update flixel ref for FlxText fixes 2025-12-24 17:38:57 -05:00
Hundrec 1163ae5a41 Bump mod API version to 0.8.0+
Be sure to update your mods to make them work!
2025-12-24 16:03:54 -05:00
EliteMasterEricandHundrec 81d85711fc Update Polymod to fix an issue with objects being casted to strings. 2025-12-24 16:03:54 -05:00
AbnormalPoofandAbnormal 5567e76ce6 Fix gf-dark not properly transitioning between lit and unlit 2025-12-24 13:28:43 -06:00
MoonDroid e4ab06cd73 Exclude week titles from ASTC Compression 2025-12-25 00:52:37 +07:00
MoonDroid 622dd4b6fa Exclude ref from astc and add lipsync 2025-12-25 00:52:37 +07:00
MightyTheArmiddiloandAbnormal 3b5e5893c0 Make space detect HaxeUI Focus in the Chart Editor 2025-12-24 00:50:14 -06:00
AbnormalPoofandHundrec 7fd8a46e94 Fix Nene being too high in Pico's game over 2025-12-24 01:45:58 -05:00
AbnormalPoofandKade 1f310a1068 Bump submod to fix an issue with nene-tankmen 2025-12-23 22:16:26 -08:00
ExcodeandKade 342c3cd7d6 Update OffsetMenu.hx 2025-12-23 22:00:42 -08:00
Kade-gtihubandAbnormal 31b8faefca add nene rexport commit 2025-12-23 23:59:53 -06:00
AbnormalPoofandKade 9ddd3b1ee8 Re-export Nene and Nene (Dark) to texture atlas 2025-12-23 21:55:34 -08:00
Kade-gtihubandAbnormal a7b1a4546e Update assets commit to include pause fix 2025-12-23 23:49:02 -06:00
MoonDroidandKade 110200f6b2 Remove redundant nullSafety toggles 2025-12-23 21:40:57 -08:00
AbnormalPoofandKade b776e6e200 Add LE SSERAFIM Story Mode prop 2025-12-23 21:36:20 -08:00
AbnormalPoofandKade 580d6da37f Add startingAnimation functionality to level props 2025-12-23 21:36:20 -08:00
Kade fd6ae9cf6c Convert magic variable to Fixel Constant 2025-12-23 21:32:42 -08:00
Hyper_andKade 200f798437 Improve the math for the song name position 2025-12-23 21:32:42 -08:00
AbnormalPoofandHundrec 00bc6c3d19 Fix the rain shader not working at all 2025-12-23 21:49:42 -05:00
EliteMasterEricandHundrec 54a0234c6b Fix a bug where opening the welcome screen would restart the theme music 2025-12-23 20:45:23 -05:00
EliteMasterEricandHundrec e0fcb1a3ed Fix an issue where welcome music would resume after focus lost while playtesting. 2025-12-23 20:45:23 -05:00
EliteMasterEricandHundrec 696a1755b3 Clean up isPlaytesting check. 2025-12-23 20:45:23 -05:00
EliteMasterEricandHundrec 4631950db8 Fix an issue where welcome theme would play during playtesting. 2025-12-23 20:45:23 -05:00
AbnormalPoofandAbnormal 3bb9449c60 Make multi-animateatlas characters use a getAnimateAtlas function instead of making a new FunkinSprite 2025-12-23 14:09:57 -06:00
MAJigsaw77andMoonDroid a966299926 Bump extension-admob. 2025-12-24 01:10:03 +07:00
Hundrec c2bf9da77d Consistent Chart Editor Window vbox heights 2025-12-23 03:07:49 -05:00
EliteMasterEricandHundrec 2ac6471cff Fix a bug where selecting multiple events would replace one with the other (plus cleaned up logging!) 2025-12-23 03:07:49 -05:00
AbnormalPoofandCameron Taylor c79d27eca8 Implement the Freeplay icon for LE SSERAFIM
assets submod
2025-12-23 02:16:02 -05:00
Cameron Taylor 99c67e963b assets submod fabs spaghetti 2025-12-23 02:03:53 -05:00
fabsandCameron Taylor a7b253ce9f sserafim shader 2025-12-23 02:03:53 -05:00
EliteMasterEricandAbnormal c25c784feb Disable "DEBUG"-level OpenFL messages to avoid logspam from shader stuff. 2025-12-22 20:07:36 -06:00
AbnormalPoofandHundrec b2d7e0a681 Take global offsets into account for the character preview 2025-12-22 20:59:56 -05:00
EliteMasterEricandHundrec da40dfb824 Fix measure ticks drawing beyond the end of the song. 2025-12-22 20:59:44 -05:00
EliteMasterEricandAbnormal 2177534a5b Add support for stacked patches, improve capabilities of test operations 2025-12-22 19:55:01 -06:00
MoonDroid 77ac4d8d00 Remove old junk code 2025-12-23 05:18:25 +07:00
MAJigsaw77andMoonDroid d56fbf0318 Use our fork of newgrounds. 2025-12-23 05:18:25 +07:00
MAJigsaw77andMoonDroid e9ac655835 Polish the webview stuff. 2025-12-23 05:18:25 +07:00
MAJigsaw77andMoonDroid 203e4cce81 Dont shadow the NG API aswell as automaticaly close the webview. 2025-12-23 05:18:25 +07:00
MAJigsaw77andMoonDroid 4d842ec1c9 WIP Support for Newgrounds on Mobile. 2025-12-23 05:18:25 +07:00
EliteMasterEricandAbnormal 66d6e14edb Fix a build issue caused by copy-pasting AnsiUtil to the tools package. 2025-12-22 12:42:44 -06:00
EliteMasterEricandKade 25879b234c Added more new colors to the AnsiUtil 2025-12-22 10:01:48 -08:00
EliteMasterEricandKade fcc4df0342 Add ANSI logging to even MORE classes! 2025-12-22 10:01:48 -08:00
EliteMasterEricandKade 70e5ad8874 ANSIUtil.hx code style cleanup, plus use ANSI logging in registry classes! 2025-12-22 10:01:48 -08:00
Karim AkraandMoonDroid 612b5d8009 Remove the new pillarboxing system from mobile
Mobile is not resizable so we don't really need it there
2025-12-21 16:23:16 +07:00
Karim AkraandMoonDroid d2fdda09d5 Fix the game docking to the bottom of the screen for iPads 2025-12-21 16:23:16 +07:00
AbnormalPoofandHundrec fc4bdb04b9 Fix spirit againnnnn 2025-12-21 03:25:52 -05:00
AbnormalPoofandHundrec de4d1370c7 Fix multiple characters 2025-12-21 03:25:52 -05:00
AbnormalPoofandHundrec a06500d4ed Fixes with the None character and Blazin' characters 2025-12-21 03:25:52 -05:00
AbnormalPoofandHundrec 8d3b99fbf7 Set cornerPosition to 0 2025-12-21 03:25:52 -05:00
AbnormalPoofandHundrec c90b7832df Revert some bandaid fixes for animate atlas characters to make them appear properly 2025-12-21 03:25:52 -05:00
9f59231d58 Chart Editor Character Previews
Co-Authored-By: ComedyLost <106041578+comedylost@users.noreply.github.com>
2025-12-21 03:25:52 -05:00
FuroandMoonDroid 592bf1a922 Preload the Result Screen Music loop is there is an intro 2025-12-20 19:01:46 +07:00
sector-aandMoonDroid 6e326e8e18 Prevent the week choosing by tapping when a week is selected 2025-12-20 18:23:45 +07:00
EliteMasterEric e888708b0a Update assets submodule 2025-12-19 17:09:38 -05:00
AbnormalPoofandEric 3f668fa5c8 Add album text and story menu level title for Spaghetti 2025-12-19 17:09:38 -05:00
HundrecandKolo 6e08aaeb34 Fix Chart Editor event values getting reset
Co-Authored-By: Kolo <67389779+koloindacrib@users.noreply.github.com>
2025-12-19 08:03:37 -05:00
AbnormalPoofandKade c32f794a6f Bump polymod 2025-12-17 14:32:26 -08:00
EliteMasterEricandAbnormal d2680209eb Fix a crash when browsing for files while Pause on Unfocus is disabled. 2025-12-16 08:01:28 -06:00
AbnormalPoofandAbnormal 253bb2d7f6 Bump flixel-animate and simplify rimlights for atlases 2025-12-16 01:15:33 -06:00
AbnormalPoofandCameron Taylor 8d4ced40a6 Add album art for Spaghetti in Freeplay 2025-12-15 22:24:28 -05:00
HundrecandEric 2cc04ee79d Update art 2025-12-15 20:17:49 -05:00
EliteMasterEricandHundrec e123e7ba8f Make the 'Art' submodule shallow by default 2025-12-15 17:49:04 -05:00
MAJigsaw77andHundrec 8920d9f992 Implement clearAssets for Android when building an .apk. 2025-12-15 16:30:49 -05:00
Hyper_andHundrec 6c905a59c8 fix: Pause state not being properly set during cutscenes 2025-12-15 16:28:44 -05:00
MoonDroidandHundrec 5464394dff fix: Add callbacks that fades in the pause button for video cutscenes 2025-12-15 16:28:44 -05:00
AbnormalPoofandHundrec 90f8242dc0 Re-export BF to remove that one dot in the confirm animation 2025-12-15 13:21:45 -05:00
MoonDroidandHundrec 3fc00b02dd Decrease blocksize for higher quality flashes on otis' gun bangs 2025-12-15 04:49:22 -05:00
Hyper_andAbnormal 2b55a4c5d6 Skip processing dead notes 2025-12-14 22:03:44 -06:00
AbnormalPoofandHundrec a8799fd60c Fix atlases moving to the wrong place on restart
Bundled with a ton of manual offset fixes and adjustments
2025-12-13 22:41:49 -05:00
Hundrec 8fb4a985f6 Remove Chart Editor tick trace and bump assets 2025-12-12 22:59:24 -05:00
MoonDroidandHundrec 79d09e75fc Decrease blocksizes for rando and sniper in Week 7 Erect 2025-12-12 22:46:50 -05:00
AbnormalPoofandHundrec 5589b5a4d5 Re-export the background Tankmen in Week 7 Erect as texture atlases 2025-12-12 22:46:50 -05:00
sector-aandHundrec 1e661c597a Don't add the current difficulty in Difficulty entry of the Pause Menu.
Revert "Don't add the current difficulty in `Difficulty` entry of the Pause Menu."

This reverts commit e0518f636dceeafa6dd00c922972afa5840e1161.

Do nothing if you change difficulty to the same one.
2025-12-12 21:01:51 -05:00
LasercarandHundrec 4e7755865c Chart Editor - additional audio checks for vocal only songs 2025-12-12 20:45:53 -05:00
LasercarandHundrec 960d2f3cf1 Welcome back welcome music 2025-12-12 20:45:53 -05:00
Karim AkraandMoonDroid 7debbc155f Update lime ref to hide the ios home indicator 2025-12-13 08:06:11 +07:00
AbnormalPoofandHundrec fbdd68e9d3 Fine-tuned offsets for 2 characters 2025-12-12 19:55:16 -05:00
MoonDroidandHundrec 12b2222819 Exclude pico holding nene from astc compression 2025-12-12 19:55:16 -05:00
AbnormalPoofandHundrec da3b1ec2f2 Re-export Pico (holding Nene) to texture atlas 2025-12-12 19:55:16 -05:00
MoonDroidandHundrec 66b49a6369 Exclude gf Texture Atlases from ASTC Compression 2025-12-12 19:22:48 -05:00
Cameron TaylorandKade 9d3ae3c974 remove some redundant getControls() in FreeplayState, just access controls directly 2025-12-12 08:34:59 -08:00
Kade-gtihub 5f80db48de Crash Fix and Visual Bug Fix on InstSelect 2025-12-12 08:34:59 -08:00
Hyper_andEric 8481785b3b fix: Fix incorrect NaN checks & simplify finite check 2025-12-12 11:17:16 -05:00
Hyper_andHundrec 1a6f6aee96 Fix a syntax error with the RegistryMacro and a partial fix for code completion 2025-12-12 09:34:31 -05:00
Cameron TaylorandHundrec e1e49bb19b change capsuleOptionsMenu -> instSelectMenu in FreeplayState 2025-12-12 08:16:37 -05:00
Cameron TaylorandHundrec ca7891e5cb remove some redundant getControls() in FreeplayState, just access controls directly 2025-12-12 08:16:37 -05:00
Cameron TaylorandHundrec dbf0776dbf remove all the letterSort.inputEnabled stuff, only use the FreeplayState's uiStateMachine to check if inputs should be enabled 2025-12-12 08:16:37 -05:00
Cameron TaylorandHundrec 040605e2a9 use UIStateMachine in FreeplayState instead of checking/modifying controls.active everywhere
thus making it easier to fix the "unlocked inputs" issue (fix FUNK-2021)
2025-12-12 08:16:37 -05:00
AbnormalPoofandHundrec 16f047f643 Re-export Otis (Speaker Shooter) to texture atlas 2025-12-12 07:53:24 -05:00
EliteMasterEricandHundrec f99eeae7a6 Some optimization to highlighted note checks. 2025-12-12 07:29:18 -05:00
LasercarandHundrec 8cb5ed91ba A big commit for a small fix
Cursor in chart editor breaks in square after placing and removing note
2025-12-12 07:29:18 -05:00
Hundrec 3fe7644f72 Widen Events toolbox 2025-12-12 07:29:18 -05:00
KoloandKade 9f6879ecc9 add frames to the event data toolbox 2025-12-11 12:38:17 -08:00
AbnormalPoofandAbnormal 5c44707bc9 Re-export some Week 7 assets to texture atlases 2025-12-10 14:32:13 -06:00
MAJigsaw77andKade ee2a77d570 Add the ability to embed songs audio files. 2025-12-10 11:26:33 -08:00
AbnormalPoofandAbnormal 770bad3a6d Re-export Girlfriend (Dark) to texture atlas 2025-12-10 00:54:30 -06:00
AbnormalPoofandAbnormal e8ad0e605f Re-export Spooky Kids to remove a billion black lines 2025-12-10 00:52:03 -06:00
AbnormalPoofandAbnormal 3267ebf0d9 De-sharpen some atlases and optimize Monster's atlas 2025-12-10 00:39:14 -06:00
LasercarandHundrec 5a2c364c50 Chart - Fix notekinds changing on multi select
Also reverts a change by this author that caused selected events to be reset when the event toolbox is first opened.
2025-12-10 00:43:12 -05:00
LasercarandHundrec d3c16b45c6 Custom note kind field fix
Now gets hidden if the note selected isn't a custom one
2025-12-10 00:43:12 -05:00
VirtuGuyandHundrec dff2a1e487 Fix difficulty toolbox showing wrong song name 2025-12-09 23:41:27 -05:00
AbnormalPoofandHundrec 5dce8af395 Re-export bf and bf-dark to texture atlas form and sever the death animation from bf-car 2025-12-09 23:03:00 -05:00
a844618ca2 fix: Stop the game from muting in silent mode when you unfocus then focus
Co-Authored-By: Karim Akra <144803230+KarimAkra@users.noreply.github.com>
2025-12-09 22:53:41 -05:00
VirtuGuyandHundrec 8c43f6f67a Fix results screen scrolling text speed 2025-12-09 21:07:17 -05:00
AbnormalPoofandHundrec 09e1b17905 Make Tankman less shiny 2025-12-09 19:56:38 -05:00
AbnormalPoofandHundrec 2294ef80d5 Re-export Tankman and Tankman (Bloody) to texture atlas 2025-12-09 19:56:38 -05:00
KoloandAbnormal 4dd87e7308 make volume settings optionally carry over to playtest 2025-12-09 12:53:18 -06:00
AbnormalPoofandAbnormal 47e462e836 Bump flixel-animate and add useRenderTexture to FunkinSprite as an option. 2025-12-09 10:51:42 -06:00
Hyper_andHundrec 00a1a7d1e2 fix: solve crash when deleting save data 2025-12-09 02:52:15 -05:00
LasercarandAbnormal eeba677da9 Chart Editor playtest option for Results Screen 2025-12-09 01:12:11 -06:00
charlesisfelineandHundrec 9f0a4988a3 null checkin for set health icon 2025-12-09 01:44:07 -05:00
AbnormalPoofandHundrec ede100c9da Re-export Girlfriend to texture atlas 2025-12-09 01:09:32 -05:00
AbnormalPoofandHundrec 3b65050ef3 Shift the flames how did this happen 2025-12-09 00:19:25 -05:00
AbnormalPoofandHundrec 63fedc046b Re-export the Freeplay difficulty stars using BTA 2025-12-09 00:19:25 -05:00
HundrecandAbnormal 741c8aa5fc Quick chart tweak bump 2025-12-08 20:18:02 -06:00
Cameron TaylorandEric bdddf364a0 change fpScoreDisplay to private variable 2025-12-08 21:03:11 -05:00
Cameron TaylorandEric 109fa509fa change fp -> fpScoreDisplay, as a more descriptive variable name 2025-12-08 21:03:11 -05:00
Cameron Taylor 90f79be163 assets submod more le sserafim stuff from fabs 2025-12-08 19:32:05 -05:00
MAJigsaw77andEric 2d0a1cdabb Remove WINAPI_GetProcessMemoryWorkingSetSize dublicate. 2025-12-08 18:14:17 -05:00
EliteMasterEricandMAJigsaw77 3bc8b0317e Adjust WinAPI to allow setting theme on specific windows.
Co-authored-by: MAJigsaw77 <MAJigsaw77@users.noreply.github.com>
2025-12-08 18:14:17 -05:00
Cameron TaylorandEric d361a323a2 early return in playVocals() to remove a level of nesting 2025-12-08 15:30:02 -05:00
Cameron TaylorandEric ceb364472e reduce code copypaste a tiny smidge for chart editor waveforms 2025-12-08 15:30:02 -05:00
MoonDroidandAbnormal 41f04a11b3 Decrease blocksize compression for gfChill 2025-12-07 12:00:30 -06:00
AbnormalPoofandAbnormal 1a24ed42e1 Re-export GF's character select sprites to remove a stray line 2025-12-07 12:00:30 -06:00
AbnormalPoofandAbnormal 75bc9300fb Re-export the Parents in the Eggnog Erect cutscene with BTA 2025-12-07 11:58:29 -06:00
AbnormalPoofandHundrec e76d3c3d26 Add global offsets for Pico and Nene's pixel variants 2025-12-06 22:57:15 -05:00
FuroandHundrec 6f3264676c Added funnyColor backwards compatibility 2025-12-06 21:23:46 -05:00
FuroandHundrec 67846d6c4b Change BGScrollingText to a single FlxText
BGScrollingText is now a single FlxText drawn multiple time in a single frame which is more optimized that a FlxSpriteGroup with multiple FlxSprites taking the text's graphic
2025-12-06 21:23:46 -05:00
EliteMasterEricandCameron Taylor 2c588b6a93 Tweak vertical offset on the measure numbers. 2025-12-06 20:27:10 -05:00
EliteMasterEricandCameron Taylor f102b0d681 Redo the backing color of the measure ticks. 2025-12-06 20:27:10 -05:00
EliteMasterEricandCameron Taylor 86a0c8a04e Rewrite measure ticks to not get redrawn EVERY FRAME GODAM 2025-12-06 20:27:10 -05:00
Hyper_andCameron Taylor 75b13d0052 fix: Cursor not properly fading out when exiting CharSelect with backspace 2025-12-06 20:15:20 -05:00
Cameron TaylorandAbnormal 9654d5ad98 remove unused pitch stuff in CharSelect 2025-12-06 16:37:21 -06:00
Cameron TaylorandAbnormal 07fb33be62 fix char select inputs for keyb and controllers 2025-12-06 16:37:21 -06:00
EliteMasterEricandCameron Taylor 9d8a653ab8 Clean up the Lime target config dropdown by removing unused targets like AIR and Flash. 2025-12-04 18:49:40 -05:00
AbnormalPoofandAbnormal 4a7115b103 Re-export Pico's PERFECT rank animation with BTA 2025-12-04 12:58:49 -06:00
AbnormalPoofandAbnormal b78d989394 Fix the cursor lerping from the top left in character select 2025-12-04 10:50:32 -06:00
AbnormalPoofandAbnormal 88baab88e9 Spaghetti chart tweaks from Cory 2025-12-04 09:06:35 -06:00
AbnormalPoofandEric 3f0082d6e7 Add getFramesWithKeyword() 2025-12-03 02:13:47 -05:00
anysadandKade 0df50ecf5d Remove unused Chart Editor Context Menu code 2025-12-02 21:16:30 -08:00
MAJigsaw77andAbnormal 55144f0a9e Bump hxvlc to 2.2.5. 2025-11-30 16:06:35 -06:00
MAJigsaw77andAbnormal a8d5651ab0 Remove the yellow notice. 2025-11-30 15:29:12 -06:00
MAJigsaw77andAbnormal 535aecdc01 Improve library version checks. 2025-11-30 15:29:12 -06:00
AbnormalPoofandAbnormal f7727c0584 Lower him a little 2025-11-30 14:38:33 -06:00
AbnormalPoofandAbnormal 1e52320014 Re-export Boyfriend (Car) to texture atlas 2025-11-30 14:38:33 -06:00
AbnormalPoofandAbnormal 3cd12d4298 Revert "remove keyboard "scheme" related code, completely unused"
This reverts commit 7c8b7eee57.
2025-11-29 20:12:00 -06:00
EliteMasterEricandAbnormal f94ec82c96 Update HXCPP to support building with C++20 2025-11-29 13:03:42 -06:00
Cameron TaylorandEric 3a0e32d24f update our custom FunkinAction check() function with newer logic from FlxAction 2025-11-29 06:41:33 -05:00
Cameron TaylorandEric 6eba4bbeda remove the unused _P and _R stuff from the Action enum abstract 2025-11-29 06:41:33 -05:00
Cameron TaylorandEric 724286f497 more controls.hx cleanup, remove a few unused functions, and add Void return types to make checkstyle happier 2025-11-29 06:41:33 -05:00
Cameron TaylorandEric 7c8b7eee57 remove keyboard "scheme" related code, completely unused 2025-11-29 06:41:33 -05:00
Cameron TaylorandEric c23472903a remove more unused vars in controls.hx 2025-11-29 06:41:33 -05:00
AbnormalPoofandHundrec 846dbd6259 Adjust Darnell's idle animation 2025-11-29 04:14:54 -05:00
AbnormalPoofandHundrec 05447c7894 Re-export THE CHUDDER!!!! 2025-11-29 04:14:54 -05:00
KoloandHundrec c4c7e17361 better backwards compatibility for FlxAtlasSprite -> FunkinSprite 2025-11-29 02:32:47 -05:00
Cameron TaylorandHundrec f3057dd9c3 use FlxMath.wrap() to make this cursor wrapping cleaner in CharSelectSubState 2025-11-28 20:34:58 -05:00
Cameron TaylorandHundrec 0e195cfb9e clean up spamDirection code, use FlxDirectionFlags 2025-11-28 20:34:58 -05:00
MoonDroidandAbnormal 611280b069 fix: Make lerping consistent across different FPS 2025-11-28 19:30:36 -06:00
AbnormalPoofandHundrec 74e2ff7b85 Fix the cigarette not appearing in the Pico doppelganger cutscene 2025-11-28 20:21:56 -05:00
Cameron TaylorandAbnormal 326b81c225 move character select cursor sprite code into it's own class 2025-11-28 18:35:10 -06:00
VirtuGuyandHundrec 3d4a281da6 Fix ghost tapping behavior when unpausing 2025-11-28 14:31:30 -05:00
Cameron TaylorandKade ff4a08541c early return for Chart Editor handleCursor() 2025-11-28 10:36:08 -08:00
EliteMasterEricandHundrec 7f5acf3b2d Fix a bug where cycling difficulties wasn't calculated properly when switching variations. 2025-11-28 04:45:16 -05:00
EliteMasterEricandHundrec eed0665e42 Switching difficulty now forces the selection to be cleared. 2025-11-28 04:45:16 -05:00
EliteMasterEricandHundrec 85c549c15c Add difficulty switches to the undo/redo history. 2025-11-28 04:45:16 -05:00
Hyper_andHundrec 5763ae5b72 Add hardware keyboard detection on Android 2025-11-28 03:12:25 -05:00
MoonDroidandAbnormal 55cad46640 Update lime ref 2025-11-28 00:39:48 -06:00
EliteMasterEricandCameron Taylor 65985173fa Implemented Clone Difficulty button in the Difficulty toolbox.
Fixes to Clone Difficulty dialog, and added Move Difficulty dialog.

assets submod
2025-11-28 01:05:19 -05:00
EricandGitHub 7dfd6ab8cd Merge pull request #1992 from FunkinCrew/pr-6495/koloindacrib/chart-editor-playtest-pause
[PUBLIC PR] Fix the game immediately pausing when playtesting in the Chart Editor
2025-11-26 22:45:27 -05:00
KoloandAbnormalPoof ba3481356c fix starting a playtest opening pause menu 2025-11-26 15:49:22 -06:00
AbnormalPoofandHundrec 58467843d5 Make Monster's singLEFT-hold offset consistent with singLEFT 2025-11-26 14:21:54 -05:00
HundrecandAbnormal 3219c40629 Adjust lyrics for Monster and Winter Horrorland
Fix some errors and formatting
2025-11-26 13:12:43 -06:00
d35fe9726b Update assets and add getAtlasSettings() for texture atlas characters
Co-authored-by: VirtuGuy <Wyatt2010shaw@gmail.com>
2025-11-26 13:12:43 -06:00
EliteMasterEricandHundrec d311a3ad5b Implement a checkbox to hide the waveform, which improves FPS. 2025-11-26 01:07:53 -05:00
EliteMasterEricandHundrec 358d91251e Code cleanup and additional docs for WaveformSprite. 2025-11-26 01:07:53 -05:00
EliteMasterEricandHundrec 25256fad3c Pause the chart editor when focus is lost. 2025-11-26 00:57:43 -05:00
Hundrec 3f4056c9f9 Don't play Stress Pico cutscene if charting 2025-11-26 00:18:16 -05:00
PurSnakeandHundrec 055f62026e Properly load variation scripts in the Chart Editor when playtesting 2025-11-26 00:18:16 -05:00
amyspark-ng ✨andHundrec 593456e53e fix notelay sound when not in grid 2025-11-26 00:03:55 -05:00
AbnormalPoofandHundrec ef9f77c665 Re-export Pico's confirm backing card with BTA to bake the filters 2025-11-25 13:59:49 -05:00
Karim AkraandHundrec dc88d62f43 Fix stickers transition looking off-size when resizing the game during a song 2025-11-25 12:22:22 -05:00
VirtuGuyandHundrec 6909b5eca1 Fix anim editor onion skin offset problems 2025-11-25 12:15:00 -05:00
AbnormalPoofandHundrec 949195fb9c Get the first owned character ID and use that for results 2025-11-25 12:07:32 -05:00
AbnormalPoofandHundrec 23aa7156dc Fix the nametag erroneously showing as "Boyfriend" when entering character select as Pico 2025-11-25 11:59:31 -05:00
Hundrec 52694d0999 Bump version to 0.8.0 2025-11-25 11:59:31 -05:00
Cameron Taylor 3e4c15e019 fabs work for le sserafim in progress 2025-11-24 20:54:21 -05:00
MightyTheArmiddiloandHundrec 9e645db007 up and only up the notifs go 2025-11-23 06:51:25 -05:00
PurSnakeandHundrec 3040692852 Reduce volume on focus lost 2025-11-23 06:36:51 -05:00
193c443bc4 Add offset var to note in NoteStyle.hx
Co-authored-by: NebulaStellaNova <78160470+NebulaStellaNova@users.noreply.github.com>
2025-11-23 06:12:45 -05:00
cb184f89ec 2hot cutscene realignment
Co-authored-by: hucks5 <hucks5@users.noreply.github.com>
2025-11-23 06:01:38 -05:00
c2d89243b6 Nene now swings her legs in-game in the 2hot cutscene
Co-authored-by: hucks5 <hucks5@users.noreply.github.com>
2025-11-23 05:49:23 -05:00
Nebula S. NovaandAbnormal 75d085d733 Remove redundant/useless sharpness setting from infoDisplay
This value only sets if you have antiAliasType as ADVANCED, which v-slice does not.
2025-11-23 04:33:21 -06:00
AbnormalPoofandHundrec e550440407 Re-export Week 5 characters (and dad too I guess) 2025-11-23 05:17:13 -05:00
AbnormalPoofandHundrec f1eb67bf52 Fix an issue where the default results animation (BF) wouldn't show up if the character ID wasn't associated 2025-11-23 05:11:47 -05:00
AbnormalPoofandHundrec efafb1acea Re-export week 4 characters (Mom and GF) to texture atlases 2025-11-23 04:58:41 -05:00
AbnormalPoofandHundrec 56c6c392b3 Fix Nene's heart in Week 7 being affected by rimlight 2025-11-23 03:40:25 -05:00
AbnormalPoofandHundrec d5afb340a2 Add flush() back to Save 2025-11-23 03:29:46 -05:00
JVN-PixelsandAbnormal f95fbf57b6 Fix typo in Medals.hx 2025-11-23 02:22:34 -06:00
JVN-PixelsandAbnormal 6b5db32522 Fix typo in Conductor.hx 2025-11-23 02:22:34 -06:00
charlesisfelineandAbnormal 300ef8fef1 yeahh im listerning 2025-11-23 02:22:34 -06:00
MightyTheArmiddiloandAbnormal e09c256c80 The true different was the frie- 2025-11-23 02:22:34 -06:00
ExcodeandAbnormal 7174e776ac Remove useless comment on CharSelectSubState.hx 2025-11-23 02:22:34 -06:00
ExcodeandAbnormal f10ea83530 Update SpectogramSprite.hx 2025-11-23 02:22:34 -06:00
ComedyLostandAbnormal e19f2155b1 evil typo 2025-11-23 02:22:34 -06:00
86a18067dd Fix Pico Backing Card Confirm
Co-authored-by: VirtuGuy <VirtuGuy@users.noreply.github.com>
2025-11-23 02:57:30 -05:00
AbnormalPoofandHundrec 15caa7a6f7 literally one digit im krueing myself 2025-11-23 02:43:36 -05:00
KoloandHundrec b2897bc86c copy maps instead of assigning 2025-11-23 01:53:22 -05:00
MikolkaandHundrec cc486ad94b Add song prev fade in delay 2025-11-23 01:39:38 -05:00
AbnormalPoofandHundrec c5c22fe1a5 More strict null checking for animation 2025-11-22 19:46:20 -05:00
VirtuGuyandHundrec 302beb2d6e Fix Menu Music Not Playing After Freeplay Exit 2025-11-22 15:39:25 -07:00
ACrazyTownandAbnormal a702040dfb [fix] Replace wrong PRESSED checks with JUST_PRESSED checks 2025-11-22 13:14:55 -06:00
VirtuGuyandAbnormal 1510e5922e Fix character select cutout size 2025-11-22 13:13:57 -06:00
EliteMasterEricandHundrec 92344d2e30 Don't play the music if the game paused due to losing focus. 2025-11-22 05:19:50 -07:00
EliteMasterEricandHundrec 5ce3315967 Only do it if "Pause on Unfocus" is enabled. 2025-11-22 05:19:50 -07:00
EliteMasterEricandHundrec 858199e298 Pause the pause music if the window isn't focused (just like all other audio) 2025-11-22 05:19:50 -07:00
VirtuGuyandHundrec 0739a40abf Fix mobile compiling issues 2025-11-21 20:58:29 -07:00
AbnormalPoofandHundrec 215bbdb9da fix for "janky" pico rank anim 2025-11-21 18:53:42 -07:00
AbnormalPoofandHundrec c9d4d07dc7 bump to latest and include all classes when building 2025-11-21 18:53:42 -07:00
AbnormalPoofandHundrec 7e1442e969 maybe fix it 2025-11-21 18:53:42 -07:00
fcfde61b24 Replace FlxAnimate with flixel-animate
Co-Authored-By: Hyper_ <40342021+NotHyper-474@users.noreply.github.com>
Co-Authored-By: MaybeMaru <97055307+MaybeMaru@users.noreply.github.com>
Co-Authored-By: Trayfellow <48515908+trayfellow@users.noreply.github.com>
2025-11-21 18:53:42 -07:00
Cameron Taylor fe6f5eb821 small typo in pausesubstate UI_DOWN -> controls.UI_DOWN 2025-11-21 19:17:13 -05:00
Cameron Taylor 512a3fc927 some extra pausesubstate tidyings/organization 2025-11-21 18:58:16 -05:00
EliteMasterEricandCameron Taylor 3bf06a1cc9 Check if Pause menu inputs were JUST pressed to prevent firing immediately when opening. 2025-11-21 18:58:16 -05:00
EliteMasterEricandCameron Taylor 2c3d61e629 Implement justPressed and justReleased chesk for PAUSE and ACCEPT. 2025-11-21 18:58:16 -05:00
EliteMasterEricandCameron Taylor 20051109dd Remove unused control actions. 2025-11-21 18:58:16 -05:00
EliteMasterEricandCameron Taylor 267cc55e40 Don't reset inputs on state switch (fixes justPressed triggering as soon as you enter a state) 2025-11-21 18:58:16 -05:00
EliteMasterEricandCameron Taylor 766937d5fe Cancel ongoing timers when leaving Freeplay. 2025-11-21 02:06:50 -05:00
Cameron Taylor 18d8fe7248 only show that giant white debug box thing on mobile 2025-11-21 02:05:01 -05:00
EliteMasterEricandCameron Taylor f7cb738803 WIP on implementing multiple keys per direction 2025-11-21 02:01:35 -05:00
EliteMasterEricandHundrec 400ef63c5a Attract State shouldn't interrupt the GF ringtone song. 2025-11-20 05:25:52 -07:00
LasercarandHundrec 1ff6a1656c Open target song stage with song characters
Also, nullable test characters (but it's buggy)
2025-11-20 05:19:01 -07:00
EricandGitHub 20da3f85f1 Merge pull request #1933 from FunkinCrew/kade-github/non-pausestate-pausing
[ENHANCEMENT] Non-descript state pausing for PlayState
2025-11-18 22:28:39 -05:00
KadeandGitHub d5e14a7101 change shouldPause to shouldSubstatePause 2025-11-18 16:13:11 -08:00
Kade-gtihub ffa8622903 non-descript state pausing 2025-11-18 16:05:40 -08:00
Kade-gtihubandHundrec 8b559639e8 visible check for notes 2025-11-18 14:03:57 -07:00
Kade-gtihubandHundrec 628c0e8975 other notes 2025-11-18 14:03:57 -07:00
Kade-gtihubandHundrec 1734ec1dbf steps/fakes notekind 2025-11-18 14:00:18 -07:00
Karim AkraandHundrec b10ae8dfa5 Fixed playstate not removing the black bars when loading it 2025-11-18 12:00:11 -07:00
Karim AkraandHundrec dc634fa35c Fixed some substates looking a bit broken after resizing 2025-11-18 12:00:11 -07:00
Cameron Taylor 06246aa3d7 assets submod fix -> master 2025-11-17 17:05:45 -05:00
Karim AkraandKade 7c1eb4f88c block vertically wide resolutions 2025-11-15 10:08:45 -08:00
Karim AkraandKade 38634ef5ca Prevent the game bounds from breaking when resizing the window 2025-11-15 10:08:45 -08:00
EliteMasterEricandKade 5d76d23d33 Fix a script error while hot reloading in the Game Over screen. 2025-11-14 16:40:19 -08:00
EliteMasterEricandKade da422c913f Update chart editor layout to fix a bug 2025-11-14 16:36:55 -08:00
Kade-gtihub ba52b932ee controller crash fix 2025-11-14 16:34:05 -08:00
EricandKade 1b90b7bebf Include MacOS Actuator crash in HaxeUI update 2025-11-14 13:38:48 -08:00
Kade-gtihub 23ca9c14c1 Fix crash on non-existant tooltip 2025-11-14 13:38:48 -08:00
AbnormalPoofandAbnormal ab300be709 Fix a compilation error with UNLOCK_EVERYTHING disabled 2025-11-14 12:29:06 -06:00
Kade-gtihub e6aaca19f6 stepmania importing
fix stops
2025-11-07 21:59:07 -08:00
Cameron Taylor 0fe226132d quick fix for incorrect flush() usage 2025-11-04 15:24:28 -05:00
Cameron Taylor 452e3a58d8 modify chart editor to save some preferences more immediately 2025-11-04 14:59:24 -05:00
Cameron Taylor e8f60a23ef helpers for data binding in progress in Save.hx
move buncho save data stuff to our new SaveProperty class

SaveMacro to generate save property stuff
2025-11-04 14:59:24 -05:00
Cameron Taylor 958a6f49ce some data binding funzies i think 2025-11-04 14:59:24 -05:00
Cameron Taylor 2005628358 move these vars up with the rest of em 2025-11-04 14:59:24 -05:00
Cameron Taylor 439901b42e buncho lil null tidying 2025-11-04 14:59:24 -05:00
Cameron Taylor 7bbe356009 move a buncho stuff from Save.hx to SaveSystem.hx
to rebase
2025-11-04 14:59:24 -05:00
Cameron Taylor 9d710b0490 some more Save.hx cleanup worky 2025-11-04 14:59:24 -05:00
Cameron Taylor d140caf4ba move Save.hx consts to Constants.hx file 2025-11-04 14:59:24 -05:00
MAJigsaw77andCameron Taylor 15b691ad7b Make the haxelib warnings be properly handled on Git Dev libraries.
Adjust the haxelib warning message aligment.

Acually, it aint neccesary.
2025-11-03 18:03:50 -05:00
AbnormalandGitHub 02b53e4900 Fix a security vulnerability in ReflectUtil 2025-11-03 13:03:54 -08:00
AbnormalPoofandHundrec 5b5d76aa59 Make CLASSIC the default easing type for Focus Camera 2025-10-28 22:52:40 -04:00
AbnormalPoofandHundrec ae861214cb Revert "Fix type mismatch in smooth lerp functions"
This reverts commit 4f1bbd3f9b.
2025-10-27 18:59:13 -04:00
AbnormalPoofandAbnormal 4d5558f3d8 Add the new characters to the Results Screen song name font... thingyy.... 2025-10-27 17:54:33 -05:00
MoonDroidandAbnormal 0af6f61fb9 Update flixel ref.
* fix: Fix glitchy bitmapfont text for Results, etc. when using missing letters.
2025-10-27 17:54:33 -05:00
EricandGitHub 72a2e9c895 Merge pull request #1914 from FunkinCrew/fix/smoothlerp-mismatch
Fix type mismatch in smooth lerp functions
2025-10-27 15:43:35 -04:00
MoonDroid 4f1bbd3f9b Fix type mismatch in smooth lerp functions 2025-10-24 16:58:51 +07:00
Hundrec b99089dd88 Assets for ansi 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec 380eb5cbaf Add remaining ansi flair woo to every class that needs it 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec a398eb17c4 Add missing Ansi flair to macros 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec 9765f5efe5 Use ansi for polymod logs 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec d6e1f0c90d Remove dupe ERROR tag from getGitCommit() 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec f56fa1955e Make AnsiUtil.apply() remove duplicate RESET codes from provided string 2025-10-23 17:29:55 -04:00
MAJigsaw77andHundrec 277abfab2b Fix html5 compile aswell as some other ANSI not showing up. 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec 5c1b0ae184 Make AnsiUtil be more like StringTools instead. 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec ee952c0789 Ansify Newgrounds, Discord, and HXVLC traces 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec eb1d54e750 Use AnsiUtil for AnsiTrace, and highlight traces with headers 2025-10-23 17:29:55 -04:00
MoonDroidandHundrec 0d8fd214a1 Add BG_ORANGE 2025-10-23 17:29:55 -04:00
07b0af53f1 Add tools.AnsiUtil fir specific macros and project.hxp
Co-Authored-By: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-10-23 17:29:55 -04:00
LasercarandAbnormal 7ffa3d5bf2 Fix UI file checks 2025-10-23 10:57:26 -05:00
HundrecandAbnormal 615d52aff2 Remove BF instrumentals for Pico 2025-10-22 17:54:11 -05:00
ImVeryBadandAbnormal 4961d4ee99 Allow level props to have a flipX field in JSON 2025-10-22 17:06:57 -05:00
ImVeryBadandAbnormal 33a020a34b StageProp animation offsets finally working ! 2025-10-22 16:57:34 -05:00
ComedyLostandAbnormal 6ba44d6cb0 one line banger 2025-10-22 16:51:39 -05:00
Nebula S. NovaandAbnormal 277d16895b Fix Healthicon bug when restarting 2025-10-22 16:26:43 -05:00
AbnormalandGitHub dbd19159b6 Merge pull request #1905 from FunkinCrew/merging-because-til-asked-me-to 2025-10-22 16:12:47 -05:00
HundrecandGitHub 9f5c88ffa1 Merging main into develop because Til asked me to 2025-10-22 17:01:52 -04:00
HundrecandKade a528a11b8c Bump assets 2025-10-22 13:14:35 -07:00
FuroandKade a52f4c5e95 Changes that Kade Requested 2025-10-22 12:50:11 -07:00
FuroandKade 0be42bf047 Osu!Mania Importing 2025-10-22 12:50:11 -07:00
AbnormalPoofandMoonDroid ba31f3611a Fix macos compilation due to a mobile-specific change 2025-10-21 06:14:49 +07:00
MoonDroidandHundrec 3119a0cb6e fix: Make it so external music gets paused when tabbing/opening into the game for mobile 2025-10-20 16:53:55 -04:00
LasercarandAbnormal 5326c40347 Stage the backup 2025-10-19 23:39:49 -05:00
Hyper_andAbnormal bbcb19d476 next time you create a Sprite that covers the whole screen pls don't make it receive mouse events 2025-10-19 23:31:44 -05:00
TechnikTilandHundrec e6d6972ab0 Fix htmlText subtitles 2025-10-20 00:14:21 -04:00
CrusherNotDripandAbnormal 4f8da59577 Make the backups folder more organized. 2025-10-19 23:03:14 -05:00
TechnikTilandHundrec 6686812504 Host README.md images on the repository 2025-10-19 19:49:52 -04:00
AbnormalPoofandHundrec 80993df87f Fix assets being cleared when running lime run 2025-10-19 19:34:07 -04:00
AbnormalPoofandHundrec 30361954f3 Fix the assets folder... not deleting assets at all. 2025-10-19 01:08:52 -06:00
ImVeryBadandKade 04965f38c6 Add support for opening folders on Linux 2025-10-17 15:15:05 -07:00
AbnormalPoofandMoonDroid 5d8360333a Update flixel, flixel-addons, and openfl 2025-10-17 15:19:39 +07:00
MAJigsaw77andMoonDroid 2a88adf6b3 Fix FunkinSoundTray. 2025-10-17 15:19:39 +07:00
MAJigsaw77andMoonDroid 38e70cb678 Fix uv refactor. 2025-10-17 15:19:39 +07:00
MAJigsaw77andMoonDroid 878427edb5 Remove the focus override in FunkinSound. 2025-10-17 15:19:39 +07:00
MAJigsaw77andMoonDroid 4dcf83f909 Change to24Bit() to rgb. 2025-10-17 15:19:39 +07:00
MAJigsaw77andMoonDroid c14bf35144 Fix GraphicCursorCross. 2025-10-17 15:19:39 +07:00
MAJigsaw77andMoonDroid 3ae09b6d5a Fix tileMatrix. 2025-10-17 15:19:39 +07:00
Karim AkraandAbnormal b778195dd1 Fixed the hxcpp check sometimes checking an incorrect path 2025-10-15 16:48:19 -05:00
sector-aandAbnormal 5f8bb643b8 whoa....a one-symbol fix for the subtitles 2025-10-15 13:36:41 -05:00
Hundrec af35608953 Bump assets 2025-10-15 07:59:39 -06:00
TilandHundrec 647f158788 Stop the haxe compilation server from clearing assets
dude i was fighting with this for DAYS
2025-10-15 07:59:39 -06:00
AbnormalPoofandHundrec e42b8c7b9f A much cleaner way to check for C++ 2025-10-15 07:59:39 -06:00
d9387f6a09 Automatically compile HXCPP Build Tools
Co-authored-by: ImVeryBad <80699736+Starexify@users.noreply.github.com>
2025-10-15 07:59:39 -06:00
MAJigsaw77andHundrec 1804cdb183 Fix the video not restarting properly on html5. 2025-10-14 10:18:23 -06:00
TechnikTilandAbnormal 2ad593341f Fix VideoCutscene checking by loading the videos OpenFL library 2025-10-14 08:33:29 -05:00
Karim AkraandHundrec 1d18a14356 Fixed compiling for html5 targets by excluding the FNFC loading stuff 2025-10-14 06:54:08 -06:00
f1a1db0834 fix normal gameover music not playing after starting music
Co-authored-by: Kolo <67389779+KoloInDaCrib@users.noreply.github.com>
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-10-14 19:28:01 +07:00
1ebce748d4 Hide back button when selecting week
Co-authored-by: VirtuGuy <Wyatt2010shaw@gmail.com>
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-10-14 19:13:53 +07:00
sector-aandHundrec 836129ec8a Add Back Button in the Character Select Menu 2025-10-14 06:13:25 -06:00
MoonDroid 97a5e2ba14 Update preferences' description for debug display 2025-10-14 19:09:08 +07:00
f75f83f653 Use Inverted Prefix
Co-authored-by: Til <techniktil@tilnotdrip.org>
2025-10-14 19:09:08 +07:00
FuroandMoonDroid 7a16a7465a Debug Display on Mobile Debug Builds 2025-10-14 19:09:08 +07:00
gamerbrossandHundrec 62a3f73de9 Better strumline note confirm animations 2025-10-14 05:43:13 -06:00
MAJigsaw77andHundrec 113fa19bed Fix MacOS latest sdk compile. 2025-10-14 04:50:41 -06:00
7b8d6577ed Fix Downscroll vwoosh
Co-authored-by: Furo <furogaming9@gmail.com>
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-10-14 17:30:53 +07:00
sector-aandHundrec 36f9701b5b Don't play Character Select slide animation when entering 2025-10-13 21:20:41 -06:00
sector-aandHundrec ec4ebf5a15 Prevent char select from opening before transition ends 2025-10-13 21:16:43 -06:00
KoloandHundrec c1988f1858 make hotreloading during playtest only affect playtest 2025-10-13 20:48:07 -06:00
AbnormalPoofandHundrec be85dd06e5 Simplify camera retrieval for Stress (Pico Mix) camera 2025-10-13 20:41:50 -06:00
Sphis-SincoandHundrec 4366fbe512 Fix a crash when HARDCODED_CREDITS is disabled. 2025-10-13 20:15:39 -06:00
Hundrec 987b3846ef Bump json2object 2025-10-13 19:26:15 -06:00
ImVeryBadandHundrec f40c74b153 Exclude all *.iml files instead of just Funkin and unit 2025-10-13 18:54:04 -06:00
ImVeryBadandHundrec 23ca7357d4 Exclude IntelliJ IDEA stuff from git 2025-10-13 18:54:04 -06:00
ImVeryBadandHundrec 95af46cbf7 Double pressing not allowed, one press at a time 2025-10-13 18:52:47 -06:00
sector-aandHundrec 56af0d6a11 Clear assets for Desktop Platform Type only 2025-10-13 18:47:08 -06:00
sector-aandHundrec c39046d999 Improve the way positioning is done 2025-10-13 11:15:22 -06:00
sector-aandHundrec 476336be68 Tweak subtitles position for downscroll 2025-10-13 11:15:22 -06:00
sector-aandHundrec e0bd5b8038 Add Subtitles to the Chart Editor! 2025-10-13 11:15:22 -06:00
sector-aandHundrec 0691207413 Unneccessary stuff 2025-10-13 11:15:22 -06:00
Hundrec 16ea626c28 Adjust descriptions for all preferences
For clarity and readability
2025-10-13 11:15:22 -06:00
sector-aandHundrec 81788550bf Fix multiline subtitles a bit 2025-10-13 11:15:22 -06:00
sector-aandHundrec ed2bd14a71 Make overlapped timeline lines actually work lol 2025-10-13 11:15:22 -06:00
sector-aandHundrec 7b66ff2304 Make camSubtitles be over all the cameras except camPause 2025-10-13 11:15:22 -06:00
sector-aandHundrec 54f2e75139 remove video subtitles preference 2025-10-13 11:15:22 -06:00
sector-aandHundrec 279b0755ea Subtitles preference 2025-10-13 11:15:22 -06:00
sector-aandHundrec b44deb393d Add a separated camera for subtitles 2025-10-13 11:15:22 -06:00
sector-aandHundrec 714af8ebe0 Prevent the subtitles text from being set each frame 2025-10-13 11:15:22 -06:00
sector-aandHundrec b4a1730e37 SRT Implementation for in-game subtitles! 2025-10-13 11:15:22 -06:00
sector-aandHundrec a85b3be037 Properly working html text elements and some documentation 2025-10-13 11:15:22 -06:00
sector-aandHundrec 90b8946429 Basic in-game subtitles implementation 2025-10-13 11:15:22 -06:00
sector-aandHundrec 09192474d7 Add haptics when you tap to begin in Title Screen 2025-10-12 06:33:38 -06:00
sector-aandHundrec 6061d5f438 Don't open the Random capsule's inst menu on mobile 2025-10-12 02:16:46 -06:00
sector-aandAbnormal 7aa9a0c9a8 assets ref 2025-10-11 00:24:47 -05:00
EliteMasterEricandHundrec b1a37a7674 Haxelib updates to json2object and Polymod 2025-10-10 01:08:25 +08:00
HundrecandAbnormal ee413f06dd Bump FlxPartialSound to fix that one deprecation warning
Hey, did a cat just walk across your keyboard?
2025-10-09 08:59:20 -05:00
Mihai AlexandruandHundrec fbbc0b2dcc Adjust logic. 2025-10-09 21:17:49 +08:00
5c07c41e62 Dont add haxeui if the platform doesnt use it.
Co-Authored-By: sector-a <82838084+sector-a@users.noreply.github.com>
2025-10-09 21:17:49 +08:00
MoonDroidandHundrec 9dd4791bb8 Adjust how auto-pause works on mobile
Made it so it doesnt freeze the game lol!
2025-10-09 21:13:12 +08:00
Karim AkraandHundrec 9a842207ac Update lime ref 2025-10-09 18:29:16 +08:00
MAJigsaw77andHundrec e139ae252f Properly format and fix a mem leak. 2025-10-09 18:29:16 +08:00
Karim AkraandHundrec fb8fd8b7d4 Fixed some issues with iOS 2025-10-09 18:29:16 +08:00
Karim AkraandHundrec 6bab076329 Final bugfix before testflight 2025-10-09 18:29:16 +08:00
sector-aandHundrec ec65cb3d27 Add a better playtest menu
fix spelling mistake

Bring back neccessary stuff

Fix input a bit

Bring back neccessary stuff AGAIN
2025-10-09 18:29:16 +08:00
Karim AkraandHundrec 96720e235f Reduce Reflect overhead in CallbackUtil 2025-10-09 18:29:16 +08:00
Karim AkraandHundrec c2b68dcd90 Android cleanup & some finalization 2025-10-09 18:29:16 +08:00
Karim AkraandHundrec 7a09f0c02c Huge chunk of the iOS implementation 2025-10-09 18:29:16 +08:00
Karim AkraandHundrec 1b0a0a476d Base Android implementation 2025-10-09 18:29:16 +08:00
HucksandHundrec 28883fb9ed nglogo behind title text
that pesky ng logo isn't in front of the title text anymore
2025-10-09 06:11:21 +08:00
Karim AkraandHundrec 1b38dab9aa Change the title of the game on mobile to "FNF" 2025-10-09 06:10:33 +08:00
EricandGitHub ab99cc6f74 Merge pull request #1836 from FunkinCrew/PurSnake/in-game-subtitles
[PUBLIC PR] SRTUtil in-game subtitles
2025-10-08 17:49:13 -04:00
PurSnakeandEric 8ba14b0e7b {} -> <> convert 2025-10-08 17:42:18 -04:00
PurSnakeandEric a7fc70a5c9 srt 2025-10-08 17:42:18 -04:00
PurSnakeandHundrec 204dae213c Move migration porcces into ChartEditor itself, when we load events data 2025-10-09 04:41:34 +08:00
PurSnakeandHundrec fdaab2b229 Reduce width for ENUM fields in events 2025-10-09 04:41:34 +08:00
PurSnakeandHundrec e67643e0b1 1 hour, 39 minutes: Convertation method for old charts with old events. No, im not gonna kill myself
I think it should be removed in one of 0.8.x patches or in 0.9
Also some verbose code as this commit is a bit big
2025-10-09 04:41:34 +08:00
PurSnakeandHundrec d1d77dd85c Event easing direction + backwards compatibility 2025-10-09 04:41:34 +08:00
kade-githubandHundrec 766bdbcd62 set the alpha to 0 as well 2025-10-09 04:10:18 +08:00
kade-githubandHundrec 7245553588 Fix notes appearing in Calibration 2025-10-09 04:10:18 +08:00
MAJigsaw77andAbnormal fddf0e92cb Fix hang caused by git status. 2025-10-08 04:24:24 -05:00
AbnormalPoofandHundrec 83c2ea2881 Also make vid nullable on html5 2025-10-08 17:18:46 +08:00
Hyper_andAbnormal c170c25693 always bet on... TWO LINE BANGERS?! 2025-10-08 01:29:09 -05:00
HundrecandAbnormal 03f6f90636 Another batch of charting fixes 2025-10-07 20:17:11 -05:00
EliteMasterEricandAbnormal bcc0a44979 Improved printing of Capsule events 2025-10-07 20:08:51 -05:00
EliteMasterEricandAbnormal 6c20b70a14 Fixes to the event dispatcher to make it more obvious if one isn't being handled properly. 2025-10-07 20:08:51 -05:00
EricandAbnormal c6bdd753df You have to make the event cancelable first. 2025-10-07 20:08:51 -05:00
AbnormalPoofandAbnormal 99ad396fa3 Take into account the confirm sound's length when fading out in the Game Over screen 2025-10-07 20:08:51 -05:00
AbnormalPoofandAbnormal cdb3acc691 Add multiple new callbacks for Freeplay and Character Select 2025-10-07 20:08:51 -05:00
KoloandHundrec 71b4a58cab make going into chart editor thru a song go to the current song pos 2025-10-07 19:12:55 +08:00
ImVeryBadandAbnormal 934bce7bdc Round NumberPreferenceItem value to prevent near-zero precision errors 2025-10-07 04:26:55 -05:00
gamerbrossandAbnormal dd5ea7748b Fix NoteStyle.getCountdownSoundPath library separator code 2025-10-07 04:08:42 -05:00
AbnormalandGitHub 4fef6dc3b6 Merge pull request #1798 from FunkinCrew/pr-5118/anysad/funkin-ui-null-safety 2025-10-07 03:49:36 -05:00
Herman KhomichandHundrec cb669335a3 oopsy (#7) 2025-10-07 05:09:21 +08:00
KoloandHundrec 68970b6716 make stickers not persist 2025-10-07 05:09:21 +08:00
EliteMasterEricandHundrec d80682333a Use cleaner array utilities rather than for loops. 2025-10-07 05:06:45 +08:00
LasercarandHundrec a8dec0cd70 Get last modified valid editor backup 2025-10-07 05:06:45 +08:00
sector-aandHundrec 6d0279fc37 Fix a line being too high 2025-10-07 04:58:23 +08:00
sector-aandHundrec f5fb39538c Assets and Art submods 2025-10-07 04:58:23 +08:00
sector-a 1b693b0752 Update assets submod 2025-10-06 22:45:16 +03:00
anysadandsector-a 4ca0e0deb5 fix shit after rebase 2025-10-06 21:21:09 +03:00
anysadandsector-a 26dbf918cc gosh, charselect is crazy 2025-10-06 21:21:09 +03:00
sector-aandHundrec 82adf357d2 Update function's documentation 2025-10-06 02:57:41 +08:00
sector-aandHundrec 8022643b8e Prevent death on time travelling 2025-10-06 02:57:41 +08:00
MAJigsaw77andHundrec 3a44168d8c Unused import 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec 2818539686 Add this back 2025-10-06 01:31:30 +08:00
EliteMasterEricandHundrec 6ad0502cce Update videos to actually include subtitles. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec dccafc5e27 Fix compile. 2025-10-06 01:31:30 +08:00
EliteMasterEricandHundrec 1653839753 Fix the feature flag condition for video subtitles. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec 7e09c25f5b If videoSubtitles are disabled specifically tell libvlc to not load any. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec f7fef85353 Dont load the subtitles at runtime. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec 83667e6232 Remove the options. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec fa86e53b7d Restart the video when setting the subtitle track. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec e43ec564e0 Add back FunkinVideoSprite. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec a25233ccdc Add the ability to use video files other than mp4. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec a3a75b89b9 Only add the option if its defined. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec 8733afe512 Added the option and the define for subtitles. 2025-10-06 01:31:30 +08:00
MAJigsaw77andHundrec 6059f21f2d [WIP] Basic subtitles support. 2025-10-06 01:31:30 +08:00
Karim AkraandHundrec 02d9a507b4 Generate every logfile with a specific timestamp 2025-10-06 00:54:35 +08:00
sector-aandMoonDroid d0a9a59de7 Add a separated camera for the Pause Menu 2025-10-05 21:23:33 +07:00
Karim AkraandKade 6dc5ac826e Fixed the log file causing the game to crash when opening multiple windows at the same time 2025-10-04 19:05:27 -07:00
AbnormalPoofandAbnormal 807b9ab09f clear out assets folder from export/ when compiling 2025-10-02 14:31:37 -05:00
MoonDroidandHundrec 45fc68f08a Update lime ref 2025-10-02 12:41:25 +08:00
Hundrec 18f2334ddd Bump funkVis and grig.audio 2025-10-02 03:20:12 +08:00
FuroandHundrec 6607645786 Fixed Memory Leaks on funkVis and grig.audio
The memory leak was due to not caching the audio levels and these libraries allocating arrays on every frame, it has been fixed in their each repository through a pull request.
2025-10-02 03:20:12 +08:00
KoloandAbnormal 9497378600 it's another what?? ONE LINER 🗣🗣
Fixes the bug where going into a song through the story menu then exiting forcefully via the F4 keybind (which moves you to the main menu but doesn't clear out the PlayStatePlaylist) and then going into the Chart Editor to playtest a song moving you further along the week once the song reaches the end. What a mouthful.
2025-10-01 13:25:49 -05:00
PurSnakeandMoonDroid 4215b1c72e Render Transition Stickers on OpenFL Sprite 2025-10-01 20:55:17 +07:00
4300aff991 Bump polymod with the latest commits
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-10-01 19:31:26 +07:00
AwesomezPro99andMoonDroid ca88b00d95 Fixed Random! song fade 2025-10-01 18:36:54 +07:00
Cameron TaylorandKade e3a5f329ad some spring cleaning on AtlasText.hx
these shouldn't affect functionality at all, just some things to make haxe checkstyle a bit less annoying and other lil cleanings

Merge branch 'master' into atlas-text-tidy

more AtlasText cleaning
2025-09-30 14:22:28 -07:00
Karim AkraandHundrec 42916a8b6a bump admob to fix a crash from ads preloading 2025-10-01 05:17:02 +08:00
MAJigsaw77andHundrec 12ed0107ab Oops 2025-10-01 05:17:02 +08:00
MAJigsaw77andHundrec 6016ee966e Load interstitial ADS more efficiently on Android. 2025-10-01 05:17:02 +08:00
Noobz4LifeandHundrec 555ec09ef5 Feral gamemode support 2025-10-01 05:07:49 +08:00
KoloandHundrec b30ea36fcc the evil and intimidating haxeflixel icon 2025-10-01 05:00:09 +08:00
Hyper_andHundrec 4c0d92507a Disable Haxe UI autolocalization
This is an English-only game (for now)
2025-10-01 04:59:16 +08:00
KoloandHundrec 8421e4c150 super minor fix for levels i noticed randomly lol 2025-10-01 04:58:14 +08:00
AwesomezPro99andHundrec be3a6dee93 Fix onion skin global offsets and crash 2025-10-01 04:49:59 +08:00
AwesomezPro99andHundrec a76a868cb6 Fix anim editor onion skin scaling 2025-10-01 04:49:59 +08:00
Hundrec 707d519110 Bump assets 2025-10-01 00:10:42 +08:00
Hundrec a93e8a9f10 Add useCache argument to getBitmapData 2025-10-01 00:10:42 +08:00
Hundrec 65da3049ba Don't cache DropShadowShader
False...
2025-10-01 00:10:42 +08:00
AbnormalandMoonDroid 4a00429a57 feat: Enable time travel for playtesting 2025-09-30 18:46:45 +07:00
da6303a0b0 Fix DropShadowShader not searching for astc textures when setting the mask
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-30 18:26:06 +07:00
AwesomezPro99andHundrec 96f8ee85e0 Fix char select static pixel icon crash 2025-09-30 14:12:01 +08:00
PurSnakeandHundrec 9f9a6924a7 Fix frame-snap and pause/unpause 2025-09-29 22:00:41 +08:00
PurSnakeandHundrec 8e6fa51c27 un-fps your icons *evil ass laugth* 2025-09-29 22:00:41 +08:00
AwesomezPro99andHundrec 047187257b Hide cursor when playtesting chart 2025-09-29 18:26:22 +08:00
VioletSnowLeopardandHundrec 2f77ccfc6e Save preferences when exiting the Chart Editor 2025-09-29 18:14:47 +08:00
ACrazyTownandHundrec d19245c23d Improve message depending on the target platform 2025-09-29 17:58:21 +08:00
ACrazyTownandHundrec 52703536a6 Provide a nicer error message when software rendering is used 2025-09-29 17:58:21 +08:00
Hundrec c5dc625550 Bump those assets 2025-09-29 17:42:33 +08:00
Hyper_andHundrec 6405fe6bb3 ...danrell 2025-09-29 17:42:33 +08:00
Hyper_andHundrec c7367ae33d fix: Also take '[' bracket into account when sanitizing JSON 2025-09-29 17:42:33 +08:00
LasercarandHundrec 1df9632e31 Better time change placement and ms label after TS 2025-09-29 16:36:17 +08:00
Hundrec 94a1ec3902 Batch of charting fixes
- Added the new No Animation notekind when certain animations play
- Adjusted precise timings of some playAnimation events to match the audio
- Fixed some issues reported on GitHub
- Implemented some suggestions reported on GitHub

Check the 0.7.6 Milestone to see which ones were fixed!
2025-09-29 16:30:01 +08:00
LasercarandHundrec c05c0160c3 Render the measuring stick before loading a chart 2025-09-28 15:20:10 +08:00
HundrecandAbnormal 6193aa2faa Nuh uh gimme back that False...
Fixes input offsets being applied in the Chart Editor after a refactor
2025-09-27 14:15:02 -05:00
LasercarandAbnormal ca79c71851 Fix ghost notes having a zindex below measure ticks 2025-09-27 13:19:21 -05:00
MAJigsaw77andHundrec d83149dc70 FIx html5 compile. 2025-09-28 01:38:50 +08:00
LasercarandHundrec 2c4167bc11 Shorten playbar text when too l... 2025-09-28 00:27:51 +08:00
mintandHundrec 117f3be390 fix(ci): move env vars to .env 2025-09-28 00:25:35 +08:00
mintandHundrec b81035f9bf fix(ci): add newground creds to env 2025-09-28 00:25:35 +08:00
8b38bcfbdd Freeplay DJ render types and softcoded TV anim
Added support for renderTypes such as: Sparrow, Multisparrow and Packer.

Added support for Scriptable DJ's

BF's TV logic softcoded now (wow)

Added pathSafety parameter on creating new FlxAtlasSprite.

Co-Authored-By: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
2025-09-27 20:03:43 +08:00
sector-aandHundrec f47de1b71c Add an ability to set Debug Display using a hotkey 2025-09-27 19:45:16 +08:00
kade-githubandHundrec 61f2ea3511 fix FUNK-3429 2025-09-27 18:55:39 +08:00
mintandHundrec 9169beaa12 fix(ci): gather-changes errors if no commits in payload 2025-09-27 18:52:56 +08:00
mintandHundrec 9f0041b196 ci: update to github-script@v8 2025-09-27 18:52:56 +08:00
KeoikiandHundrec 6cc35815ba Fix Song ID field accidentally being removed. 2025-09-27 03:57:37 +08:00
KeoikiandHundrec c88dfb4947 Bump to latest assets 2025-09-27 03:57:37 +08:00
KeoikiandHundrec a27149aa70 Fix a few issues from conflict merging. 2025-09-27 03:57:37 +08:00
KeoikiandHundrec 8e9dea898f Fix "Live Input" note positioning being wack
The Live Input positioning now uses the same snapping math that the cursor placement does. (Why wasn't this the case before??)
2025-09-27 03:57:37 +08:00
KeoikiandHundrec 73d3c27c25 Fix 2 issues + add numerators 17 to 32
- Fixed Skip Forward and Backwards buttons, they now properly set the position to the start of a measure.
- Fixed having the playhead (the red thing) at any position than the default would cause the song time to be wrong when passing through denominator changes.
2025-09-27 03:57:37 +08:00
KeoikiandHundrec b9eeebdbea Fix metronome measure beeps. 2025-09-27 03:57:37 +08:00
KeoikiandHundrec 1f33aec825 FIX MEASURE TICKS GRAAAAAAAAAAAHH!!!!!! 2025-09-27 03:57:37 +08:00
LasercarandHundrec 2d891a4248 Refresh time change inputs 2025-09-27 03:57:37 +08:00
LasercarandHundrec e533c87d08 Final fix (I hope) 2025-09-27 03:57:37 +08:00
LasercarandHundrec c5f5fdb089 Whoops 2.0 2025-09-27 03:57:37 +08:00
LasercarandHundrec 87fdb84202 Whoops. 2025-09-27 03:57:37 +08:00
LasercarandHundrec d78ead2927 Refactor time change commands, add @AbnormalPoof suggestion 2025-09-27 03:57:37 +08:00
LasercarandHundrec a4148102eb Fix crash, dropdown text not updating and timestamp.step is step
Timestamp step steps in step now

Nearly forgot to include the assets in this.
2025-09-27 03:57:37 +08:00
LasercarandHundrec 69affd43bb Forgot to remove these traces 2025-09-27 03:57:37 +08:00
LasercarandHundrec 1dce731aa4 Modify time changes in the metadata toolbox 2025-09-27 03:57:37 +08:00
KeoikiandHundrec f3c0541ade Fix note preview lines being in incorrect places.
Fun fact: This already affected "Monster" in current release versions and wasn't something caused by me.
2025-09-27 03:57:37 +08:00
KeoikiandHundrec eb40d3bda5 Fix measure number values and positions
(and remove 2 comments I forgot to remove last time)
2025-09-27 03:57:37 +08:00
KeoikiandHundrec 533c8f7ede Remove unused variables, I forgor 💀 2025-09-27 03:57:37 +08:00
KeoikiandHundrec b29339cd1a Several things. (Check description)
- Fix "beat time" when dealing with time signatures.
- Remove old, now unused stuff from previous commit in the Conductor.
- Overhaul getTimeInSteps() and getStepTimeInMs() to be a cumulative calculation.
2025-09-27 03:57:37 +08:00
KeoikiandHundrec 814bc4a3bf just to get this pushed i guess 2025-09-27 03:57:37 +08:00
KeoikiandHundrec b05a5c7f54 Changes from the old branch. 2025-09-27 03:57:37 +08:00
EliteMasterEricandHundrec eba71982c6 Also validate ADMOD_APPID in the project file. 2025-09-26 13:47:59 +08:00
EliteMasterEricandHundrec 4a6c81b136 Require the .env file include ad keys for release builds.
REMEMBER: You can build with `-debug` or with `-DNO_FEATURE_MOBILE_ADVERTISEMENTS` to skip this check.
2025-09-26 13:47:59 +08:00
EliteMasterEricandHundrec 4f671f1e5b Add some utility functions for working with constants from macros. 2025-09-26 13:47:59 +08:00
Karim AkraandHundrec 01a076b6ed Fix incorrect vars names on admob null checks 2025-09-26 04:25:23 +08:00
Karim AkraandHundrec f0f3e65bb0 fixed an oopsie in RegistryMacro 2025-09-26 03:44:22 +08:00
Karim AkraandHundrec 2398fabffb Fixed the build cache failing for iOS 2025-09-26 03:44:22 +08:00
EliteMasterEricandAbnormal a03c93b006 The chart editor now uses the vocal data from the metadata rather than hardcoding to the character IDs. NOTE: This is correct but it breaks voices on some charts, we need some way of allowing users to easily correct messed up vocals. 2025-09-25 06:01:30 -05:00
EliteMasterEricandAbnormal 5c78c5ba2c Errors display a popup rather than crashing now. 2025-09-25 06:01:30 -05:00
Karim AkraandAbnormal c8d0ae6a9d Reset camera zoom as well 2025-09-25 06:01:30 -05:00
EliteMasterEricandAbnormal 3867e4bdca This should keep the text visible after playtesting. 2025-09-25 06:01:30 -05:00
EliteMasterEricandAbnormal bdad46e96d Fix a bug where variations weren't accounted for with chart variations. 2025-09-25 06:01:30 -05:00
EliteMasterEricandAbnormal d03199ccda Implement a debug menu to select difficulty/variation 2025-09-25 06:01:30 -05:00
EliteMasterEricandAbnormal 0c4f22173e Created a function to playtest a song directly from its FNFC file (TODO: difficulty select) 2025-09-25 06:01:30 -05:00
Cameron TaylorandAbnormal 17bafc2b5e cleanup on DebugDisplayMode enum abstract so we don't need to cast/convert to string all the time 2025-09-25 04:19:19 -05:00
EliteMasterEricandHundrec 666aaf4428 Restructred note kinds a bit to add hard-coded note kinds, then implemented the "noanim" note kind into the chart editor. 2025-09-25 16:45:50 +08:00
EviethecoderandHundrec 618e093e1c character sing overides and no anim notes 2025-09-25 16:45:50 +08:00
AwesomezPro99andAbnormal 2f865a5c7f Fix script create event 2025-09-24 23:24:02 -05:00
JVN-PixelsandAbnormal aaf5084fd9 Freeplay Previews - Fading Out 2025-09-24 17:00:34 -05:00
KoloandHundrec 44c14ec4ce fortunately the fix for hundrecs issue was just moving the line earlier 2025-09-25 01:28:47 +08:00
KoloandHundrec 66882d9677 kill off the cover brah 2025-09-25 01:28:47 +08:00
KoloandHundrec bc1d36ddea one liner number three billion 2025-09-24 22:39:36 +08:00
JVN-PixelsandHundrec 2d5ef19b3b Chart Editor - Remaining Time Milliseconds 2025-09-24 21:32:56 +08:00
AwesomezPro99andHundrec a61016d2df Fix Animation Editor crash 2025-09-24 21:03:00 +08:00
AwesomezPro99andHundrec f2f6b4c859 Fix cursor visibility when exiting Anim Editor 2025-09-24 20:54:17 +08:00
EricandGitHub 9f92d64641 Merge pull request #1761 from FunkinCrew/Hyper/debug-logging-crash
[PUBLIC PR] Prevent crash when closing certain states on debug builds
2025-09-23 20:30:10 -04:00
EricandGitHub bb71dcba91 Merge pull request #1759 from FunkinCrew/VioletSnowLeopard/stop-test-on-exit
[PUBLIC PR] Fix Offset Calibration/Test starting after exiting the Lag Adjustment menu
2025-09-23 20:29:50 -04:00
VioletSnowLeopardandEric 23a38f76f9 Don't start calibration/test if no longer in offset menu 2025-09-23 20:29:31 -04:00
Hyper_andEric 9a8bda8b11 this should be running last... 2025-09-23 19:50:40 -04:00
EricandGitHub 3c10cf5181 Merge pull request #1757 from FunkinCrew/hmm/update-haxeui-core
Update `haxeui-core`.
2025-09-23 19:50:22 -04:00
EricandGitHub c5095e0a8d Merge branch 'master' into hmm/update-haxeui-core 2025-09-23 19:49:07 -04:00
HeroandHundrec 3486fe3dde refactor: cleaner and faster time formatter 2025-09-24 07:44:46 +08:00
MAJigsaw77andEric 56fe047e3a Update haxeui-core. 2025-09-23 19:38:35 -04:00
AwesomezPro99andHundrec 26b253d8ca Fix save data options back 2025-09-24 01:16:21 +08:00
AbnormalandHundrec 3f85f1dd70 Bump hscript to fix null coalescing assignment (??=) 2025-09-20 16:31:19 +08:00
AbnormalandHundrec dad829dd91 bump hxcpp to fix a build error with macos 26 2025-09-19 03:20:52 +08:00
HundrecandGitHub e62c5a223c Merge to sync commit histories
Build and Upload nightly game builds / gather-changes (push) Canceled after 0s
Build and Upload nightly game builds / gather-tags (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (macos, macos) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (windows, windows) (push) Canceled after 0s
Build and Upload nightly game builds / docker-image (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (html5) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (linux) (push) Canceled after 0s
2025-09-19 01:33:29 +08:00
Hundrec 5fc7491121 Merge changes from main 2025-09-19 01:28:01 +08:00
Hundrec 9598455118 KoloInDaKrib 2025-09-19 01:27:42 +08:00
Nightfall-16andHundrec 0e4b9753a0 contribute I guess 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec 2bbe3d788d crash 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec cd731d7e40 work 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec e11e3d51c2 please work 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec b19b78df5d please work now 2025-09-19 01:26:12 +08:00
Hundrec 1a061cbbcc 0.7.5 changelog and version bump
Set the date for the 0.7.5 patch
2025-09-18 16:24:18 +08:00
MoonDroid 9908c8be32 Fix html5 builds from crashing and not compiling 2025-09-18 14:55:52 +07:00
MoonDroid 8522deab64 Adjust warning message 2025-09-18 14:55:52 +07:00
MoonDroid 7e93521666 Add custom context warnings for environment errors
woo!! im proud of this !!
2025-09-18 14:55:52 +07:00
MoonDroidandMihai Alexandru 5aeb3a28fb Add AnsiUtil for ease of ansi use
Co-Authored-By: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-09-18 14:55:52 +07:00
MAJigsaw77andMoonDroid b5db93726a Rewrite Environment macro. 2025-09-18 14:55:52 +07:00
EliteMasterEricandHundrec 20de8db41b Update to the latest commit on the hxcpp-debugger fork. 2025-09-18 14:43:08 +08:00
Hundrec 5d73d65fdc Fix Eggnog Erect cutscene crash 2025-09-18 03:03:31 +08:00
8c750d368d Update openfl and flixel for ASTC fixes.
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-18 01:44:25 +07:00
Hundrec 8d328ace84 Fix HTML5 compile error 2025-09-18 01:03:00 +08:00
MAJigsaw77andHundrec 9d825f00aa Use Sys.println instead of trace` for Admob events. 2025-09-16 22:56:11 +08:00
Honton129andAbnormal c7b68507a8 Typo Fix on PlayStatePlaylist
that was bothering me
2025-09-15 05:08:37 -05:00
Hyper_andHundrec 970ccf2f4a fix: Chart loading fixes
* Prevents charter from deleting `default` variation when there's more than one variation available.
* Fixes song variation list not being saved into `default` metadata file.
* Fixes chart data not being assigned its respective variation when loading from FNFC.
2025-09-15 03:12:43 +08:00
PixelandHundrec 479036a400 Add ability to pause during Credits scroll 2025-09-15 03:09:33 +08:00
KoloandHundrec 80d7ace714 remove the stupid chud gibberish 2025-09-15 03:06:44 +08:00
Hundrec a7d5736e14 Adjust Stage Editor exit confirm prompt 2025-09-15 03:02:15 +08:00
LasercarandHundrec b31be1043c chart name in backup filename 2025-09-15 03:02:14 +08:00
Hundrec 6c4d27a842 Nudge Chart Editor menubar for new debug display 2025-09-15 02:55:39 +08:00
Hundrec fdab26ce56 Fix Pico not shooting in Stress upon restarting 2025-09-15 02:26:22 +08:00
VioletSnowLeopardandHundrec 8ead5eeb08 change save shortcuts to CTRL+S and CTRL+SHIFT+S 2025-09-15 02:18:53 +08:00
Hundrec c4ebd3175d Remove beatHit() flash from Freeplay backing card
It's a cool idea, but too distracting, especially for Pico's blue backing card
2025-09-15 02:13:45 +08:00
e593bc8a61 Fix Debug Display memory leak on Linux
Co-authored-by: Linus Torvalds <torvalds@linux-foundation.org>
2025-09-15 02:03:55 +08:00
Hyper_andHundrec cb554bd5f7 the one line fix 2025-09-15 01:59:37 +08:00
Hyper_andHundrec dd54acfbd1 this pointless condition was pissing me off 2025-09-15 01:59:37 +08:00
LasercarandHundrec 6783f04139 No Chudding allowed 2025-09-15 01:56:03 +08:00
PurSnakeandHundrec 11eb4060d0 duh 2025-09-15 01:56:03 +08:00
VioletSnowLeopardandHundrec b5357acf98 Fallback to default if metadata voices don't exist 2025-09-15 01:50:09 +08:00
Hyper_andHundrec f85f2f03d9 another one liner god damn it
Fixes a crash when there's some previously focused text fields,
and the user tries to exit via the confirmation dialog.
Before a state switch HaxeUI tries to reset the tooltip manager and
remove all components, which in this case causes it to refocus a text field,
which tries to access a destroyed camera to retrieve the bounds.
Doing this ensures there's no text fields for it to do that.
2025-09-15 01:41:38 +08:00
VioletSnowLeopardandHundrec 146a6cde68 Fix typo - pause *button* 2025-09-14 18:23:41 +08:00
AbnormalandHundrec 65105df9ac disable smoothing on the baked bitmaps 2025-09-14 17:31:01 +08:00
AbnormalandHundrec 9106ef215a add the lighting to pico's rank animation (had to bake the blendmode lol) 2025-09-14 17:31:01 +08:00
Megalives6193andHundrec 38426bc5d7 [GITHUB] Fix Logo In README.md 2025-09-14 04:35:22 +08:00
KoloandHundrec 79e8b5d71e fix: Prevent crashing by replacing the state constructor if needed 2025-09-14 03:14:38 +08:00
MoonDroidandHundrec 6e954cfac0 Update astc compression rate for results excellent bf 2025-09-13 21:56:48 +08:00
MAJigsaw77andAbnormal 041d33b2d3 Fixes to the audio config stuff. 2025-09-12 16:26:15 -05:00
MoonDroid 1b5b0925ed chore: Rename the define and remove redundant block 2025-09-11 03:39:25 +07:00
Karim AkraandMoonDroid cd0bc85b76 added FEATURE_LOG_TRACE_FILE for logging all the logs into a text file 2025-09-11 03:39:25 +07:00
MAJigsaw77andHundrec d4929164b6 Update hxvlc. 2025-09-10 12:35:35 +08:00
Abnormal 7bc06861b5 add an extra line 2025-09-09 12:39:19 -05:00
Abnormal 3ccbed2cbb clarify dev build in outdated libraries 2025-09-09 12:39:19 -05:00
MAJigsaw77andAbnormal 29c0b0f8eb Add support for dev libs in project.hxp haxelibs check. 2025-09-09 12:39:19 -05:00
AbnormalandEric 7ff0753472 scripted freeplay and popups shot dead on august 32nd, 2031 with no traces found 2025-09-09 05:35:35 -04:00
AbnormalandEric 19328abb9e and then there were 7 2025-09-09 05:35:35 -04:00
AbnormalandEric feb0863358 and then there were 8 2025-09-09 05:35:35 -04:00
AbnormalandEric 43e51e9a4a erm......sorry modders you're gonna have to do the work yourself lol 2025-09-09 05:35:35 -04:00
AbnormalandEric 3b610d8910 add a TON of modding stuff 2025-09-09 05:35:35 -04:00
PurSnakeandHundrec 63ec749c7a Disable Freeplay intro when coming from stickers 2025-09-09 03:53:51 +08:00
Hyper_andHundrec c8e6a78154 don't check libraries on display 2025-09-09 03:45:48 +08:00
Az989YTandHundrec 89b2b7fcc3 change event dispatching to match comment order 2025-09-09 03:14:55 +08:00
kade-githubandAbnormal 1b1c781426 bump polymod 2025-09-08 14:11:42 -05:00
kade-githubandEric 5c8a888b77 remove includes that dont matter 2025-09-08 15:00:28 -04:00
kade-githubandEric 257576b4d3 Allow for scripted state reloading 2025-09-08 15:00:28 -04:00
sector-aandEric 56f0a7780d Debug Display HTML5 Fixes 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric faa47e8f3e More tweaks 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric b99d599bce Fix iOS compile. 2025-09-08 13:02:48 -04:00
92bc856f5b Fix macos compile.
Co-Authored-By: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-08 13:02:48 -04:00
sector-aandEric 0490ca8145 Debug display bg opacity preference 2025-09-08 13:02:48 -04:00
sector-aandEric c9b8f21f61 Debug Display modes!!! 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 60caa80a84 One more graph adjustment 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 477f85a71e Adjust graphs width. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric b5aed5fb4f Adjusted the graphs. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 7d714ecfeb Add MemoryUtil for Apple external. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric f3ff132e90 Adjust the external API a little. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 1912155b32 Organized some small stuff 2025-09-08 13:02:48 -04:00
sector-aandEric daa87c8c58 The new debug display is mostly done! 2025-09-08 13:02:48 -04:00
sector-aandEric ad87832662 The graph is done 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric b8b0a5bf73 Added task mem and adjusted the counter. 2025-09-08 13:02:48 -04:00
sector-aandEric 5a8b681e2f FPS Graph (unfinished) 2025-09-08 13:02:48 -04:00
sector-aandEric 5a6a3f5599 Small tweaks 2025-09-08 13:02:48 -04:00
sector-aandEric bab422029b FunkinCounter (unfinished) 2025-09-08 13:02:48 -04:00
Hyper_andHundrec 88fc5f9ed6 refactor: use null-safe list functions in Medals and Leaderboards 2025-09-08 22:45:32 +08:00
Hundrec f3c7cfd799 Hide the A-Bot visualizer when restarting 2025-09-08 17:10:38 +08:00
Hundrec 231439d808 Adjust haxelib warning message 2025-09-08 16:51:37 +08:00
AbnormalandHundrec d6f80730a5 check the library versions and warn the user of any outdated ones 2025-09-08 16:51:37 +08:00
AwesomezPro99andHundrec 9cfee0398a Fix stress pico bricks 2025-09-07 14:29:39 +08:00
Hyper_andAbnormal aa5956a30b Force texture to be uploaded to the GPU
Fixes an issue where multi-sparrow characters cause a stutter when the alternative sprites are displayed
2025-09-06 22:15:28 -05:00
Hundrec b9d68957e0 Cache Stress Tankmen into FunkinMemory 2025-09-07 02:37:57 +08:00
kade-githubandHundrec 1b9f5c62fb Fix a bug with Lag Adjustment strumline 2025-09-07 02:23:59 +08:00
kade-githubandHundrec a0a57ca94d Make strumlineNotes and noteHoldCovers public 2025-09-07 02:23:59 +08:00
sector-aandHundrec 271451b9df Update assets 2025-09-07 01:39:13 +08:00
sector-aandHundrec 21458f26f2 Fix dialogue proceeding when pressing the pause button 2025-09-07 01:39:13 +08:00
kade-github c1b4d4d53e space... STATE_CREATE 2025-09-06 09:41:24 -07:00
kade-github 28d71dc2fe add onStateCreate to Module 2025-09-06 09:41:24 -07:00
AwesomezPro99andHundrec a10bbf60ac Fix menu music fade in 2025-09-06 16:37:08 +08:00
KoloandHundrec 4e572b171f cancel login if ongoing 2025-09-06 16:15:36 +08:00
Hundrec cc768c83aa Remove extra line 2025-09-06 16:09:06 +08:00
LasercarandHundrec 035f3770d5 No more audio playback while dragging playheads
This commit specifically fixes the note preview playhead.
2025-09-06 16:09:06 +08:00
LasercarandHundrec 2d95754d49 Chart editor audio playback while dragging playheads fix
Two down, one to go
2025-09-06 16:09:06 +08:00
VioletSnowLeopardandHundrec 47b1cee6d5 don't open welcome dialog multiple times 2025-09-06 15:59:38 +08:00
lemz1andHundrec 4ebd00bc06 haxe ui focus check 2025-09-06 15:40:00 +08:00
AwesomezPro99andHundrec 66554ec4b6 Fix backing card beat hit check 2025-09-06 15:22:24 +08:00
MAJigsaw77andHundrec bfc798c629 Update extension-admob. 2025-09-06 15:11:46 +08:00
EliteMasterEric 6b334b941a Add some import aliases so scripts don't break. 2025-09-05 17:16:54 -04:00
sector-aandEric b06090a477 Move CharacterData to data/character 2025-09-05 17:16:54 -04:00
VioletSnowLeopardandHundrec ca5fbb65e5 add *the* checkbox to crash template 2025-09-06 05:08:28 +08:00
KoloandEric c2305f32f0 this bug seems kinda old why has nobody reported it before 2025-09-05 15:51:55 -04:00
VioletSnowLeopardandEric 747446e2e4 rank anim only plays when changing difficulties 2025-09-05 14:15:56 -04:00
MrMaderaandEric 250d2188b9 Add ability to press back to exit Character Select 2025-09-05 13:23:51 -04:00
kade-github 61c710fe20 some fixes and goodness 2025-09-05 09:45:56 -07:00
KoloandKade 0fd6f5b3b7 that fucking ghost that i hate 2025-09-05 09:45:56 -07:00
HundrecandAbnormal 8a204c606a Bring back Freeplay rank slamming animation
Another Freeplay fix caused by the same commit!

Manually reverts changes from https://github.com/FunkinCrew/Funkin/commit/f7a48bcb3b9ab0f5393552b828b8687c6ae6926c where Cam converted the FreeplayRank class into a FlxSpriteGroup.
2025-09-04 14:04:28 -05:00
MAJigsaw77andMoonDroid 4401ea0c9a Organize ios project folder a little. 2025-09-04 13:32:06 +07:00
sector-aandMoonDroid 12ace54b2b Update assets 2025-09-04 13:15:58 +07:00
f15cd73dff revert changes to cache in flxpartialsound
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-04 10:49:40 +07:00
b39d7bb5a0 Don't log info if the command is clean (or display)
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-04 10:12:20 +07:00
f1066a2b0d Fix Freeplay capsule offsetting once and for all
And nudge the Gold Perfect sparkles to where they used to be in 0.5.3

Both of these issues were introduced in 0.6.0 and have finally been uprooted. Huzzah!

Co-Authored-By: Lasercar <64717068+lasercar@users.noreply.github.com>
Co-Authored-By: Kolo <67389779+koloindacrib@users.noreply.github.com>
Co-Authored-By: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
Co-Authored-By: Hyper_ <40342021+nothyper-474@users.noreply.github.com>
Co-Authored-By: Linus Torvalds <1024025+torvalds@users.noreply.github.com>
2025-09-03 18:44:24 -05:00
MAJigsaw77andHundrec b04b555b38 Intialize hxvlc asynchronously. 2025-09-04 04:28:02 +08:00
MoonDroid f349eedc72 fix: Re-add backbutton transitioning 2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid ee2f3c32eb Fixes for the recent update 2025-09-03 18:28:19 +07:00
LasercarandMoonDroid 0f99a1c7c2 Fix for https://github.com/FunkinCrew/Funkin/issues/3627 2025-09-03 18:28:19 +07:00
LasercarandMoonDroid 95aba09cd6 Fix target variation
Also no need to do instrumental stuff if not random
2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 811b9e3343 don't freeze game if it fails to find target song/difficulty 2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 37b9823b36 minor fix
Don't allow letter sort input while the instrumental selector is up
2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 555684afda choose if you want the instrumental to be random 2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 0d5c6ac963 selecting random can choose a random available instrumental 2025-09-03 18:28:19 +07:00
Cameron TaylorandMoonDroid db67a6928c ignroe VarTypeHint since it's a bit incompatible with final 2025-09-03 17:35:24 +07:00
Cameron TaylorandMoonDroid 9c34ab5f9c checkstyle fixins for AnsiTrace.hx 2025-09-03 17:35:24 +07:00
Cameron TaylorandMoonDroid f09074b79d add checkstyle regex to ignore long lines if they are strings (w/ whitespace) 2025-09-03 17:35:24 +07:00
Cameron TaylorandMoonDroid a5842fbe3f some checkstyle changes 2025-09-03 17:35:24 +07:00
8db654e63d Add support for 16K page sizes on Android.
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-03 17:11:36 +07:00
MoonDroid 60144c0dc7 refactor: make time formatter extensible and easier to read 2025-09-03 16:12:59 +07:00
MAJigsaw77andMoonDroid 830b5dfd71 Properly formatted build time. 2025-09-03 16:12:59 +07:00
MAJigsaw77andHundrec 12d81f0f3b Fix setting the window fullscreen from preloader. 2025-09-03 16:56:23 +08:00
sector-aandMoonDroid 8c835447c8 Improved positioning of the Dialogue Box and the Portrait 2025-09-03 06:32:53 +07:00
547 changed files with 26805 additions and 20988 deletions
+3 -3
View File
@@ -48,19 +48,19 @@ body:
attributes:
label: Mobile Device Model
description: (Mobile users only) What mobile device are you playing on?
placeholder: ex. iPhone 16, Galaxy S25, iPad 11th Gen
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
- type: input
attributes:
label: Mobile OS Version
description: (Mobile users only) What version is your Operating System?
placeholder: ex. iOS 18.5, Android 15, iPadOS 18.5
placeholder: ex. iOS 26.2, Android 16, iPadOS 26.2
- type: input
attributes:
label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
placeholder: ex. 0.8.1
validations:
required: true
+1 -1
View File
@@ -36,7 +36,7 @@ body:
attributes:
label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
placeholder: ex. 0.8.1
validations:
required: true
+1 -1
View File
@@ -36,7 +36,7 @@ body:
attributes:
label: Version
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
placeholder: ex. 0.7.4
placeholder: ex. 0.8.1
validations:
required: true
+4 -3
View File
@@ -15,6 +15,7 @@ body:
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
- label: I have properly titled my issue
- label: I did not have any mods installed when I encountered this issue
- type: dropdown
attributes:
@@ -48,19 +49,19 @@ body:
attributes:
label: Mobile Device Model
description: (Mobile users only) What mobile device are you playing on?
placeholder: ex. iPhone 16, Galaxy S25, iPad 11th Gen
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
- type: input
attributes:
label: Mobile OS Version
description: (Mobile users only) What version is your Operating System?
placeholder: ex. iOS 18.5, Android 15, iPadOS 18.5
placeholder: ex. iOS 26.2, Android 16, iPadOS 26.2
- type: input
attributes:
label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
placeholder: ex. 0.8.1
validations:
required: true
+12 -2
View File
@@ -35,14 +35,14 @@ jobs:
docker:
- '.github/workflows/build-game.yml'
- '**/Dockerfile'
- uses: actions/github-script@v7
- uses: actions/github-script@v8
id: should-trigger
with:
result-encoding: string
script: |
const { payload } = context
const changes = ${{ steps.filter.outputs.changes }}
const manual = payload.commits
const manual = payload?.commits
.some(c => c.message.toLowerCase().replace(/\W/g, " ").includes("docker rebuild"))
console.log({ payload, changes, manual, commits: payload.commits })
@@ -167,6 +167,11 @@ jobs:
cd .haxelib/hxcpp/git/tools/hxcpp
haxe compile.hxml
cd ../../../../..
- name: Populate .env
run: |
touch .env
grep API_NG_APP_ID .env || echo API_NG_APP_ID=${{ secrets.API_NG_APP_ID }} >> .env
grep API_NG_ENC_KEY .env || echo API_NG_ENC_KEY=${{ secrets.API_NG_ENC_KEY }} >> .env
- name: Build game (windows)
if: ${{ matrix.target == 'windows' }}
run: |
@@ -246,6 +251,11 @@ jobs:
path: /usr/share/hxcpp
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
- name: Populate .env
run: |
touch .env
grep API_NG_APP_ID .env || echo API_NG_APP_ID=${{ secrets.API_NG_APP_ID }} >> .env
grep API_NG_ENC_KEY .env || echo API_NG_ENC_KEY=${{ secrets.API_NG_ENC_KEY }} >> .env
- name: Build game
run: |
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
+5 -1
View File
@@ -13,10 +13,14 @@ shitAudio/
.swp
NewgroundsCredentials.hx
# Exclude IntelliJ IDEA stuff
.idea/
*.iml
# Mobile signing stuff
.apple/
.env
key.keystore
*.keystore
# Exclude JS stuff
node_modules/
+1
View File
@@ -4,3 +4,4 @@
[submodule "art"]
path = art
url = https://github.com/FunkinCrew/Funkin.art
shallow = true
+25 -87
View File
@@ -1,31 +1,22 @@
{
"[haxe]": {
// Automatically keep Haxe files formatted.
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "never"
},
"editor.codeActionsOnSave": { "source.organizeImports": "never" },
"editor.defaultFormatter": "nadako.vshaxe",
"editor.tabSize": 2
},
"[json]": {
// Automatically keep JSON files formatted.
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
// Automatically keep JSONC files formatted.
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.tabWidth": 2,
// XML formatting style configuration
// XML Formatting
"xml.format.enabled": true,
"xml.format.legacy": false,
"xml.format.emptyElements": "collapse",
@@ -33,7 +24,7 @@
"xml.format.enforceQuoteStyle": "preferred",
"xml.format.preserveAttributeLineBreaks": false,
"xml.format.preservedNewlines": 0,
"xml.format.splitAttributes": false,
"xml.format.splitAttributes": "preserve",
"xml.format.joinCDATALines": true,
"xml.format.preserveEmptyContent": false,
"xml.format.joinCommentLines": false,
@@ -56,26 +47,19 @@
"xml.format.maxLineWidth": 0,
"xml.format.grammarAwareFormatting": true,
// Generic file formatting style configuration
// General formatting
"files.insertFinalNewline": true,
"files.trimFinalNewlines": false,
"files.trimTrailingWhitespace": true,
// Automatically detect indentation.
"editor.detectIndentation": true,
"editor.insertSpaces": true,
"editor.tabSize": 2,
// Automatically enforce Linux style line endings.
"prettier.tabWidth": 2,
"files.eol": "\n",
"haxe.displayPort": "auto",
"haxe.enableCompilationServer": true,
"haxe.enableServerView": true,
"haxe.displayServer": {
"arguments": ["-v"]
},
// Fix file associations for HScript.
"haxe.displayServer": { "arguments": ["-v"] },
"files.associations": {
"*.hxp": "haxe",
"*.hscript": "haxe",
@@ -84,14 +68,27 @@
"*.hxc": "haxe"
},
"projectManager.git.baseFolders": ["./"],
"haxecheckstyle.sourceFolders": ["src", "source"],
"haxecheckstyle.externalSourceRoots": [],
"haxecheckstyle.configurationFile": "checkstyle.json",
"haxecheckstyle.codeSimilarityBufferSize": 100,
"lime.projectFile": "project.hxp",
"lime.targets": [
{ "name": "windows", "enabled": true, "label": "Windows" },
{ "name": "mac", "enabled": true, "label": "macOS" },
{ "name": "linux", "enabled": true, "label": "Linux" },
{ "name": "html5", "enabled": true, "label": "HTML5" },
{ "name": "android", "enabled": true, "label": "Android" },
{ "name": "ios", "enabled": true, "label": "iOS" },
// Disabled targets
{ "name": "hl", "enabled": false, "label": "HashLink" },
{ "name": "air", "enabled": false },
{ "name": "electron", "enabled": false },
{ "name": "flash", "enabled": false },
{ "name": "neko", "enabled": false },
{ "name": "tvos", "enabled": false }
],
"lime.defaultTargetConfiguration": "Windows / Debug",
"lime.targetConfigurations": [
{
"label": "Windows / Debug (Discord)",
@@ -103,21 +100,11 @@
"target": "windows",
"args": ["-debug", "-DANIMATE", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (FlxAnimate Test)",
"target": "hl",
"args": ["-debug", "-DANIMATE"]
},
{
"label": "Windows / Debug (Straight to Freeplay)",
"target": "windows",
"args": ["-debug", "-DFREEPLAY", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Straight to Freeplay)",
"target": "hl",
"args": ["-debug", "-DFREEPLAY"]
},
{
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
"target": "windows",
@@ -132,26 +119,6 @@
"target": "windows",
"args": ["-debug", "-DSONG=2hot", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
"target": "hl",
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
},
{
"label": "Windows / Debug (Conversation Test)",
"target": "windows",
"args": ["-debug", "-DDIALOGUE", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Conversation Test)",
"target": "hl",
"args": ["-debug", "-DDIALOGUE"]
},
{
"label": "Windows / Debug (Results Screen Test)",
"target": "windows",
"args": ["-debug", "-DRESULTS"]
},
{
"label": "Windows / Debug (Straight to Stage Editor)",
"target": "windows",
@@ -172,36 +139,16 @@
"target": "windows",
"args": ["-debug", "-DHXVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Straight to Animation Editor)",
"target": "hl",
"args": ["-debug", "-DANIMDEBUG"]
},
{
"label": "Windows / Debug (Latency Test)",
"target": "windows",
"args": ["-debug", "-DLATENCY", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Latency Test)",
"target": "hl",
"args": ["-debug", "-DLATENCY"]
},
{
"label": "Windows / Debug (Waveform Test)",
"target": "windows",
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "Windows / Release (GitHub Actions)",
"target": "windows",
"args": ["-release", "-DGITHUB_BUILD"]
},
{
"label": "HashLink / Debug (Waveform Test)",
"target": "hl",
"args": ["-debug", "-DWAVEFORM"]
},
{
"label": "HTML5 / Debug (Watch)",
"target": "html5",
@@ -209,10 +156,7 @@
}
],
"lime.buildTypes": [
{
"label": "Debug",
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{ "label": "Debug", "args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"] },
{
"label": "Debug (Unlock Everything)",
"args": ["-debug", "-DUNLOCK_EVERYTHING"]
@@ -225,10 +169,7 @@
"label": "Debug (Straight to Chart Editor)",
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "Release",
"args": ["-release"]
},
{ "label": "Release", "args": ["-release"] },
{
"label": "Release (GitHub Actions)",
"args": ["-release", "-DGITHUB_BUILD"]
@@ -244,9 +185,6 @@
],
"vscord.app.privacyMode.enable": true,
"json.schemas": [
{
"fileMatch": ["/hmm.json"],
"url": "./.vscode/schema/hmm.json"
}
{ "fileMatch": ["/hmm.json"], "url": "./.vscode/schema/hmm.json" }
]
}
+405 -1
View File
@@ -4,6 +4,410 @@ All notable changes 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).
## [0.8.2] - 2026-02-23
### Added
- Added 14 new blend modes, allowing the engine to render better visual effects.
- Added a PlayState parameter that mirrors notes during a song.
- Added preview graphs for Chart Editor event easing types. ([eea1265](https://github.com/FunkinCrew/Funkin/commit/eea12657b1d8131014a3fdc270c3f351fae6e851)) - by @PurSnake in [#6374](https://github.com/FunkinCrew/Funkin/pull/6374)
- Added the ability to mirror notes on the X and Y axes in the Chart Editor. ([fd0c2df](https://github.com/FunkinCrew/Funkin/commit/fd0c2df35ee8cda3f1566a297c4faec984d809bb)) - by @Lasercar in [#4372](https://github.com/FunkinCrew/Funkin/pull/4372)
- Added a deselect button and shortcut to the Stage Editor. ([a9f1d41](https://github.com/FunkinCrew/Funkin/commit/a9f1d41771f0c97fdd70d4d6c54087ac0e64ff38)) - by @Lasercar in [#5281](https://github.com/FunkinCrew/Funkin/pull/5281)
### Changed
- Made some improvements to Polymod:
- Added string interpolation support. (Thanks NotHyper-474!)
- Scripted classes can now extend nothing. (Thanks NotHyper-474!)
- Scripted classes can now extend other scripted classes. (Thanks KoloInDaCrib!)
- Tweaked some error messages to be more verbose.
- Added `scriptHas` and `staticScriptHas` functions to check if a script has a specific variable/function.
- The `is` operator is now more accurate to regular Haxe. (Thanks NotHyper-474!)
- Static functions can now use static extensions (`using`s). (Thanks NotHyper-474!)
- `interface`s can now be properly parsed. (Thanks Starexify!)
- Optional fields and extensions can now be used in `typedef`s. (Thanks Starexify!)
- Added stricter checking for default/optional arguments in functions. (Thanks Starexify!)
- May be a breaking change for some scripts, so please update your scripts to account for this!
- Enums now have multiple arguments. (Thanks Starexify!)
- Basic types (e.g `String`, `Int`, `Bool`, etc.) can now be used with the `is` operator without having to import the type first.
- Tweaked charts for the following songs:
- Bopeebo (Pico Mix) [Normal/Hard] - Adjusted note timings and added a “hey” animation event in Sections 18, 26, and 27.
- Fresh Erect [Nightmare] - Added 2 notes where Boyfriend’s vocals echo.
- DadBattle Erect [Nightmare] - Added a missing jack note in Section 38, added holds to two notes in Section 63.
- DadBattle (Pico Mix) [all difficulties] - Adjusted timings for various notes in Sections 37-40 and 60.
- Spookeez Erect [Erect/Nightmare] - Adjusted many hold note lengths to better match vocals.
- Spookeez (Pico Mix) - Added one missing note to Section 14.
- South Erect [Erect/Nightmare] - Adjusted many hold note lengths to better match vocals.
- Pico [Easy] - Adjusted timings for one note each in Sections 36 and 38.
- Pico [Normal/Hard] - Adjusted timings for Boyfriend’s right notes to be a bit earlier in Sections 31-42.
- Pico [all difficulties] - Added a Focus Camera event on Girlfriend during the duet.
- Satin Panties Erect [Erect/Nightmare] - Split a hold note into two for Mom in Sections 14 and 18.
- High Erect [Erect/Nightmare] - Adjusted timing for one note each in Sections 25 and 41.
- M.I.L.F [Easy] - Adjusted timings for two notes in Section 73.
- M.I.L.F [Hard] - Changed one down note into an up note in Section 15 for consistency.
- Cocoa Erect [Nightmare] - Fixed one note not being a Mom notekind in Section 24, added holds to 2 notes in Section 67.
- Cocoa (Pico Mix) [all difficulties] - Adjusted timings for two notes in Section 15.
- Eggnog Erect [Erect/Nightmare] - Adjusted how a grace note is charted in Section 52, added 2 missing notes for Mom in Section 72, adjusted Camera Bop event in Section 63.
- Senpai (Pico Mix) [all difficulties] - Adjusted timing for one note in Section 10.
- Roses [Normal] - Adjusted timings for four notes in Sections 30, 32, 41, and 43.
- Thorns Erect [Erect] - Adjusted timings for two notes in Section 36, removed some double notes to differentiate the chart from Nightmare.
- Ugh Erect [Erect/Nightmare] - Adjusted some Zoom Camera events around Section 24.
- Guns (Pico Mix) [all difficulties] - Fixed many timing issues throughout the whole song.
- SPAGHETTI [Normal/Hard] - Added one note to match Boyfriend’s rapping.
- Darnell (BF Mix) [all difficulties] - Removed extra notes from Sections 49 and 50, adjusted various note timings throughout the song.
- Lit Up [Normal] - Added one missing note each in Sections 40 and 60.
- Lit Up (BF Mix) [Normal/Hard] - Adjusted timings for various grace notes in Sections 1, 52, 55, and 56.
- 2hot [Easy/Normal] - Recharted many sections to be more predictable.
- [macOS] Disabled high DPI to significantly improve performance on some displays.
- Improved the Lag Adjustment menu to better handle very large offsets.
- Chart Editor exporting now omits default values and orders values more consistently.
- Chart Editor playtests now processes all events before the starting position.
- Optimized the Week 2 Spooky Mansion stage background by using a texture atlas. ([2bdd5a7](https://github.com/FunkinCrew/funkin.assets/commit/2bdd5a713f0b5c7e945c457e2a4a92bdb367cc02)) - by @MaybeMaru and @Trayfellow in [funkin.assets#348](https://github.com/FunkinCrew/funkin.assets/pull/348)
- Added more default imports for mods. ([1620cf3](https://github.com/FunkinCrew/Funkin/commit/1620cf388973eab5d83e151eec28b2c8abc84eb1)) - by @Starexify in [#6706](https://github.com/FunkinCrew/Funkin/pull/6706)
- The Set Camera Bop event now supports decimal values for rate and offset at 0.25 precision. ([ffaba34](https://github.com/FunkinCrew/Funkin/commit/ffaba343258c4a0e5ee7e1bd7fa0e8138ae60bc1)) - by @KoloInDaCrib in [#6741](https://github.com/FunkinCrew/Funkin/pull/6741)
### Fixed
- Fixed some issues with Polymod:
- The super constructor for scripted classes can now accept more than 4 arguments. (Thanks NotHyper-474!)
- Functions can now accept more than 8 arguments. (Thanks NotHyper-474!)
- Script variables are no longer dropped early when passing them to a callback (ex. `FlxTimer`). (Thanks NotHyper-474!)
- Fix a memory leak that could happen if a script tried to reference an anonymous object with a lot of `StringMap`s. (Thanks NotHyper-474!)
- Properties (`get_`/`set_` functions) are now much more accurate to regular Haxe. (Thanks NotHyper-474!)
- Re-exported some assets to fix some issues:
- Re-exported all variations of Pico as texture atlases to optimize memory usage.
- Freeplay DJ Pico’s table sparks no longer persist when his gun is lifted.
- Pico (Blazin’)’s game over retry animation now properly displays its blend modes.
- Pico (Christmas) no longer shows several stray marks around his sprites.
- Nene (Christmas) no longer becomes slightly offset when her knife is raised.
- The running tankmen in Stress (Pico Mix) no longer tank the game’s performance.
- Daddy Dearest now properly holds his up pose.
- Monster (Christmas)’s hat no longer moves while he holds a pose.
- Darnell’s can in 2hot now shows a sparkling effect.
- [DESKTOP] Modded atlas characters with both .png and .astc spritesheets no longer crash the game.
- [DESKTOP] The game display no longer sometimes becomes offset from the window.
- [MOBILE] Selecting a song using touch controls when a keyboard is connected no longer disables the keyboard strumline layout.
- [ANDROID] Improved Freeplay scrolling behavior on high framerate displays.
- Ctrl-clicking on a hold note no longer crashes the Chart Editor.
- [WINDOWS] Chart Editor save dialog windows no longer freeze the game.
- The Chart Editor Results Screen playtest option now consistently shows the Results Screen after each playtest.
- Scrolling really quickly in Freeplay no longer crashes the game. ([76699b2](https://github.com/FunkinCrew/Funkin/commit/76699b235cd5b359bc34e6950cd05bbbb13918f8)) - by @ComedyLost in [#6962](https://github.com/FunkinCrew/Funkin/pull/6962)
- Fixed a memory leak in the Freeplay menu. ([910ba65](https://github.com/FunkinCrew/Funkin/commit/910ba650b0c1011802b151b85f4a9d8177ae95c7)) - by @KoloInDaCrib in [#6615](https://github.com/FunkinCrew/Funkin/pull/6615)
- Modded difficulty dots and graphics now render correctly in Freeplay. ([90b856d](https://github.com/FunkinCrew/Funkin/commit/90b856d88635ac4e5517e934298e3820b2e5f45c)) - by @ComedyLost in [#6205](https://github.com/FunkinCrew/Funkin/pull/6205)
- Freeplay capsules with long song names no longer display too far to the left during the new rank animation. ([17a914c](https://github.com/FunkinCrew/Funkin/commit/17a914c933592cb3ba79881ca6a8d900b8513cbc)) - by @VirtuGuy in [#6690](https://github.com/FunkinCrew/Funkin/pull/6690)
- Freeplay capsule BPM values ending in 1 now display with proper spacing. ([e5b4351](https://github.com/FunkinCrew/Funkin/commit/e5b43510dd810a74a7d06f7f02c52892d11d6f8b)) - by @VirtuGuy in [#6696](https://github.com/FunkinCrew/Funkin/pull/6696)
- The Freeplay pixel icon now properly plays its confirm animation when mashing the capsule. ([a56bc44](https://github.com/FunkinCrew/Funkin/commit/a56bc4462d1c49e8ec71462b9bd7ff338b743c93)) - by @VirtuGuy in [#6723](https://github.com/FunkinCrew/Funkin/pull/6723)
- [HTML5] Scrolling with the mouse now works properly in Story Mode and Freeplay. ([506d80b](https://github.com/FunkinCrew/Funkin/commit/506d80b287b969121dc3601714c2459cc71f7e55)) - by @VirtuGuy in [#6621](https://github.com/FunkinCrew/Funkin/pull/6621)
- Optimized rendering in the Character Select menu. ([2ecebf7](https://github.com/FunkinCrew/Funkin/commit/2ecebf79205a66b64148d64bbb3162c9fc073d85)) - by @MaybeMaru in [#6789](https://github.com/FunkinCrew/Funkin/pull/6789)
- [MOBILE] Tapping on a lock in Character Select now plays its reject animation. ([05148f0](https://github.com/FunkinCrew/Funkin/commit/05148f0e5dad4d60089e6074e616b583ba29a5e6)) - by @VirtuGuy in [#6779](https://github.com/FunkinCrew/Funkin/pull/6779)
- Hold note covers now follow when the strumline position is modified. ([77c6651](https://github.com/FunkinCrew/Funkin/commit/77c6651e245ea276b1bdf33e5d6732073dcb6037)) - by @MightyTheArmiddilo in [#6746](https://github.com/FunkinCrew/Funkin/pull/6746)
- The pause and game over screens no longer sometimes play the wrong character's themes. ([878644a](https://github.com/FunkinCrew/Funkin/commit/878644aee7ba425e482707b94b9f887ec23f6306)) - by @KoloInDaCrib in [#6812](https://github.com/FunkinCrew/Funkin/pull/6812)
- The game over camera focus point is now consistent, regardless of which animation was previously playing. ([18cceb4](https://github.com/FunkinCrew/Funkin/commit/18cceb4946a51fda0009d77baae1115437694415)) - by @KoloInDaCrib in [#6705](https://github.com/FunkinCrew/Funkin/pull/6705)
- Pico (Pixel) and other pixel characters now use RetroCameraFade as Boyfriend (Pixel) does. ([d1382aa](https://github.com/FunkinCrew/Funkin/commit/d1382aa985f97e3b40b0b629cb3d04301ebfb55e)) - by @afreetoplaynoob in [#6526](https://github.com/FunkinCrew/Funkin/pull/6526)
- [MOBILE] The back button can no longer exit during the Boyfriend fakeout death animation. ([9d06018](https://github.com/FunkinCrew/Funkin/commit/9d0601813e912de9e25a851ad9204a654316625a)) - by @ActualMandM in [#6917](https://github.com/FunkinCrew/Funkin/pull/6917)
- The song name text is now properly hidden at the beginning of the Results Screen animation. ([a5b15b9](https://github.com/FunkinCrew/Funkin/commit/a5b15b9424f16d8eadc7a92f1c355da647990c3a)) - by @NotHyper-474 and @Trayfellow in [#6931](https://github.com/FunkinCrew/Funkin/pull/6931)
- The sticker transition no longer covers the volume soundtray. ([616546d](https://github.com/FunkinCrew/Funkin/commit/616546d298a06c4135a32ffad41483be208552f1)) - by @VirtuGuy in [#6727](https://github.com/FunkinCrew/Funkin/pull/6727)
- Lightning now appears more regularly during South Erect and South (Pico Mix). ([9ca2092](https://github.com/FunkinCrew/funkin.assets/commit/9ca20929bc3dde6b07d74100c45fb45e48769c35)) - by @JackXson-Real in [funkin.assets#345](https://github.com/FunkinCrew/funkin.assets/pull/345)
- [HTML5] The city lights in the Week 3 stages now show up. ([603684f](https://github.com/FunkinCrew/funkin.assets/commit/603684ffb327f03d8216aaeaba143ca5f9cd77f0)) - by @VirtuGuy in [funkin.assets#342](https://github.com/FunkinCrew/funkin.assets/pull/342)
- The Week 3 Pico doppelganger cutscene background music no longer cuts off at the end. ([8ef58aa](https://github.com/FunkinCrew/funkin.assets/commit/8ef58aabdba8287d4c88808e2f0ecc0ac385b383)) - by @Trayfellow in [funkin.assets#349](https://github.com/FunkinCrew/funkin.assets/pull/349)
- The Story Mode cutscene before Stress now properly plays when viewed more than once. ([6ec45ec](https://github.com/FunkinCrew/funkin.assets/commit/6ec45ec82ca77188b1b2b9c58ada172f5c357103)) - by @NotHyper-474 in [funkin.assets#341](https://github.com/FunkinCrew/funkin.assets/pull/341)
- Darnell's can sound effects in 2hot no longer play twice with high offsets or when lagging. ([6315e25](https://github.com/FunkinCrew/funkin.assets/commit/6315e25eaaa8be4ca85e9b607f547595c699997a)) - by @JackXson-Real in [funkin.assets#354](https://github.com/FunkinCrew/funkin.assets/pull/354)
- [MOBILE] The cutscene after 2hot now properly accounts for different resolutions. ([dd3371a](https://github.com/FunkinCrew/Funkin/commit/dd3371a549f4e10f68f1a458f40ec1a342f07876)) - by @ActualMandM in [#6937](https://github.com/FunkinCrew/Funkin/pull/6937)
- Made the AttractState video skipping pie smoother. ([5a161b1](https://github.com/FunkinCrew/Funkin/commit/5a161b1d7342d7931950edf37554ce3a338736f9)) - by @PurSnake in [#6728](https://github.com/FunkinCrew/Funkin/pull/6728)
- The screenshot preview no longer breaks when changing states. ([dc78db8](https://github.com/FunkinCrew/Funkin/commit/dc78db8808a7ea205fe75eba006e4ec1c57a8190)) - by @Lasercar in [#6792](https://github.com/FunkinCrew/Funkin/pull/6792)
- Holding Ctrl and clicking on a hold note trail no longer crashes the Chart Editor (again). ([86ac336](https://github.com/FunkinCrew/Funkin/commit/86ac336d62a65fb420cdd6b1be739c3262d21159)) - by @Lasercar in [#6912](https://github.com/FunkinCrew/Funkin/pull/6912)
- Chart Editor tooltips no longer appear when hovering over a deleted note or event. ([091c18b](https://github.com/FunkinCrew/Funkin/commit/091c18bccd95b588ba0d4577a94d83acf5942417)) - by @KoloInDaCrib in [#6929](https://github.com/FunkinCrew/Funkin/pull/6929)
- Chart Editor vocal waveforms now render more accurately after BPM changes in the song. ([4e8f48d](https://github.com/FunkinCrew/Funkin/commit/4e8f48d5f0f1be4438c4ef9571137eaa35935f67)) - by @gamerbross in [#6833](https://github.com/FunkinCrew/Funkin/pull/6833)
- The Chart Editor vocal waveforms now display in the proper position regardless of the game's aspect ratio. ([1fb9040](https://github.com/FunkinCrew/Funkin/commit/1fb9040470432528a0441d7938d217e424ce27fd)) - by @Starexify in [#6193](https://github.com/FunkinCrew/Funkin/pull/6193)
- The second section number and divider now renders consistently in the Chart Editor. ([ac539bd](https://github.com/FunkinCrew/Funkin/commit/ac539bdf26ee7b5ecdcc52665c7c95287ef491c0)) - by @NotHyper-474 in [#6765](https://github.com/FunkinCrew/Funkin/pull/6765)
- The Chart Editor now saves the Show Results Screen setting. ([c893a73](https://github.com/FunkinCrew/Funkin/commit/c893a7320d72abd3743bc5c1ddeaecf073035eb1)) - by @MightyTheArmiddilo in [#6854](https://github.com/FunkinCrew/Funkin/pull/6854)
- Closing the game while in the Stage Editor no longer crashes the game. ([99cd4e2](https://github.com/FunkinCrew/Funkin/commit/99cd4e2cbd7fba87249b02b072a1e60f07c2c907)) - by @VirtuGuy in [#6775](https://github.com/FunkinCrew/Funkin/pull/6775)
- The onion skin now properly renders as transparent after changing characters in the Animation Editor. ([5c444f1](https://github.com/FunkinCrew/Funkin/commit/5c444f1109e5af04735a4383e577c69682b3713f)) - by @VirtuGuy in [#6545](https://github.com/FunkinCrew/Funkin/pull/6545)
- Blacklisted a class for security. ([3a113c2](https://github.com/FunkinCrew/Funkin/commit/3a113c270f92a6b14dfcda221124742437e1670a)) - by @charlesisfeline in [#6573](https://github.com/FunkinCrew/Funkin/pull/6573)
- Even more tiny fixes!
## [0.8.1] - 2026-01-04
### Added
- Added a new "Non-scoreable" notekind to the Chart Editor that doesn't affect scores and ranks, or play miss animations.
### Changed
- Changed the order in which events are dispatched in PlayState. ([16f3e03](https://github.com/FunkinCrew/Funkin/commit/16f3e038d80c37d016c9d2b6cdd1012c94ef88b9)) - by @ComedyLost in [#6606](https://github.com/FunkinCrew/Funkin/pull/6606)
### Fixed
- The last note of SPAGHETTI no longer counts toward scoring, allowing players to earn Perfect ranks.
## [0.8.0] - 2026-01-03
The LE SSERAFIM collab update!
### Added
- Added a new playable song: SPAGHETTI (feat. j-hope of BTS) (Clean ver.) by LE SSERAFIM (feat. j-hope)!
- Try it out from either Story Mode or the Freeplay Menu!
- Added subtitles for various cutscenes and songs throughout the game.
- This can be toggled in the Preferences menu.
- [MOBILE] Added support for Newgrounds logins and saving/loading from the cloud.
- [MOBILE] Added support for opening FNFC chart files through the filesystem.
- [MOBILE] Added haptics for tapping to begin on the Title Screen.
- [MOBILE] Added the back button to the Character Select menu.
- Added new function callbacks for Module scripts in Freeplay and Character Select.
- Added `getOtherNotes()` function for counting notes that are not of a certain notekind.
- Implemented stacked patches to JSON merging.
- This allows mods to add new variations to existing songs without any extra fuss. See the [Code Cookbook](https://thekade.net/funkin-cookbook/) for more information.
- Added the ability to import Osu!Mania and StepMania charts in the Chart Editor.
- Added Move Difficulty and Clone Difficulty buttons to the Chart Editor Difficulty window.
- Added a checkbox to hide vocal waveforms in the Chart Editor.
- Added support for SRT subtitles that can display in game. ([a7fc70a](https://github.com/FunkinCrew/Funkin/commit/a7fc70a5c9cbd811b48cf2eb2f6c221814c2a3cd)) - by @PurSnake in [#6206](https://github.com/FunkinCrew/Funkin/pull/6206)
- Added support for various render types and scripting for the Freeplay DJ. ([8b38bcf](https://github.com/FunkinCrew/Funkin/commit/8b38bcfbdd9ef1b135046022b1b93eec5dedbd28)) - by @PurSnake and @AbnormalPoof in [#5698](https://github.com/FunkinCrew/Funkin/pull/5698)
- Level props can now have a flipX field. ([4961d4e](https://github.com/FunkinCrew/Funkin/commit/4961d4ee992445a487587dd3a7f3ab965eb4f5cf)) - by @Starexify in [#6218](https://github.com/FunkinCrew/Funkin/pull/6218)
- [LINUX] Added support for Feral Gamemode. ([555ec09](https://github.com/FunkinCrew/Funkin/commit/555ec09ef5be3c1d1966eeaa0275835e59aa5709)) - by @Noobz4Life in [#2473](https://github.com/FunkinCrew/Funkin/pull/2473)
- Added support for importing charts from Osu!Mania. ([0be42bf](https://github.com/FunkinCrew/Funkin/commit/0be42bf0475975d3195d4da85e653899df72a28c)) - by @FuroYT in [#6155](https://github.com/FunkinCrew/Funkin/pull/6155)
- Added a "No Animation" notekind to the Chart Editor. ([618e093](https://github.com/FunkinCrew/Funkin/commit/618e093e1ce2d4f45fa4da5d34ed84e58394126e)) - by @Eviethecoder in [#4036](https://github.com/FunkinCrew/Funkin/pull/4036)
- Added a way to adjust time signature/BPM changes through the Metadata window in the Chart Editor. ([b05a5c7](https://github.com/FunkinCrew/Funkin/commit/b05a5c7f54f9b41d63081837dd2c7392203b4530)) - by @Keoiki and @Lasercar in [#4770](https://github.com/FunkinCrew/Funkin/pull/4770)
- Added character preview windows for the player and opponent in the Chart Editor. ([9f59231](https://github.com/FunkinCrew/Funkin/commit/9f59231d58a889e2c9a0f8e7e30b2e1c370e0d9a)) - by @PurSnake and @ComedyLost in [#6221](https://github.com/FunkinCrew/Funkin/pull/6221)
- Added the option to view the Results Screen after Chart Editor playtests. ([eeba677](https://github.com/FunkinCrew/Funkin/commit/eeba677da9fac75cd34481ead01dc05dfa074bce)) - by @Lasercar in [#4087](https://github.com/FunkinCrew/Funkin/pull/4087)
- Added the option to carry over Chart Editor volume levels into a playtest. ([4dd87e7](https://github.com/FunkinCrew/Funkin/commit/4dd87e7308789bae9a9e903bb15737e8149012f7)) - by @KoloInDaCrib in [#6302](https://github.com/FunkinCrew/Funkin/pull/6302)
- Added the ability to time-travel during a Chart Editor playtest in release builds with PgUp and PgDown. ([4a00429](https://github.com/FunkinCrew/Funkin/commit/4a00429a57f03f60159a4e2e4f531bd6fa118658)) - by @AbnormalPoof in [#4209](https://github.com/FunkinCrew/Funkin/pull/4209)
- Added the Rift of the Necrodancer collab trailer to the pool of videos in Attract Mode.
### Changed
- The mod API version is now 0.8.0. Please update your mods to ensure they work.
- Made some improvements to Polymod:
- Scripts can no longer accidentally redefine variables defined in their super class.
- `Math` and `Std` can now properly be used in static functions. (Thanks NotHyper-474!)
- Significantly improved the speed of retrieving modded assets, improving the performance of the game with a lot of mods installed. (Thanks PurSnake!)
- Replaced `FlxAnimate` with `flixel-animate` to overhaul texture atlas handling:
- Significantly improved performance in the Character Select Menu.
- Pico (holding Nene)’s game over confirm animation now plays properly.
- The camera now properly focuses on atlas characters after restarting the song.
- Atlas characters now properly display the rimlight shader.
- Implemented multiple new settings for atlas sprites.
- Fixed a ton of bugs across various menus.
- Re-exported many assets to improve memory usage and performance throughout the game.
- [MOBILE] Music from external sources now pauses when opening the game.
- [MOBILE] The game is now named “FNF” on the Home Screen.
- [DESKTOP] Reworked window resizing behavior to behave more consistently.
- Made it easier to implement custom Pause Menu substates.
- Pressing F6 (rebindable) now cycles through debug display modes.
- Boyfriend and Girlfriend’s scared animations in Week 2 now last longer.
- Added “Change difficulty” to the list of undo-able actions in the Chart Editor.
- Changed the default easing type for the Chart Editor Focus Camera event from Linear to Classic.
- The game now displays a user-friendly crash message when attempting to play without a graphics card. ([5270353](https://github.com/FunkinCrew/Funkin/commit/52703536a62fc004fb7be0457da89705f812364f)) - by @ACrazyTown in [#6160](https://github.com/FunkinCrew/Funkin/pull/6160)
- Adjusted strumline confirm animations when hitting notes to feel more responsive. ([62a3f73](https://github.com/FunkinCrew/Funkin/commit/62a3f73de9d653d4ca5a7d7f4c895fc9fd1a4be7)) - by @gamerbross in [#6261](https://github.com/FunkinCrew/Funkin/pull/6261)
- The Lag Adjustment menu now displays a note splash when hitting perfectly. ([342c3cd](https://github.com/FunkinCrew/Funkin/commit/342c3cd7d6f33b3babb70f3d1fb604b0bdb90464)) - by @ExtraCode75 in [#6602](https://github.com/FunkinCrew/Funkin/pull/6602)
- Freeplay song previews now fade out before restarting. ([aaf5084](https://github.com/FunkinCrew/Funkin/commit/aaf5084fd9e9c2d3b7e823767a93db82ce908d86)) - by @JVNpixels in [#6094](https://github.com/FunkinCrew/Funkin/pull/6094)
- The game's volume is now reduced when unfocused without pausing. ([3040692](https://github.com/FunkinCrew/Funkin/commit/30406928522f88762eab4bbf571fedf1424f7569)) - by @PurSnake in [#6250](https://github.com/FunkinCrew/Funkin/pull/6250)
- Optimized the way the windows in Week 3 are handled. ([70d433d](https://github.com/FunkinCrew/funkin.assets/commit/70d433de3027badb39027d295d1b18d20a130741)) - by @CrusherNotDrip in [funkin.assets#291](https://github.com/FunkinCrew/funkin.assets/pull/291)
- Script create events now also run after hot-reloading with F5, rather than only during initialization. ([2f865a5](https://github.com/FunkinCrew/Funkin/commit/2f865a5c7f4d8712f1b1e0bc70cb910f4fed08dc)) - by @VirtuGuy in [#6084](https://github.com/FunkinCrew/Funkin/pull/6084)
- Hot-reloading with F5 during a Chart Editor playtest no longer returns to the Chart Editor. ([c1988f1](https://github.com/FunkinCrew/Funkin/commit/c1988f1858344a78a82bf335774d986703cac942)) - by @KoloInDaCrib in [#6275](https://github.com/FunkinCrew/Funkin/pull/6275)
- Opening the Chart Editor during a song now places the playhead at the current song position. ([71b4a58](https://github.com/FunkinCrew/Funkin/commit/71b4a58cab821c3b5a530e3a2be6d73966faa2a6)) - by @KoloInDaCrib in [#6210](https://github.com/FunkinCrew/Funkin/pull/6210)
- The Easing property for Chart Editor events is now split into two separate dropdowns: type and direction. ([d1d77dd](https://github.com/FunkinCrew/Funkin/commit/d1d77dd85c3daed22ee954353fa41e81279ced3c)) - by @PurSnake in [#5612](https://github.com/FunkinCrew/Funkin/pull/5612)
- The Chart Editor Events window now supports collapsible groups. ([9f6879e](https://github.com/FunkinCrew/Funkin/commit/9f6879ecc98e725afa3de8f9b02755047b353cd7)) - by @KoloInDaCrib in [#6554](https://github.com/FunkinCrew/Funkin/pull/6554)
- Chart Editor BPM fields now support up to 3 decimal places. ([c863ef3](https://github.com/FunkinCrew/funkin.assets/commit/c863ef378569ce0d6b0804029e5d395b1c321cbd)) - by @roma-perec-bp in [funkin.assets#273](https://github.com/FunkinCrew/funkin.assets/pull/273)
- Long difficulty and variation names are now shortened in the Chart Editor playbar. ([2c4167b](https://github.com/FunkinCrew/Funkin/commit/2c4167bc11aec92693494816735063c381279b3e)) - by @Lasercar in [#6150](https://github.com/FunkinCrew/Funkin/pull/6150)
- The remaining time in the playbar now also shows a millisecond value in the Chart Editor. ([2d5ef19](https://github.com/FunkinCrew/Funkin/commit/2d5ef19b3b3c55db1eb70506b00d4f820e7ccb20)) - by @JVNpixels in [#6089](https://github.com/FunkinCrew/Funkin/pull/6089)
- Added more hotkeys to the Chart Editor user guide. ([7d0d9eb](https://github.com/FunkinCrew/funkin.assets/commit/7d0d9ebf7ea60e04d09c9b140772c36493dc6291)) - by @afreetoplaynoob in [funkin.assets#294](https://github.com/FunkinCrew/funkin.assets/pull/294)
- The Stage Editor can now be opened from Freeplay to the selected capsule's stage. ([1ff6a16](https://github.com/FunkinCrew/Funkin/commit/1ff6a1656cebdfe415768ffe38bd43b37c663eda)) - by @Lasercar in [#5264](https://github.com/FunkinCrew/Funkin/pull/5264)
- The Stage Editor now saves backups upon quitting or crashing. ([5326c40](https://github.com/FunkinCrew/Funkin/commit/5326c40347b1548a682ef1f79eeb4a70618256ff)) - by @Lasercar in [#6190](https://github.com/FunkinCrew/Funkin/pull/6190)
- Stage Editor backups are now saved in the same folder as Chart Editor backups. ([4f8da59](https://github.com/FunkinCrew/Funkin/commit/4f8da595777c4b7fe6ddf6536ffa29a50c23dcb3)) - by @CrusherNotDrip in [#6297](https://github.com/FunkinCrew/Funkin/pull/6297)
### Fixed
- Fixed some issues with Polymod:
- Modded assets now properly go in their respective asset libraries. (Thanks NotHyper-474!)
- Static functions and fields from imported scripted classes can now be used.
- Properties can now be used in a static context.
- Local variables in a function no longer get dropped if said function calls its own `scriptCall` function. (Thanks KoloInDaCrib!)
- Local functions now properly report the class name.
- [DESKTOP] Switching audio devices no longer breaks audio processing.
- [HTML5] The Character Select Menu no longer crashes on Firefox.
- Pressing Escape and an arrow key with the Freeplay alternate instrumental selector open no longer crashes the game.
- Hot reloading with F5 on the Game Over screen no longer crashes the game.
- [HTML5] Restarting a video cutscene now properly plays the video again.
- [iOS] The navigation bar is no longer unlocked after watching an ad.
- [iOS] The “larger text” accessibility setting no longer causes the game to not fill the whole screen.
- [iOS] The game now displays error popups.
- [ANDROID] Taking a screenshot no longer pauses the game for a moment.
- The Title Screen no longer switches to Attract Mode while the Girlfriend’s Ringtone easter egg is playing.
- Holding Escape in the Main Menu no longer quickly closes the game.
- [MOBILE] Scrolling to another Week is no longer possible after entering a Story Week.
- [MOBILE] Long Freeplay capsule song names now properly scroll sideways.
- Exiting Character Select no longer allows mashing inputs in Freeplay.
- Controller inputs now register in the Character Select Menu.
- The player character in Character Select no longer slides in upon entering.
- The Character Select cursor no longer flies in from the top left corner.
- The Character Select cursor no longer continues to move without input after unfocusing the game.
- Video cutscenes no longer start at maximum volume for a split second.
- The input system now properly handles alternating between two keybinds bound to the same note.
- The camera now bops independently from the framerate during songs.
- The Pause Menu music now properly pauses when unfocusing with Pause on Unfocus enabled.
- Changing to the same difficulty as the current one during a Story Week no longer resets the Week score.
- More characters including parentheses now display properly in the Results Screen.
- [MOBILE] The rimlight for Girlfriend (Tankman Stickup) now renders properly.
- Character sprites with high global offsets no longer disappear near the edge of the screen.
- Fixed positions for Pico (Pixel) and Nene (Pixel), and removed the School (Pico) stage.
- Tweaked a few charts.
- [macOS] Fixed a crash related to notifications in the Chart Editor.
- [Windows] Opening the File Explorer no longer sometimes hangs in the Chart Editor.
- Pressing F4 to exit a debug editor before a tooltip appears no longer crashes the game.
- Changing the theme in the Chart Editor more than once no longer crashes the game.
- Unplugging a controller no longer crashes the Chart Editor.
- The Chart Editor measure ticks no longer cause a memory leak.
- Selecting multiple events by Ctrl-clicking in the Chart Editor no longer converts one into the other.
- Event tooltips now display all values including default ones.
- Pause on Unfocus now properly applies to Chart Editor playtests.
- Unfocusing while the Chart Editor is playing audio now pauses the playback.
- Fixed a memory leak with the Freeplay backing card scrolling text. ([67846d6](https://github.com/FunkinCrew/Funkin/commit/67846d6c4bfd2f42e002fa129a51772f0717329d)) - by @FuroYT in [#5963](https://github.com/FunkinCrew/Funkin/pull/5963)
- Using a static pixel icon in Character Select no longer crashes the game. ([96f8ee8](https://github.com/FunkinCrew/Funkin/commit/96f8ee85e0c2281834b7334b87d1862003e5fa05)) - by @VirtuGuy in [#6171](https://github.com/FunkinCrew/Funkin/pull/6171)
- A-Bot's visualizer no longer causes a memory leak during gameplay or in Character Select. ([6607645](https://github.com/FunkinCrew/Funkin/commit/6607645786ed8611436fa5f92a35027c2a64f1d0)) - by @FuroYT in [#5908](https://github.com/FunkinCrew/Funkin/pull/5908)
- The Credits menu no longer crashes when not using hardcoded credits. ([4366fbe](https://github.com/FunkinCrew/Funkin/commit/4366fbe51274438ad2bc190afcca42e37987386d)) - by @sphis-sinco in [#5982](https://github.com/FunkinCrew/Funkin/pull/5982)
- The game no longer lag spikes when Darnell throws his can in 2hot. ([a0bc6d6](https://github.com/FunkinCrew/funkin.assets/commit/a0bc6d61fde5799a7fc619b7fdea691b0d8bae05)) - by @KoloInDaCrib in [funkin.assets#313](https://github.com/FunkinCrew/funkin.assets/pull/313)
- Hold note covers no longer get permanently stuck playing after a lag spike. ([66882d9](https://github.com/FunkinCrew/Funkin/commit/66882d9677268396320ee003478a3e2627655755)) - by @KoloInDaCrib in [#6066](https://github.com/FunkinCrew/Funkin/pull/6066)
- Scrolling through Freeplay songs for the first time no longer stutters. ([cc486ad](https://github.com/FunkinCrew/Funkin/commit/cc486ad94b5e9806d18fb65165663fea0ce0d7b4)) - by @mikolka9144 in [#4851](https://github.com/FunkinCrew/Funkin/pull/4851)
- Re-implemented an error that appears when a video file does not exist. ([2ad5933](https://github.com/FunkinCrew/Funkin/commit/2ad593341f8bea8384646f4803dc8795a9efe32a)) - by @TechnikTil in [#6253](https://github.com/FunkinCrew/Funkin/pull/6253)
- [ANDROID] Keyboard detection and strumline positioning now work properly. ([5763ae5](https://github.com/FunkinCrew/Funkin/commit/5763ae5b728359c5639bbc578f15b65a16fa57b1)) - by @NotHyper-474 in [#6328](https://github.com/FunkinCrew/Funkin/pull/6328)
- The game now properly parses .json files beginning with a bracket. ([c7367ae](https://github.com/FunkinCrew/Funkin/commit/c7367ae33d7e11f57b821f7d5fbff39a3edc1604)) - by @NotHyper-474 in [#6164](https://github.com/FunkinCrew/Funkin/pull/6164)
- The Newgrounds logo no longer covers the intro text. ([28883fb](https://github.com/FunkinCrew/Funkin/commit/28883fb9ed025abf9f9aee2621f365cd6f4d8b6c)) - by @hucks5 in [#6196](https://github.com/FunkinCrew/Funkin/pull/6196)
- Exiting from the Save Data Options menu no longer exits the Options Menu as well. ([26b253d](https://github.com/FunkinCrew/Funkin/commit/26b253d8cac97f0dcd09a5ef233f7058cd26008e)) - by @VirtuGuy in [#6036](https://github.com/FunkinCrew/Funkin/pull/6036)
- Numeric values in the Options Menu are now rounded to prevent near-zero precision errors. ([934bce7](https://github.com/FunkinCrew/Funkin/commit/934bce7bdc5780e09623d845eda97a27c0ad0ecf)) - by @Starexify in [#6225](https://github.com/FunkinCrew/Funkin/pull/6225)
- The Test/Offset Calibration menu no longer starts playing after exiting the Lag Adjustment menu. ([23a38f7](https://github.com/FunkinCrew/Funkin/commit/23a38f76f9c79c5fab5b9af5e2d98e51597e05a8)) - by @VioletSnowLeopard in [#6088](https://github.com/FunkinCrew/Funkin/pull/6088)
- [MOBILE] The Back button now fades out when entering a Week in Story Mode. ([1ebce74](https://github.com/FunkinCrew/Funkin/commit/1ebce748d4e3306b4464a7bf82fab56bcb427a62)) - by @VirtuGuy in [#6255](https://github.com/FunkinCrew/Funkin/pull/6255)
- The Freeplay difficulty can now only be changed in one direction at a time. ([95af46c](https://github.com/FunkinCrew/Funkin/commit/95af46cbf72291d9d3226df91ed17f1aa3d3839f)) - by @Starexify in [#6262](https://github.com/FunkinCrew/Funkin/pull/6262)
- Pico's backing card now renders properly in widescreen aspect ratios. ([aa29b3d](https://github.com/FunkinCrew/funkin.assets/commit/aa29b3dcc1ee2f8e89d2622c480b2e997410d57d)) - by @VirtuGuy in [funkin.assets#297](https://github.com/FunkinCrew/funkin.assets/pull/297)
- The album titles in Freeplay are now positioned consistently. ([ed8cca4](https://github.com/FunkinCrew/funkin.assets/commit/ed8cca4694a098ae6aa56c4d2a231587edb9688c)) - by @Donothan73 in [funkin.assets#257](https://github.com/FunkinCrew/funkin.assets/pull/257)
- Health icons now bop independently from the framerate. ([8e6fa51](https://github.com/FunkinCrew/Funkin/commit/8e6fa51c27ceaa830efec06bb8a52e281a008255)) - by @PurSnake in [#6035](https://github.com/FunkinCrew/Funkin/pull/6035)
- The note resetting animation on song restart is now consistent between Downscroll and Upscroll. ([7b8d657](https://github.com/FunkinCrew/Funkin/commit/7b8d6577eddba568f20c3d23663dded054699090)) - by @FuroYT in [#6220](https://github.com/FunkinCrew/Funkin/pull/6220)
- Offsets are now properly applied to stage props. ([33a020a](https://github.com/FunkinCrew/Funkin/commit/33a020a34bb220f56c0ff9fbad8688e10ca5b44f)) - by @Starexify in [#6224](https://github.com/FunkinCrew/Funkin/pull/6224)
- Notestyles now accept offset values. ([193c443](https://github.com/FunkinCrew/Funkin/commit/193c443bc47c1a7d1481d280a1d0674b920a323c)) - by @NebulaStellaNova in [#6326](https://github.com/FunkinCrew/Funkin/pull/6326)
- The path for a notestyle's countdown sound is now properly loaded as a sound instead of an image. ([dd5ea77](https://github.com/FunkinCrew/Funkin/commit/dd5ea7748b3b1878139998c209aa06db0fa05e0b)) - by @gamerbross in [#6232](https://github.com/FunkinCrew/Funkin/pull/6232)
- The Pause Menu theme no longer plays at maximum volume for a split second when pausing. ([6ba44d6](https://github.com/FunkinCrew/Funkin/commit/6ba44d6cb0c71deb29eb23b82b61f58b2e95193e)) - by @ComedyLost in [#6334](https://github.com/FunkinCrew/Funkin/pull/6334)
- The scrolling text in the Results Screen is now positioned consistently. ([200f798](https://github.com/FunkinCrew/Funkin/commit/200f7984379cb1bd911e49de92be312750893420)) - by @NotHyper-474 in [#6595](https://github.com/FunkinCrew/Funkin/pull/6595)
- The scrolling text in the Results Screen is now FPS-independent. ([8c43f6f](https://github.com/FunkinCrew/Funkin/commit/8c43f6f67a362bde4de4ad8dbd72966d7c4a9f60)) - by @VirtuGuy in [#6476](https://github.com/FunkinCrew/Funkin/pull/6476)
- The Results Screen theme now loops more smoothly. ([592bf1a](https://github.com/FunkinCrew/Funkin/commit/592bf1a9221d4dcef53ae421571eddf78ed631e4)) - by @FuroYT in [#6477](https://github.com/FunkinCrew/Funkin/pull/6477)
- Transition stickers now render properly with the window resized to any aspect ratio. ([4215b1c](https://github.com/FunkinCrew/Funkin/commit/4215b1c72e4391b2176014825885799b81dde2ba)) - by @PurSnake in [#6156](https://github.com/FunkinCrew/Funkin/pull/6156)
- Boyfriend now stands in the same position in the Week 3 and Week 3 Erect stages. ([d2d15f1](https://github.com/FunkinCrew/funkin.assets/commit/d2d15f114256125d53c6331b46415701ae291c1f)) - by @CEliuxJV in [funkin.assets#261](https://github.com/FunkinCrew/funkin.assets/pull/261)
- Pico's game over music now properly loops after his explosion death in 2hot. ([f1a1db0](https://github.com/FunkinCrew/Funkin/commit/f1a1db08347c3f9248fd16909cff06027297ad13)) - by @KoloInDaCrib in [#6216](https://github.com/FunkinCrew/Funkin/pull/6216)
- 2hot now smoothly transitions into the cutscene. ([462b1b6](https://github.com/FunkinCrew/funkin.assets/commit/462b1b6e6e9b622ad8fd15f15a1f39c7dffa4764)) - by @hucks5 in [funkin.assets#286](https://github.com/FunkinCrew/funkin.assets/pull/286)
- Nene now swings her legs during in-game cutscenes. ([6a5d9b2](https://github.com/FunkinCrew/funkin.assets/commit/6a5d9b265732d60da28083fb1c3d7b7b854edf63)) - by @hucks5 in [funkin.assets#289](https://github.com/FunkinCrew/funkin.assets/pull/289)
- Nene's 50 combo animation now renders its heart properly. ([bcd96ee](https://github.com/FunkinCrew/funkin.assets/commit/bcd96eeb78b9b03cf7b24dc956ac3bc36438193e)) - by @VirtuGuy in [funkin.assets#296](https://github.com/FunkinCrew/funkin.assets/pull/296)
- A-Bot (Christmas) now properly looks at the opponent. ([f81cc16](https://github.com/FunkinCrew/funkin.assets/commit/f81cc16c8f99655f76b7e27ce221819798c9c94e)) - by @ComedyLost in [funkin.assets#279](https://github.com/FunkinCrew/funkin.assets/pull/279)
- Nene (Pixel) now raises and lowers her knife properly. ([1d9f50d](https://github.com/FunkinCrew/funkin.assets/commit/1d9f50d405f202e07efd883aeef5d6c1e9eaaa90)) - by @VirtuGuy in [funkin.assets#274](https://github.com/FunkinCrew/funkin.assets/pull/274)
- Pico (Pixel)'s game over theme now plays at the right time. ([e8a4d24](https://github.com/FunkinCrew/funkin.assets/commit/e8a4d2472ca2dc28b03be05953dff3d7b5ec7433)) - by @JackXson-Real and @ComedyLost in [funkin.assets#288](https://github.com/FunkinCrew/funkin.assets/pull/288)
- Pico holding Nene's idle animation no longer lasts too long. ([3483345](https://github.com/FunkinCrew/funkin.assets/commit/348334583746ec45f1c6ab2c5e1715a67038cc5c)) - by @JackXson-Real in [funkin.assets#271](https://github.com/FunkinCrew/funkin.assets/pull/271)
- The tankmen in Stress no longer briefly appear behind the speakers when restarting. ([7f79ad8](https://github.com/FunkinCrew/funkin.assets/commit/7f79ad8c08d66fb909a9382de2c09f0430b756f1)) - by @VirtuGuy in [funkin.assets#298](https://github.com/FunkinCrew/funkin.assets/pull/298)
- Encountering a Set Health Icon event during Minimal playtesting no longer crashes the Chart Editor. ([9f0a498](https://github.com/FunkinCrew/Funkin/commit/9f0a4988a3cc47581916a9750731bb7e63f168f3)) - by @charlesisfeline in [#6481](https://github.com/FunkinCrew/Funkin/pull/6481)
- Typing in Chart Editor text fields no longer triggers bound key functions. ([3b5e589](https://github.com/FunkinCrew/Funkin/commit/3b5e5893c01c2b56036e53626eaea8d41e70f640)) - by @MightyTheArmiddilo in [#6601](https://github.com/FunkinCrew/Funkin/pull/6601)
- Clicking between Chart Editor events no longer converts the second event's dropdown values into those of the first. ([c170c25](https://github.com/FunkinCrew/Funkin/commit/c170c256934ae316a735d38620ad5e24c2f15f4d)) - by @NotHyper-474 in [#6211](https://github.com/FunkinCrew/Funkin/pull/6211)
- Chart Editor note kinds no longer lose their data when selecting multiple notes with different data. ([5a2c364](https://github.com/FunkinCrew/Funkin/commit/5a2c364c507fb4b86a740c3b2476c336496cd729)) - by @Lasercar in [#4248](https://github.com/FunkinCrew/Funkin/pull/4248)
- Placing a note and removing it no longer breaks the cursor in the Chart Editor. ([8cb5ed9](https://github.com/FunkinCrew/Funkin/commit/8cb5ed91baf50b6f78eefc2e50612a01195a2742)) - by @Lasercar in [#4237](https://github.com/FunkinCrew/Funkin/pull/4237)
- Playtesting in the Chart Editor now properly loads song variation scripts. ([055f620](https://github.com/FunkinCrew/Funkin/commit/055f62026ef44a928876d9065cc388da0905e9f1)) - by @PurSnake in [#6405](https://github.com/FunkinCrew/Funkin/pull/6405)
- Playtesting in the Chart Editor no longer sometimes moves on to another song like Story Mode. ([9497378](https://github.com/FunkinCrew/Funkin/commit/9497378600bad44639ff8b3fc92cb329052fd08a)) - by @KoloInDaCrib in [#6192](https://github.com/FunkinCrew/Funkin/pull/6192)
- The cursor is now hidden when playtesting through the Chart Editor. ([0471872](https://github.com/FunkinCrew/Funkin/commit/047187257b6712e61bf05fdebab33c7b3e9ac0f6)) - by @VirtuGuy in [#6137](https://github.com/FunkinCrew/Funkin/pull/6137)
- The Chart Editor note moving sound no longer plays when dragging outside the grid. ([593456e](https://github.com/FunkinCrew/Funkin/commit/593456e53e02c53e71189fdb154e4a3e4f2b3092)) - by @amyspark-ng in [#4157](https://github.com/FunkinCrew/Funkin/pull/4157)
- The Chart Editor music now fades in after a period of silence when enabled. ([960d2f3](https://github.com/FunkinCrew/Funkin/commit/960d2f3cf166400c6fecee212b87961f3df10c2e)) - by @Lasercar in [#4148](https://github.com/FunkinCrew/Funkin/pull/4148)
- The Chart Editor Difficulty toolbox no longer displays the incorrect song name. ([dff2a1e](https://github.com/FunkinCrew/Funkin/commit/dff2a1e487dc20f86ef4f526a259083a7726ea6a)) - by @VirtuGuy in [#6547](https://github.com/FunkinCrew/Funkin/pull/6547)
- Chart Editor notifications now display a bit higher. ([9e645db](https://github.com/FunkinCrew/Funkin/commit/9e645db0074424ffb2de42b27afb3adaff55de62)) - by @MightyTheArmiddilo in [#6445](https://github.com/FunkinCrew/Funkin/pull/6445)
- The Yes and No text in the Chart Editor exit confirmation prompt buttons now always render properly. ([4c0d925](https://github.com/FunkinCrew/Funkin/commit/4c0d92507ae0b81a744d223e5292bf8d27013abd)) - by @NotHyper-474 in [#6173](https://github.com/FunkinCrew/Funkin/pull/6173)
- Exiting the Chart Editor now consistently saves your audio preferences. ([2f77ccf](https://github.com/FunkinCrew/Funkin/commit/2f77ccfc6e93fabef27d3e06447f9f6edd8e840d)) - by @VioletSnowLeopard in [#6127](https://github.com/FunkinCrew/Funkin/pull/6127)
- The Chart Editor and Stage Editor backup window now properly retrieves the most recently edited backup. ([a8dec0c](https://github.com/FunkinCrew/Funkin/commit/a8dec0cd7053f88865757f30a7d9f0ca5eb0fb02)) - by @Lasercar in [#6119](https://github.com/FunkinCrew/Funkin/pull/6119)
- The Chart Editor backup dialog no longer appears if the latest backup is deleted. ([bc1d36d](https://github.com/FunkinCrew/Funkin/commit/bc1d36ddeab81a790e3604598248a9b2fe869cac)) - by @KoloInDaCrib in [#6027](https://github.com/FunkinCrew/Funkin/pull/6027)
- Switching between Animation and Spritesheet mode no longer crashes the Animation Editor. ([a61016d](https://github.com/FunkinCrew/Funkin/commit/a61016d2dfc2ac6a50aae230969c036f0753cfaa)) - by @VirtuGuy in [#6114](https://github.com/FunkinCrew/Funkin/pull/6114)
- The Animation Editor no longer displays a missing object icon next to the dropdown menus. ([b30ea36](https://github.com/FunkinCrew/Funkin/commit/b30ea36fcc982ce4ac0be9143fd4701e180fc684)) - by @KoloInDaCrib in [#6177](https://github.com/FunkinCrew/Funkin/pull/6177)
- The Animation Editor onion skin now properly accounts for character scale and offsets. ([a76a868](https://github.com/FunkinCrew/Funkin/commit/a76a868cb6a27c1c76d09177b90cf6df3df29d17)) - by @VirtuGuy in [#6123](https://github.com/FunkinCrew/Funkin/pull/6123)
- The debug cursor is now properly hidden when exiting the Animation Editor. ([f2f6b4c](https://github.com/FunkinCrew/Funkin/commit/f2f6b4c8591e1ab4a8c6f0ed998d6d25d7cc9f43)) - by @VirtuGuy in [#6112](https://github.com/FunkinCrew/Funkin/pull/6112)
- Plenty more tiny fixes.
## New Contributors for 0.8.0
* @Eviethecoder made their first contribution in [#4036](https://github.com/FunkinCrew/Funkin/pull/4036)
* @FuroYT made their first contribution in [#5908](https://github.com/FunkinCrew/Funkin/pull/5908)
* @HeroEyad made their first contribution in [#6049](https://github.com/FunkinCrew/Funkin/pull/6049)
* @hucks5 made their first contribution in [#6196](https://github.com/FunkinCrew/Funkin/pull/6196)
* @Starexify made their first contribution in [#6225](https://github.com/FunkinCrew/Funkin/pull/6225)
* @NebulaStellaNova made their first contribution in [#6326](https://github.com/FunkinCrew/Funkin/pull/6326)
* @MightyTheArmiddilo made their first contribution in [#6445](https://github.com/FunkinCrew/Funkin/pull/6445)
* @Donothan73 made their first contribution in [funkin.assets#257](https://github.com/FunkinCrew/funkin.assets/pull/257)
* @CEliuxJV made their first contribution in [funkin.assets#261](https://github.com/FunkinCrew/funkin.assets/pull/261)
* @roma-perec-bp made their first contribution in [funkin.assets#273](https://github.com/FunkinCrew/funkin.assets/pull/273)
* @ComedyLost made their first contribution in [funkin.assets#279](https://github.com/FunkinCrew/funkin.assets/pull/279)
## [0.7.5] - 2025-09-19
### Added
- Added an `onStateCreate` event to the Module class.
### Changed
- Gave the debug display a huge upgrade!
- Choose from three settings in the Preferences Menu: Off/Simple/Advanced
- New “Task memory” counter shows how much RAM the game is using on your computer.
- Advanced mode displays graphs for FPS and RAM usage.
- Hot reloading now works in scripted states.
- Made a few more classes scriptable.
- FunkinVideoSprite
- FlxStrip
- FlxTypedGroup
- Strumline
- StageProp​​
- The Random capsule now brings up the option to select a random instrumental. ([0d5c6ac](https://github.com/FunkinCrew/Funkin/commit/0d5c6ac9632ee3bf3977b9bd369daab9f2d95fea)) - by @VioletSnowLeopard in [#5891](https://github.com/FunkinCrew/Funkin/pull/5891)
- The Character Select menu now returns to Freeplay after pressing a back button. ([250d218](https://github.com/FunkinCrew/Funkin/commit/250d2188b9cb2c92d283d8b12ed7de0286324592)) - by @MrMadera in [#5887](https://github.com/FunkinCrew/Funkin/pull/5887)
- Pressing the Shift key in the Credits menu now pauses scrolling. ([479036a](https://github.com/FunkinCrew/Funkin/commit/479036a4008fe1c54adc970c0a476fae6b1b3e94)) - by @JVNpixels in [#2924](https://github.com/FunkinCrew/Funkin/pull/2924)
- Chart Editor and Stage Editor backup files are now given relevant filenames. ([b31be10](https://github.com/FunkinCrew/Funkin/commit/b31be1043ccd1e906a656913b947bae160b451fe)) - by @Lasercar in [#4118](https://github.com/FunkinCrew/Funkin/pull/4118)
- The Animation Editor's onion skin now properly accounts for offsets and displays the idle animation. ([0fd6f5b](https://github.com/FunkinCrew/Funkin/commit/0fd6f5b3b7bd115a3b6821096daa929c220bd235)) - by @KoloInDaCrib in [#5810](https://github.com/FunkinCrew/Funkin/pull/5810)
### Fixed
- The Freeplay new rank animation now shows the new rank slamming onto the old one.
- Freeplay capsules with a Gold Perfect rank no longer become randomly offset when scrolling.
- The Pico Freeplay DJ’s new rank animation now properly renders its lighting.
- Freeplay song previews no longer play replaced or deleted audio files.
- The strumline no longer disappears after re-entering Lag Adjustment.
- Fixed various issues with Week 6 dialogue boxes, portraits, and audio.
- [MOBILE] The strumlines are no longer offset to the left in Blazin’.
- The preloader no longer disables fullscreen mode after completion.
- Attempting to log in to Newgrounds during an ongoing login attempt no longer crashes the game. ([4e572b1](https://github.com/FunkinCrew/Funkin/commit/4e572b171fe901a84f12b4566963fd3c1f4ae6fb)) - by @KoloInDaCrib in [#5922](https://github.com/FunkinCrew/Funkin/pull/5922)
- Added null-safety to Leaderboards and Medals to prevent a rare crash. ([88fc5f9](https://github.com/FunkinCrew/Funkin/commit/88fc5f9ed684e287d7411a59fb4fdf170acfed5a)) - by @NotHyper-474 in [#5926](https://github.com/FunkinCrew/Funkin/pull/5926)
- Freeplay ranks no longer pop out when scrolling through song capsules. ([747446e](https://github.com/FunkinCrew/Funkin/commit/747446e2e420449f422fbc69aafda6128ac8d16e)) - by @VioletSnowLeopard in [#5822](https://github.com/FunkinCrew/Funkin/pull/5822)
- The Freeplay intro no longer plays when returning from a sticker transition. ([63ec749](https://github.com/FunkinCrew/Funkin/commit/63ec749c7a3c90d529e9e8f2f3ed1c8913e8f9a1)) - by @PurSnake in [#5942](https://github.com/FunkinCrew/Funkin/pull/5942)
- Hot-reloading with F5 after earning a rank no longer crashes Freeplay. ([79e8b5d](https://github.com/FunkinCrew/Funkin/commit/79e8b5d71e052953b77b81b66e0a83a7bbeeeeed)) - by @KoloInDaCrib in [#5971](https://github.com/FunkinCrew/Funkin/pull/5971)
- The Main Menu music now fades in consistently when exiting from Freeplay to the Title Screen. ([a10bbf6](https://github.com/FunkinCrew/Funkin/commit/a10bbf60acbbeadfb5a91930745ef4fd338b1a24)) - by @VirtuGuy in [#5855](https://github.com/FunkinCrew/Funkin/pull/5855)
- The game now falls back to default audio files when files specified by the metadata don't exist. ([b5357ac](https://github.com/FunkinCrew/Funkin/commit/b5357acf98bde81a4121a3723061f7c559a0640f)) - by @VioletSnowLeopard in [#5954](https://github.com/FunkinCrew/Funkin/pull/5954)
- The cutscene pause menu no longer persists across songs after exiting a video cutscene. ([c2305f3](https://github.com/FunkinCrew/Funkin/commit/c2305f32f0147f6326e8665d21a6b8a793f45033)) - by @KoloInDaCrib in [#5880](https://github.com/FunkinCrew/Funkin/pull/5880)
- Mashing to skip the Eggnog Erect cutscene no longer crashes the game. ([5ca3c6a](https://github.com/FunkinCrew/funkin.assets/commit/5ca3c6a5745b61a457db149c289784c08e560ee9)) - by @VioletSnowLeopard in [funkin.assets#258](https://github.com/FunkinCrew/funkin.assets/pull/258)
- Stress (Pico Mix) no longer lag spikes when loading in the background tankmen. ([6701405](https://github.com/FunkinCrew/funkin.assets/commit/670140571239ca08c1ae1c5286773d99c2875c2d)) - by @NotHyper-474 and @KoloInDaCrib in [funkin.assets#242](https://github.com/FunkinCrew/funkin.assets/pull/242)
- Stress (Pico Mix) no longer lag spikes when Tankman changes sprites. ([aa5956a](https://github.com/FunkinCrew/Funkin/commit/aa5956a30bdc47d53ebebb4b61a7ebb3f04c7dc6)) - by @NotHyper-474 in [#5939](https://github.com/FunkinCrew/Funkin/pull/5939)
- The bricks in Stress (Pico Mix) no longer move after restarting the song. ([9cfee03](https://github.com/FunkinCrew/Funkin/commit/9cfee0398a057567f80ad68ff6052964da63af09)) - by @VirtuGuy in [#5875](https://github.com/FunkinCrew/Funkin/pull/5875)
- Pico now properly shoots after restarting Stress. ([b0ef0d3](https://github.com/FunkinCrew/funkin.assets/commit/b0ef0d352c199e93903652a95ffdf16a3dc2d569)) - by @7oltan in [funkin.assets#254](https://github.com/FunkinCrew/funkin.assets/pull/254)
- A-Bot's visualizer now zeroes out when restarting the song. ([ba3a8b0](https://github.com/FunkinCrew/funkin.assets/commit/ba3a8b0cbbf785a0729dcf85e25b6e59c0c12657)) - by @KoloInDaCrib in [funkin.assets#246](https://github.com/FunkinCrew/funkin.assets/pull/246)
- Notekinds now properly receive dispatched events last in PlayState. ([89b2b7f](https://github.com/FunkinCrew/Funkin/commit/89b2b7fcc3130db0af1f782594af72ae1f4ce320)) - by @Az989YT in [#5936](https://github.com/FunkinCrew/Funkin/pull/5936)
- Text from .json files is now properly sanitized before it is parsed. ([80d7ace](https://github.com/FunkinCrew/Funkin/commit/80d7ace714efb729cb8f1b23b61fdab4ac655275)) - by @KoloInDaCrib in [#5108](https://github.com/FunkinCrew/Funkin/pull/5108)
- Fixed a crash when exiting the Chart Editor after interacting with a text field. ([f85f2f0](https://github.com/FunkinCrew/Funkin/commit/f85f2f03d97fc1332275f088ddce2b4056f0ded2)) - by @NotHyper-474 in [#5992](https://github.com/FunkinCrew/Funkin/pull/5992)
- Fixed some issues with variation handling in the Chart Editor. ([970ccf2](https://github.com/FunkinCrew/Funkin/commit/970ccf2f4a9b5802b70d0b3c269c8b9e541d53f2)) - by @NotHyper-474 in [#4396](https://github.com/FunkinCrew/Funkin/pull/4396)
- Audio no longer plays when dragging a playhead in the Chart Editor. ([2d95754](https://github.com/FunkinCrew/Funkin/commit/2d95754d492da06920d56de2b2bcbb9b81182af0)) - by @Lasercar in [#4140](https://github.com/FunkinCrew/Funkin/pull/4140)
- The Chart Editor Offsets window song preview no longer continues playing during playtesting. ([11eb406](https://github.com/FunkinCrew/Funkin/commit/11eb4060d03b18a2f3604297eb9a24e54077fe3c)) - by @PurSnake and @Lasercar in [#5973](https://github.com/FunkinCrew/Funkin/pull/5973)
- The fullscreen keybind no longer triggers when typing in a field. ([4ebd00b](https://github.com/FunkinCrew/Funkin/commit/4ebd00bc06a5c94e935f557ba14ba9729faec104)) - by @lemz1 in [#4131](https://github.com/FunkinCrew/Funkin/pull/4131)
- The Stage Editor new stage menu can no longer be opened multiple times at once. ([47b1cee](https://github.com/FunkinCrew/Funkin/commit/47b1cee6d5ee37cd18146ca8098da85610c6b544)) - by @VioletSnowLeopard in [#5873](https://github.com/FunkinCrew/Funkin/pull/5873)
- Adjusted the save hotkeys in the Animation Editor to be usable on Windows. ([8ead5ee](https://github.com/FunkinCrew/Funkin/commit/8ead5eeb0840756a843fdcf0f3f347b087e7182d)) - by @VioletSnowLeopard in [#5974](https://github.com/FunkinCrew/Funkin/pull/5974)
- A few more tiny fixes.
## New Contributors for 0.7.5
* @Az989YT made their first contribution in [#5936](https://github.com/FunkinCrew/Funkin/pull/5936)
## [0.7.4] - 2025-09-01
### Added
@@ -47,7 +451,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved properties support, behaving more like regular Haxe. (Thanks NotHyper-474!)
- Fixed a null object reference when evaluating static variables with no explicit values. (Thanks NotHyper-474!)
- Fixed a null object reference when iterating over a null variable. (Thanks NotHyper-474!)
- Scripts are now automatically cleared when Polymod is initialized. (Thanks KoloInDaKrib!)
- Scripts are now automatically cleared when Polymod is initialized. (Thanks KoloInDaCrib!)
- Polished every chart in the game!
- The lengths of hold notes have been adjusted to match the vocals as closely as possible, across all variations and difficulties.
- Easy and Normal charts from Week 1 to Week 3 have been adjusted for more consistency between the player and opponent.
+1 -1
View File
@@ -197,7 +197,7 @@ You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/fu
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020-2024 The Funkin' Crew Inc.
Copyright 2020-2026 The Funkin' Crew Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+4 -4
View File
@@ -1,4 +1,4 @@
<div align='center'><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Friday_Night_Funkin%27_logo.svg/2560px-Friday_Night_Funkin%27_logo.svg.png" width="800">
<div align='center'><img src="docs/readme_images/FNF_logo.png" width="800">
<h2>Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for <a href="https://ldjam.com/events/ludum-dare/47">Ludum Dare 47.</a></h2>
@@ -14,8 +14,8 @@ This game was made with love to Newgrounds and its community. Extra love to Tom
<div align='center'>
<table>
<tr>
<td><img src="https://fridaynightfunkin.wiki.gg/images/d/d7/Title_Card.gif" alt="Title Screen" width="350"/></td>
<td><img src="https://fridaynightfunkin.wiki.gg/images/9/99/Menu.png" alt="Main Menu" width="350"/></td>
<td><img src="docs/readme_images/Title_Card.gif" alt="Title Screen" width="350"/></td>
<td><img src="docs/readme_images/Menu.png" alt="Main Menu" width="350"/></td>
</tr>
</table>
</div>
@@ -63,4 +63,4 @@ Full credits can be found in-game, or in the `credits.json` file which is locate
## Special Thanks
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
+1 -1
Submodule art updated: 094ff10919...429ab728b1
+1 -1
Submodule assets updated: e6f463fb64...5be03b9cd8
+89 -18
View File
@@ -5,22 +5,6 @@
"blocksize": "10x10",
"colorprofile": "cl",
"custom": [
{
"asset": "asset/shared/images/resultScreen/results-pico/resultsPERFECT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "asset/shared/images/resultScreen/results-pico/resultsGREAT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "asset/shared/images/resultScreen/results-pico/resultsGOOD/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "asset/shared/images/resultScreen/results-pico/resultsSHIT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/preload/images/NOTE_assets.png",
"blocksize": "4x4"
@@ -37,10 +21,42 @@
"asset": "assets/preload/images/alphabet.png",
"blocksize": "4x4"
},
{
"asset": "assets/preload/images/charSelect/gfChill/spritemap1.png",
"blocksize": "6x6"
},
{
"asset": "assets/shared/images/NOTE_hold_assets.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/characters/nene-dark/spritemap1.png",
"blocksize": "6x6"
},
{
"asset": "assets/shared/images/characters/otis/muzzle-flashes/otis_flashes.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/characters/sserafim/chaewon/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/characters/pico-dark/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/characters/pico/basic-animations/spritemap1.png",
"blocksize": "6x6"
},
{
"asset": "assets/shared/images/characters/pico/explosion-death/spritemap1.png",
"blocksize": "8x8"
},
{
"asset": "assets/shared/images/characters/pico/playable-animations/spritemap1.png",
"blocksize": "6x6"
},
{
"asset": "assets/shared/images/holdCoverBlue.png",
"blocksize": "4x4"
@@ -77,6 +93,30 @@
"asset": "assets/shared/images/notes.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/resultScreen/results-pico/resultsGOOD/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/resultScreen/results-pico/resultsGREAT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/resultScreen/results-pico/resultsPERFECT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/resultScreen/results-pico/resultsSHIT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/sserafim/images/sserafim-lipsync-yunjin/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/sserafim/images/sserafim-lipsync/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/week1/image/erect/bg.png",
"blocksize": "8x8"
@@ -85,6 +125,10 @@
"asset": "assets/week1/image/erect/lights.png",
"blocksize": "6x6"
},
{
"asset": "assets/shared/images/characters/pico/death/spritemap1.png",
"blocksize": "6x6"
},
{
"asset": "assets/week1/images/erect/crowd.png",
"blocksize": "8x8"
@@ -92,6 +136,14 @@
{
"asset": "assets/week1/images/erect/server.png",
"blocksize": "8x8"
},
{
"asset": "assets/week7/images/erect/rando/spritemap1.png",
"blocksize": "6x6"
},
{
"asset": "assets/week7/images/erect/sniper/spritemap1.png",
"blocksize": "6x6"
}
],
"excludes": [
@@ -101,23 +153,42 @@
"assets/preload/images/icons/",
"assets/preload/images/soundtray/",
"assets/preload/images/stageBuild/",
"assets/preload/images/titleEnter.png",
"assets/preload/images/titleEnter_mobile.png",
"assets/preload/images/storymenu/titles/",
"assets/preload/images/title-screen-text/spritemap1.png",
"assets/preload/images/title-screen-text-mobile/spritemap1.png",
"assets/preload/images/ui/popup/pixel/*",
"assets/shared/images/characters/abotPixel/*",
"assets/shared/images/characters/bfPixel.png",
"assets/shared/images/characters/bfPixelsDEAD.png",
"assets/shared/images/characters/gf-christmas/spritemap1.png",
"assets/shared/images/characters/gf-dark/spritemap1.png",
"assets/shared/images/characters/gf/spritemap1.png",
"assets/shared/images/characters/gfCar/spritemap1.png",
"assets/shared/images/characters/gfPixel.png",
"assets/shared/images/characters/nene/spritemap1.png",
"assets/shared/images/characters/nenePixel/*",
"assets/shared/images/characters/pico-holding-nene/spritemap1.png",
"assets/shared/images/characters/picoPixel/*",
"assets/shared/images/characters/senpai.png",
"assets/shared/images/characters/spirit.png",
"assets/shared/images/characters/sserafim/eunchae/spritemap1.png",
"assets/shared/images/characters/sserafim/kazuha/spritemap1.png",
"assets/shared/images/characters/sserafim/sserafim-gf/spritemap1.png",
"assets/shared/images/characters/sserafim/yunjin/spritemap1.png",
"assets/shared/images/characters/sserafim/sakura/spritemap1.png",
"assets/shared/images/healthBar.png",
"assets/shared/images/resultScreen/*",
"assets/shared/images/resultScreen/clearPercent/",
"assets/shared/images/resultScreen/rankText/",
"assets/shared/images/resultScreen/results-bf/resultsEXCELLENT/spritemap1.png",
"assets/shared/images/resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD.png",
"assets/shared/images/resultScreen/results-bf/resultsGREAT/gf/spritemap1.png",
"assets/shared/images/resultScreen/results-bf/resultsSHIT/gf/spritemap1.png",
"assets/shared/images/ui/chart-editor/",
"assets/shared/images/ui/countdown/pixel/*",
"assets/sserafim/images/cutscene/cutsceneMain/spritemap1.png",
"assets/sserafim/images/cutscene/ref.png",
"assets/sserafim/images/ref.png",
"assets/week1/*",
"assets/week1/images/erect/brightLightSmall.png",
"assets/week1/images/erect/lightAbove.png",
+335 -167
View File
@@ -1,127 +1,131 @@
{
"extendsConfigPath": "",
"defineCombinations": [],
"defaultSeverity": "INFO",
"baseDefines": [],
"version": 1,
"exclude": {},
"defaultSeverity": "INFO",
"extendsConfigPath": "",
"numberOfCheckerThreads": 5,
"baseDefines": [],
"defineCombinations": [],
"checks": [
{
"props": {},
"comment": "If enabled, prevents using anonymous types inline without creating a typedef for them.",
"props": { "severity": "INFO" },
"type": "Anonymous"
},
{
"comment": "If enabled, mandates spacing around array access syntax.",
"props": {
"spaceBefore": false,
"spaceInside": false
"spaceInside": false,
"severity": "INFO"
},
"type": "ArrayAccess"
},
{
"props": {},
"comment": "If enabled, mandates array literals be defined with [] instead of 'new'.",
"props": { "severity": "WARNING" },
"type": "ArrayLiteral"
},
{
"comment": "If enabled, mandates specific syntax for the use of array literals.",
"props": {
"allowSingleArgParens": true,
"allowReturn": true,
"allowFunction": true,
"allowCurlyBody": true
"allowCurlyBody": true,
"severity": "INFO"
},
"type": "ArrowFunction"
},
{
"props": {
"avoidIdentifiers": []
},
"comment": "Specify a list of variable and function names which are not allowed and should be renamed. Doesn't support regex.",
"props": { "avoidIdentifiers": [], "severity": "WARNING" },
"type": "AvoidIdentifier"
},
{
"props": {},
"comment": "If enabled, prevents imports like 'import flixel.*' from being used.",
"props": { "severity": "INFO" },
"type": "AvoidStarImport"
},
{
"props": {
"severity": "IGNORE"
},
"type": "AvoidTernaryOperator"
},
{
"props": {},
"comment": "If enabled, prevents conditional compilation from crossing blocks. This prevents the case where conditional compilation could potentially create invalid syntax.",
"props": { "severity": "INFO" },
"type": "BlockBreakingConditional"
},
{
"comment": "Specifies a specific naming format for the parameter of a catch block.",
"props": {
"format": "^(e|t|ex|[a-z][a-z][a-zA-Z]+)$"
"format": "^(e|t|ex|[a-z][a-z][a-zA-Z]+)$",
"severity": "INFO"
},
"type": "CatchParameterName"
},
{
"comment": "If enabled, flags when large blocks of similar code should be extracted to a function.",
"props": {
"thresholdSimilar": 120,
"severityIdentical": "WARNING",
"thresholdIdentical": 60
"thresholdIdentical": 60,
"severityIdentical": "IGNORE"
},
"type": "CodeSimilarity"
},
{
"props": {},
"comment": "If enabled, flags Haxe code that is commented out. Unused code should be removed and accessed via version control later if needed.",
"props": { "severity": "WARNING" },
"type": "CommentedOutCode"
},
{
"comment": "If enabled, flags when conditional compilation blocks are not properly aligned. NOTE:Disabled because it's kinda broken.",
"props": {
"severity": "IGNORE",
"policy": "aligned",
"allowSingleline": true
"allowSingleline": true,
"severity": "IGNORE"
},
"type": "ConditionalCompilation"
},
{
"comment": "Specifies a specific naming format for constant (static) identifiers.",
"props": {
"ignoreExtern": true,
"format": "^?:_[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
"tokens": ["INLINE", "NOTINLINE"]
"tokens": ["INLINE", "NOTINLINE"],
"severity": "INFO"
},
"type": "ConstantName"
},
{
"comment": "Calculates the complexity of methods using the McCabe metric, and throws warnings at certain thresholds. Indicates that a function should be refactored or split up.",
"props": {
"thresholds": [
{
"complexity": 20,
"severity": "WARNING"
},
{
"complexity": 25,
"severity": "ERROR"
}
{ "complexity": 20, "severity": "WARNING" },
{ "complexity": 25, "severity": "ERROR" }
],
"severity": "IGNORE"
},
"type": "CyclomaticComplexity"
},
{
"props": {},
"comment": "If enabled, the default case of a switch statement must be last.",
"props": { "severity": "INFO" },
"type": "DefaultComesLast"
},
{
"props": {
"lineStyle": "onestar",
"startStyle": "twostars",
"severity": "IGNORE"
},
"comment": "Specifies the format for doc-style comments ().",
"props": { "lineStyle": "onestar", "startStyle": "ignore" },
"type": "DocCommentStyle"
},
{
"props": {},
"comment": "If enabled, prevents the use of the Dynamic type.",
"props": { "severity": "IGNORE" },
"type": "Dynamic"
},
{
"props": {},
"comment": "If enabled, ensures ~/.../ is used instead of 'new EReg()'",
"props": { "severity": "WARNING" },
"type": "ERegLiteral"
},
{
"comment": "If enabled, enforces specific syntax on empty blocks (require only one line, etc.)",
"props": {
"tokens": [
"CLASS_DEF",
@@ -138,11 +142,13 @@
"TRY",
"CATCH"
],
"option": "empty"
"option": "empty",
"severity": "IGNORE"
},
"type": "EmptyBlock"
},
{
"comment": "If enabled, flags when consecutive empty lines are used.",
"props": {
"requireEmptyLineAfterPackage": true,
"requireEmptyLineAfterInterface": true,
@@ -150,17 +156,18 @@
"allowEmptyLineAfterSingleLineComment": true,
"max": 1,
"requireEmptyLineAfterClass": true,
"allowEmptyLineAfterMultiLineComment": true
"allowEmptyLineAfterMultiLineComment": true,
"severity": "IGNORE"
},
"type": "EmptyLines"
},
{
"props": {
"enforceEmptyPackage": true
},
"comment": "If enabled, mandates the use of 'package;' on root level modules.",
"props": { "enforceEmptyPackage": true, "severity": "WARNING" },
"type": "EmptyPackage"
},
{
"comment": "If enabled, provides advanced checking for when consecutive empty lines are used.",
"props": {
"none": [],
"upto": [],
@@ -169,11 +176,13 @@
"max": 1,
"ignore": [],
"atleast": [],
"skipSingleLineTypes": true
"skipSingleLineTypes": true,
"severity": "IGNORE"
},
"type": "ExtendedEmptyLines"
},
{
"comment": "If enabled, mandates documentation comments for public fields.",
"props": {
"requireParams": true,
"fieldType": "BOTH",
@@ -181,25 +190,33 @@
"ignoreOverride": true,
"tokens": ["ABSTRACT_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF"],
"modifier": "PUBLIC",
"excludeNames": ["new", "toString"]
"excludeNames": ["new", "toString"],
"severity": "INFO"
},
"type": "FieldDocComment"
},
{
"props": {
"max": 1000,
"ignoreEmptyLines": true,
"severity": "IGNORE"
},
"comment": "If enabled, warns if files exceed a certain number of lines.",
"props": { "max": 1000, "ignoreEmptyLines": true, "severity": "IGNORE" },
"type": "FileLength"
},
{
"props": {
"option": "upperCase"
},
"comment": "If enabled, requires modules match the casing of their file name.",
"type": "FileNameCase",
"props": { "severity": "WARNING" }
},
{
"comment": "If enabled, requires inline/static variables to be declared final.",
"type": "Final",
"props": { "severity": "IGNORE" }
},
{
"comment": "If enabled, requires hexadecimal literals to be in upper case. 0xFFFFFF is valid and 0xffffff is invalid.",
"props": { "option": "upperCase", "severity": "WARNING" },
"type": "HexadecimalLiteral"
},
{
"comment": "If enabled, warns if a local variable or parameter shadows an existing field.",
"props": {
"ignoreSetter": true,
"ignoreFormat": "^(main|run)$",
@@ -208,108 +225,167 @@
"type": "HiddenField"
},
{
"props": {
"character": " ",
"severity": "IGNORE"
},
"comment": "If enabled, mandates indentation.",
"props": { "character": "", "severity": "IGNORE" },
"type": "Indentation"
},
{
"comment": "If enabled, mandates characters used for indentation.",
"props": {
"character": "space",
"severity": "INFO",
"severity": "IGNORE",
"ignorePattern": "^$"
},
"type": "IndentationCharacter"
},
{
"props": {
"ignoreReturnAssignments": true,
"severity": "WARNING"
},
"comment": "If enabled, warns about assignments performed within another expression, like 'if (a=b){}'",
"props": { "ignoreReturnAssignments": true, "severity": "WARNING" },
"type": "InnerAssignment"
},
{
"comment": "If enabled, enforces that interfaces should not contain properties.",
"props": {
"allowProperties": false,
"allowMarkerInterfaces": true
"allowMarkerInterfaces": true,
"severity": "WARNING"
},
"type": "Interface"
},
{
"comment": "If enabled, enforces a newline before the left curly brace of a block.",
"props": {
"option": "nl",
"ignoreEmptySingleline": true,
"tokens": [
"CLASS_DEF",
"ENUM_DEF",
"ABSTRACT_DEF",
"TYPEDEF_DEF",
"INTERFACE_DEF",
"FUNCTION",
"FOR",
"IF",
"WHILE",
"SWITCH",
"TRY",
"CATCH",
"REIFICATION",
"ARRAY_COMPREHENSION"
],
"severity": "INFO"
},
"type": "LeftCurly"
},
{
"comment": "If enabled, enforces the left curly brace occurs on the end of the line, without a newline.",
"props": {
"option": "eol",
"ignoreEmptySingleline": true,
"tokens": ["OBJECT_DECL"],
"severity": "IGNORE"
},
"type": "LeftCurly"
},
{
"comment": "If enabled, warns when lines of code exceed a certain length.",
"props": {
"max": 160,
"ignorePattern": "^$"
"ignorePattern": "^(\\s*['\"][^'\"]*['\"].*)$",
"severity": "INFO"
},
"type": "LineLength"
},
{
"comment": "If enabled, mandates a specific naming convention for listener methods.",
"props": {
"listeners": ["addEventListener", "addListener", "on", "once"],
"format": "^_?[a-z][a-zA-Z0-9]*$"
"format": "^_?[a-z][a-zA-Z0-9]*$",
"severity": "IGNORE"
},
"type": "ListenerName"
},
{
"comment": "Specifies a specific naming format for local variables.",
"props": {
"ignoreExtern": true,
"format": "^[a-z][a-zA-Z0-9]*$",
"tokens": []
"tokens": [],
"severity": "INFO"
},
"type": "LocalVariableName"
},
{
"props": {
"ignoreNumbers": [-1, 0, 1, 2],
"severity": "IGNORE"
},
"comment": "If enabled, warns when unlabelled numeric values get used. Use named constants instead to improve code clarity.",
"props": { "ignoreNumbers": [-1, 0, 1, 2], "severity": "INFO" },
"type": "MagicNumber"
},
{
"props": {
"format": "^[A-Z][a-zA-Z0-9]*$",
"tokens": ["ABSTRACT"]
},
"type": "MemberName"
},
{
"comment": "Specifies a specific naming format for public abstract member fields.",
"props": {
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
"tokens": ["ABSTRACT"]
"tokens": ["PUBLIC", "ABSTRACT"]
},
"type": "MemberName"
},
{
"comment": "Specifies a specific naming format for private abstract member fields.",
"props": {
"ignoreExtern": true,
"format": "^[_a-z][a-zA-Z0-9]*$",
"tokens": ["PUBLIC", "PRIVATE", "CLASS", "TYPEDEF"]
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
"tokens": ["PRIVATE", "ABSTRACT"]
},
"type": "MemberName"
},
{
"comment": "Specifies a specific naming format for public class member fields.",
"props": {
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
"tokens": ["PUBLIC", "CLASS"]
},
"type": "MemberName"
},
{
"comment": "Specifies a specific naming format for private class member fields.",
"props": {
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
"tokens": ["PRIVATE", "CLASS"]
},
"type": "MemberName"
},
{
"comment": "Specifies a specific naming format for enum member fields.",
"props": {
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
"tokens": ["ENUM"]
},
"type": "MemberName"
},
{
"comment": "Specifies a specific naming format for typedef member fields.",
"props": {
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
"tokens": ["TYPEDEF"]
},
"type": "MemberName"
},
{
"comment": "If enabled, enforces that modules should not exceed a certain number of methods.",
"props": {
"maxPrivate": 100,
"maxPublic": 100,
"maxTotal": 100
"maxTotal": 100,
"severity": "IGNORE"
},
"type": "MethodCount"
},
{
"props": {
"max": 50,
"ignoreEmptyLines": true,
"severity": "IGNORE"
},
"comment": "If enabled, enforces that methods should not exceed a certain number of lines of code, excluding empty lines.",
"props": { "max": 50, "ignoreEmptyLines": true, "severity": "IGNORE" },
"type": "MethodLength"
},
{
"comment": "Specifies a specific naming format for method names.",
"props": {
"ignoreExtern": true,
"format": "^[a-z][a-zA-Z0-9]*$",
@@ -319,6 +395,7 @@
"type": "MethodName"
},
{
"comment": "Specifies a specific order for modifiers on fields.",
"props": {
"modifiers": [
"PUBLIC_PRIVATE",
@@ -334,57 +411,64 @@
"type": "ModifierOrder"
},
{
"comment": "If enabled, complains when a specific string literal is used in the same file multiple times. Use a named constant instead.",
"props": {
"minLength": 2,
"ignore": "^\\s+$",
"allowDuplicates": 5
"allowDuplicates": 5,
"severity": "IGNORE"
},
"type": "MultipleStringLiterals"
},
{
"props": {},
"comment": "If enabled, complains when a multiple variables are declared in a single expression. Use individual lines.",
"props": { "severity": "WARNING" },
"type": "MultipleVariableDeclarations"
},
{
"comment": "If enabled, mandates the use of braces on specific block types, unless they fit on a single line.",
"props": {
"allowSingleLineStatement": true,
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
"tokens": [
"FUNCTION",
"FOR",
"IF",
"ELSE_IF",
"WHILE",
"DO_WHILE",
"CATCH"
],
"severity": "INFO"
},
"type": "NeedBraces"
},
{
"props": {
"max": 3,
"severity": "IGNORE"
},
"comment": "If enabled, complains when control flow statements are nested too deep (indicating high complexity).",
"props": { "max": 3, "severity": "IGNORE" },
"type": "NestedControlFlow"
},
{
"props": {
"max": 1,
"severity": "IGNORE"
},
"comment": "If enabled, complains when 'for' loops are nested too deep (indicating high complexity).",
"props": { "max": 1, "severity": "IGNORE" },
"type": "NestedForDepth"
},
{
"props": {
"max": 4
},
"comment": "If enabled, complains when 'if' statements are nested too deep (indicating high complexity).",
"props": { "max": 4, "severity": "IGNORE" },
"type": "NestedIfDepth"
},
{
"props": {
"max": 1
},
"comment": "If enabled, complains when try/catch statements are nested too deep (indicating high complexity).",
"props": { "max": 1, "severity": "INFO" },
"type": "NestedTryDepth"
},
{
"props": {
"option": "questionMark"
},
"comment": "If enabled, enforces ?value rather than value = null.",
"props": { "option": "questionMark", "severity": "INFO" },
"type": "NullableParameter"
},
{
"comment": "If enabled, enforces that operators should be surrounded by whitespace.",
"props": {
"oldFunctionTypePolicy": "none",
"unaryOpPolicy": "none",
@@ -402,6 +486,7 @@
"type": "OperatorWhitespace"
},
{
"comment": "If enabled, enforces that most operators should be after a newline rather than before.",
"props": {
"tokens": [
"+",
@@ -444,54 +529,52 @@
"type": "OperatorWrap"
},
{
"props": {
"tokens": ["="],
"option": "eol"
},
"comment": "If enabled, enforces that assignment operators should be before a newline rather than after.",
"props": { "tokens": ["="], "option": "eol" },
"type": "OperatorWrap"
},
{
"comment": "Specifies a specific naming format for method parameter names.",
"props": {
"ignoreExtern": true,
"format": "^(_|[a-z][a-zA-Z0-9]*$)",
"tokens": []
"tokens": [],
"severity": "INFO"
},
"type": "ParameterName"
},
{
"props": {
"max": 7,
"ignoreOverriddenMethods": false
},
"comment": "If enabled, complains when there are too many parameters on a method.",
"props": { "max": 7, "ignoreOverriddenMethods": false },
"type": "ParameterNumber"
},
{
"props": {
"severity": "WARNING"
},
"comment": "If enabled, complains when you make a property accessor method public.",
"props": { "severity": "WARNING" },
"type": "PublicAccessor"
},
{
"props": {
"prohibitMeta": false
},
"comment": "If enabled, complains when an @:access metadata is redundant.",
"props": { "prohibitMeta": false, "severity": "INFO" },
"type": "RedundantAccessMeta"
},
{
"props": {
"prohibitMeta": false
},
"comment": "If enabled, complains when an @:allow metadata is redundant.",
"props": { "prohibitMeta": false, "severity": "INFO" },
"type": "RedundantAllowMeta"
},
{
"comment": "If enabled, complains when an access modifier is redundant.",
"props": {
"enforcePrivate": false,
"enforcePublic": true,
"enforcePublicPrivate": false
"enforcePublicPrivate": false,
"severity": "WARNING"
},
"type": "RedundantModifier"
},
{
"comment": "If enabled, enforces that a return type is specified for all methods.",
"props": {
"enforceReturnTypeForAnonymous": false,
"allowEmptyReturn": true,
@@ -500,44 +583,63 @@
"type": "Return"
},
{
"props": {
"ignoreFormat": "^$",
"max": 2,
"severity": "IGNORE"
},
"comment": "If enabled, mandates a maximum number of 'return' statements in a method.",
"props": { "ignoreFormat": "^$", "max": 2, "severity": "IGNORE" },
"type": "ReturnCount"
},
{
"comment": "If enabled, enforces a specific placement for curly braces. TODO:Choose between Allman/K&R and enforce project-wide.",
"props": {
"option": "aloneorsingle",
"tokens": [
"CLASS_DEF",
"ENUM_DEF",
"ABSTRACT_DEF",
"TYPEDEF_DEF",
"INTERFACE_DEF",
"OBJECT_DECL",
"FUNCTION",
"FOR",
"IF",
"WHILE",
"SWITCH",
"TRY",
"CATCH",
"REIFICATION",
"ARRAY_COMPREHENSION"
],
"severity": "IGNORE"
},
"type": "RightCurly"
},
{
"comment": "Enforces specific whitespace around certain separators, such as commas and semicolons.",
"props": {
"commaPolicy": "after",
"semicolonPolicy": "after",
"allowTrailingComma": false,
"dotPolicy": "none"
"dotPolicy": "none",
"severity": "INFO"
},
"type": "SeparatorWhitespace"
},
{
"props": {
"tokens": [","],
"option": "eol"
},
"comment": "Enforces specific line wrapping for certain separators, such as commas and semicolons.",
"props": { "tokens": [","], "option": "eol" },
"type": "SeparatorWrap"
},
{
"props": {},
"comment": "If enabled, complains about overly complicated boolean expressions.",
"props": { "severity": "WARNING" },
"type": "SimplifyBooleanExpression"
},
{
"props": {},
"comment": "If enabled, complains about overly complicated boolean return statements.",
"props": { "severity": "WARNING" },
"type": "SimplifyBooleanReturn"
},
{
"comment": "If enabled, enforce spacing in and around conditional statements and certain operators.",
"props": {
"spaceIfCondition": "should",
"spaceAroundBinop": true,
@@ -551,81 +653,147 @@
"type": "Spacing"
},
{
"comment": "Enforce a specific string literal style (either ' or \").",
"props": {
"policy": "onlySingle",
"allowException": true,
"severity": "IGNORE"
"severity": "INFO"
},
"type": "StringLiteral"
},
{
"comment": "Puts a warning on unresolved TODO/FIXME comments.",
"props": {
"format": "^\\s*(TODO|FIXME|HACK|XXX|BUG)"
"format": "^\\s*(TODO|FIXME|HACK|XXX|BUG)",
"severity": "INFO"
},
"type": "TODOComment"
},
{
"props": {
"ignorePattern": "^$"
},
"comment": "Checks if there are any tabs in the middle of a line.",
"props": { "severity": "INFO" },
"type": "TabForAligning"
},
{
"props": {
"severity": "IGNORE"
},
"comment": "If enabled, complains whenever trace() gets used in code.",
"props": { "severity": "IGNORE" },
"type": "Trace"
},
{
"props": {},
"comment": "If enabled, complains about trailing whitespaces at the end of lines.",
"props": { "severity": "INFO" },
"type": "TrailingWhitespace"
},
{
"comment": "If enabled, requires type be specified on member fields.",
"props": { "ignoreEnumAbstractValues": true, "severity": "INFO" },
"type": "Type"
},
{
"comment": "If enabled, mandates documentation comments on classes and interfaces.",
"props": {
"tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"]
"tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"],
"severity": "INFO"
},
"type": "TypeDocComment"
},
{
"comment": "Specifies a specific naming format for abstract types.",
"props": {
"ignoreExtern": true,
"format": "^[A-Z]+[a-zA-Z0-9]*$",
"tokens": []
"tokens": ["ABSTRACT"],
"severity": "WARNING"
},
"type": "TypeName"
},
{
"comment": "Specifies a specific naming format for class types.",
"props": {
"ignoreExtern": true,
"format": "^[A-Z]+[a-zA-Z0-9]*$",
"tokens": ["CLASS"],
"severity": "WARNING"
},
"type": "TypeName"
},
{
"comment": "Specifies a specific naming format for enum types.",
"props": {
"ignoreExtern": true,
"format": "^[A-Z]+[a-zA-Z0-9]*$",
"tokens": ["ENUM"],
"severity": "WARNING"
},
"type": "TypeName"
},
{
"comment": "Specifies a specific naming format for interface types.",
"props": {
"ignoreExtern": true,
"format": "^[A-Z]+[a-zA-Z0-9]*$",
"tokens": ["INTERFACE"],
"severity": "WARNING"
},
"type": "TypeName"
},
{
"comment": "Specifies a specific naming format for typedefs.",
"props": {
"ignoreExtern": true,
"format": "^[A-Z]+[a-zA-Z0-9]*$",
"tokens": ["TYPEDEF"],
"severity": "WARNING"
},
"type": "TypeName"
},
{
"props": {},
"comment": "If enabled, complains when a class with only static fields and values has a redundant constructor.",
"props": { "severity": "WARNING" },
"type": "UnnecessaryConstructor"
},
{
"props": {
"moduleTypeMap": {},
"ignoreModules": []
},
"comment": "If enabled, complains when a module contains unused imports. NOTE:Sometimes complains about required imports.",
"props": { "moduleTypeMap": {}, "ignoreModules": [], "severity": "INFO" },
"type": "UnusedImport"
},
{
"props": {
"severity": "WARNING"
},
"comment": "If enabled, complains when a local variable is unused.",
"props": { "severity": "WARNING" },
"type": "UnusedLocalVar"
},
{
"comment": "If enabled, requires type hints for certain variables.",
"props": {
"typeHintPolicy": "enforce_all",
"ignoreEnumAbstractValues": true
"ignoreEnumAbstractValues": true,
"severity": "IGNORE"
},
"type": "VarTypeHint"
},
{
"comment": "If enabled, complains when a class member variable is initialized at the class level rather than the constructor.",
"type": "VariableInitialisation",
"props": {
"allowFinal": true,
"severity": "IGNORE"
}
},
{
"comment": "If enabled, mandates whitespace after certain tokens.",
"props": {
"tokens": [",", ";"],
"allowTrailingComma": false
"allowTrailingComma": false,
"severity": "INFO"
},
"type": "WhitespaceAfter"
},
{
"comment": "If enabled, mandates whitespace around certain tokens.",
"props": {
"tokens": [
"=",
@@ -660,10 +828,10 @@
"&=",
"^=",
"=>"
]
],
"severity": "INFO"
},
"type": "WhitespaceAround"
}
],
"numberOfCheckerThreads": 5
]
}
+1 -1
View File
@@ -60,7 +60,7 @@ This section provides guidelines to follow when [opening an issue](https://githu
## Requirements
Make sure you're playing:
- the latest version of the game (currently v0.7.4)
- the latest version of the game (currently v0.8.1)
- without any mods
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

+5
View File
@@ -0,0 +1,5 @@
Images stored in this folder come from these links:
FNF_logo.png - https://fridaynightfunkin.wiki.gg/images/FNF_logo.png
Menu.png - https://fridaynightfunkin.wiki.gg/images/Menu.png
Title_Card.gif - https://fridaynightfunkin.wiki.gg/images/d/d7/Title_Card.gif
+37
View File
@@ -0,0 +1,37 @@
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>me.funkin.fnfc</string>
<key>UTTypeDescription</key>
<string>FNFC Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.zip-archive</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>fnfc</string>
</array>
<key>public.mime-type</key>
<string>application/zip</string>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>FNFC Document</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>me.funkin.fnfc</string>
</array>
</dict>
</array>
+1 -1
View File
@@ -6,7 +6,7 @@
"name": "EliteMasterEric"
}
],
"api_version": "0.7.0",
"api_version": "0.8.0",
"mod_version": "1.0.0",
"license": "Apache-2.0"
}
+1 -1
View File
@@ -6,7 +6,7 @@
"name": "EliteMasterEric"
}
],
"api_version": "0.7.0",
"api_version": "0.8.0",
"mod_version": "1.0.0",
"license": "Apache-2.0"
}
+40 -41
View File
@@ -4,8 +4,8 @@
"name": "FlxPartialSound",
"type": "git",
"dir": null,
"ref": "3c9f63e3501c20c0b60442089dc05306f5a87968",
"url": "https://github.com/FunkinDroidTeam/FlxPartialSound.git"
"ref": "26ed20b9c181a576f4cbe18ccdea38912c01476e",
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
},
{
"name": "astc-compressor",
@@ -18,7 +18,7 @@
"name": "extension-admob",
"type": "git",
"dir": null,
"ref": "02334589ff9603a5f483077a44395009644f6274",
"ref": "4c69bb43f1fb89fd6003dc98d2f3a9d75c5364b6",
"url": "https://github.com/FunkinCrew/extension-admob"
},
{
@@ -41,26 +41,33 @@
"type": "haxelib",
"version": "1.0.3"
},
{
"name": "extension-webviewcore",
"type": "git",
"dir": null,
"ref": "d13109450d65c40678244ca23b1aa88ac18d77e6",
"url": "https://github.com/LimeExtensions/extension-webviewcore"
},
{
"name": "flixel",
"type": "git",
"dir": null,
"ref": "ac2a34fe4b3400bc04218f46320a65af0f337fc0",
"ref": "c8e79d2a42f9c11c639519ef2a6fd7ef2379a8aa",
"url": "https://github.com/FunkinCrew/flixel"
},
{
"name": "flixel-addons",
"type": "git",
"dir": null,
"ref": "b9118f47f43a66bc0e5fbfcfd9903f0425e918ee",
"ref": "6fa30b3f5209146c852c25f3d1003e08898083e2",
"url": "https://github.com/FunkinCrew/flixel-addons"
},
{
"name": "flxanimate",
"name": "flixel-animate",
"type": "git",
"dir": null,
"ref": "49214278b9124823582cdcecd94f4a1de9a4b36b",
"url": "https://github.com/FunkinCrew/flxanimate"
"ref": "c5e3393c70b71a191f20fa902114cea92042e486",
"url": "https://github.com/MaybeMaru/flixel-animate"
},
{
"name": "format",
@@ -71,14 +78,14 @@
"name": "funkin.vis",
"type": "git",
"dir": null,
"ref": "1966f8fbbbc509ed90d4b520f3c49c084fc92fd6",
"ref": "02bada154b474c2554709b9d12aef0cbf0da3ec9",
"url": "https://github.com/FunkinCrew/funkVis"
},
{
"name": "grig.audio",
"type": "git",
"dir": "src",
"ref": "8567c4dad34cfeaf2ff23fe12c3796f5db80685e",
"ref": "6409f3c6d1b4c52176813d3ede86c0d34e8af2c1",
"url": "https://github.com/FunkinCrew/grig.audio"
},
{
@@ -90,8 +97,8 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
"ref": "47ee9f5fa02d422e186e844f87e68220cabfcc5b",
"url": "https://github.com/haxeui/haxeui-core"
"ref": "088b153b35985f8e4419ef710057ebd5b3749513",
"url": "https://github.com/FunkinCrew/haxeui-core"
},
{
"name": "haxeui-flixel",
@@ -100,33 +107,24 @@
"ref": "100f2c96beab619cfe72c567a058c41c71e3e998",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{
"name": "hscript",
"type": "git",
"dir": null,
"ref": "8c1d238b069ef97cec13f5121b29d2afe2b8985d",
"url": "https://github.com/FunkinCrew/hscript"
},
{
"name": "hxcpp",
"type": "git",
"dir": null,
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
"ref": "5932340d095a7eea8635fe4d1355f1c0efd0b3c2",
"url": "https://github.com/FunkinCrew/hxcpp"
},
{
"name": "hxcpp-debug-server",
"type": "git",
"dir": "hxcpp-debug-server",
"ref": "147294123f983e35f50a966741474438069a7a8f",
"ref": "7459934666a473a4cc4d066ba4a93ef92f1ce94c",
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
},
{
"name": "hxdiscord_rpc",
"type": "git",
"dir": null,
"ref": "82c47ecc1a454b7dd644e4fcac7e91155f176dec",
"url": "https://github.com/FunkinCrew/hxdiscord_rpc"
"type": "haxelib",
"version": "1.3.0"
},
{
"name": "hxjsonast",
@@ -135,6 +133,11 @@
"ref": "20e72cc68c823496359775ac1f06500e67f189d5",
"url": "https://github.com/nadako/hxjsonast/"
},
{
"name": "hxgamemode",
"type": "haxelib",
"version": "1.0.1"
},
{
"name": "hxp",
"type": "haxelib",
@@ -143,56 +146,52 @@
{
"name": "hxvlc",
"type": "haxelib",
"version": "2.2.2"
"version": "2.2.5"
},
{
"name": "json2object",
"type": "git",
"dir": null,
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
"ref": "59e0467c953d1f26e3cbf2a070f140e2d2e8457d",
"url": "https://github.com/FunkinCrew/json2object"
},
{
"name": "jsonpatch",
"type": "git",
"dir": null,
"ref": "f9b83215acd586dc28754b4ae7f69d4c06c3b4d3",
"url": "https://github.com/EliteMasterEric/jsonpatch"
"type": "haxelib",
"version": "1.1.0"
},
{
"name": "jsonpath",
"type": "git",
"dir": null,
"ref": "7a24193717b36393458c15c0435bb7c4470ecdda",
"url": "https://github.com/EliteMasterEric/jsonpath"
"type": "haxelib",
"version": "1.1.0"
},
{
"name": "lime",
"type": "git",
"dir": null,
"ref": "e5f8c27124598505917a001588b560244731adfb",
"ref": "cae0c0bc10915f6281625d07e5527f3cecb4347f",
"url": "https://github.com/FunkinCrew/lime"
},
{
"name": "newgrounds",
"type": "git",
"dir": null,
"ref": "c8b30832027fa04f48fe6a45fa7903d5b265d56e",
"url": "https://github.com/Geokureli/Newgrounds/"
"ref": "b2b53cd0a5f030af74efd325732951cddbea8826",
"url": "https://github.com/FunkinCrew/Newgrounds"
},
{
"name": "openfl",
"type": "git",
"dir": null,
"ref": "a0df7c3afe360c9af59a76e45007dbf4e53b5131",
"ref": "5399946b68fd98f345d6b3e52b95ed6b78bf427c",
"url": "https://github.com/FunkinCrew/openfl"
},
{
"name": "polymod",
"type": "git",
"dir": null,
"ref": "55ee00236806330c89c3bff1e1563eb6425a9d96",
"url": "https://github.com/larsiusprime/polymod"
"ref": "d8f036378c4ff472f1922ad623b8800b6a061c1a",
"url": "https://github.com/FunkinCrew/polymod"
},
{
"name": "thx.core",
+282 -6
View File
@@ -1,30 +1,306 @@
{
"disableFormatting": false,
"emptyLines": {
"finalNewline": true,
"maxAnywhereInFile": 1,
"beforePackage": 0,
"afterPackage": 1,
"importAndUsing": {
"betweenImports": 0,
"betweenImportsLevel": "firstLevelPackage",
"beforeUsing": 1,
"beforeType": 1
},
"betweenTypes": 1,
"lineCommentsBetweenTypes": "keep",
"lineCommentsBetweenFunctions": "keep",
"betweenSingleLineTypes": 0,
"beforeRightCurly": "remove",
"afterLeftCurly": "remove",
"afterReturn": "remove",
"beforeBlocks": "remove",
"afterBlocks": "remove",
"enumAbstractEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenVars": 0,
"afterVars": 1,
"betweenFunctions": 1,
"existingBetweenFields": "keep"
},
"classEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenStaticVars": 0,
"afterStaticVars": 1,
"afterPrivateVars": 1,
"betweenVars": 0,
"afterVars": 1,
"afterStaticFunctions": 1,
"betweenStaticFunctions": 1,
"afterPrivateFunctions": 1,
"betweenFunctions": 1,
"existingBetweenFields": "keep"
},
"macroClassEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenStaticVars": 0,
"afterStaticVars": 1,
"afterPrivateVars": 1,
"betweenVars": 0,
"afterVars": 1,
"afterStaticFunctions": 1,
"betweenStaticFunctions": 1,
"afterPrivateFunctions": 1,
"betweenFunctions": 1,
"existingBetweenFields": "keep"
},
"externClassEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenVars": 0,
"afterVars": 0,
"betweenFunctions": 0,
"existingBetweenFields": "keep"
},
"abstractEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenStaticVars": 0,
"afterStaticVars": 1,
"afterPrivateVars": 1,
"betweenVars": 0,
"afterVars": 1,
"afterStaticFunctions": 1,
"betweenStaticFunctions": 1,
"afterPrivateFunctions": 1,
"betweenFunctions": 1,
"existingBetweenFields": "keep"
},
"interfaceEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenVars": 0,
"afterVars": 0,
"betweenFunctions": 0,
"existingBetweenFields": "keep"
},
"enumEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenFields": 0,
"existingBetweenFields": "keep"
},
"typedefEmptyLines": {
"beginType": 0,
"endType": 0,
"betweenFields": 0,
"existingBetweenFields": "keep"
},
"conditionalsEmptyLines": {
"afterIf": 0,
"beforeElse": 0,
"afterElse": 0,
"beforeEnd": 0,
"beforeError": 0,
"afterError": 0
},
"beforeDocCommentEmptyLines": "one",
"afterFieldsWithDocComments": "one",
"afterFileHeaderComment": 1,
"betweenMultilineComments": 0
},
"indentation": {
"conditionalPolicy": "aligned",
"character": " ",
"tabWidth": 2,
"trailingWhitespace": false,
"indentObjectLiteral": true,
"indentComplexValueExpressions": false,
"indentCaseLabels": true
},
"lineEnds": {
"lineEndCharacter": "auto",
"metadataType": "none",
"metadataVar": "none",
"metadataFunction": "none",
"metadataOther": "none",
"caseColon": "after",
"sharp": "after",
"leftCurly": "both",
"rightCurly": "both",
"emptyCurly": "noBreak",
"blockCurly": {
"emptyCurly": "break",
"leftCurly": "both",
"rightCurly": "both"
},
"anonFunctionCurly": {
"emptyCurly": "break",
"leftCurly": "both",
"rightCurly": "both"
},
"anonTypeCurly": {
"emptyCurly": "break",
"leftCurly": "both",
"rightCurly": "both"
},
"objectLiteralCurly": {
"emptyCurly": "break",
"leftCurly": "after",
"rightCurly": "both"
},
"leftCurly": "both",
"rightCurly": "both"
"typedefCurly": {
"emptyCurly": "break",
"leftCurly": "both",
"rightCurly": "both"
}
},
"sameLine": {
"ifBody": "same",
"elseBody": "next",
"ifElse": "next",
"doWhile": "next",
"elseIf": "same",
"ifElseSemicolonNextLine": true,
"expressionIf": "same",
"expressionIfWithBlocks": false,
"forBody": "next",
"comprehensionFor": "same",
"whileBody": "next",
"doWhileBody": "next",
"doWhile": "same",
"tryBody": "next",
"tryCatch": "next"
"catchBody": "next",
"tryCatch": "next",
"caseBody": "next",
"expressionCase": "keep",
"expressionTry": "same",
"functionBody": "same",
"anonFunctionBody": "same",
"returnBody": "same",
"returnBodySingleLine": "same",
"untypedBody": "same"
},
"whitespace": {
"switchPolicy": "around"
}
"parenConfig": {},
"bracesConfig": {},
"bracketConfig": {},
"typeParamOpenPolicy": "none",
"typeParamClosePolicy": "none",
"typeExtensionPolicy": "after",
"commaPolicy": "onlyAfter",
"dotPolicy": "none",
"colonPolicy": "none",
"caseColonPolicy": "onlyAfter",
"objectFieldColonPolicy": "after",
"typeHintColonPolicy": "none",
"typeCheckColonPolicy": "around",
"ternaryPolicy": "around",
"semicolonPolicy": "onlyAfter",
"ifPolicy": "after",
"doPolicy": "after",
"whilePolicy": "after",
"forPolicy": "after",
"switchPolicy": "around",
"tryPolicy": "after",
"catchPolicy": "after",
"binopPolicy": "around",
"intervalPolicy": "none",
"arrowFunctionsPolicy": "around",
"functionTypeHaxe3Policy": "none",
"functionTypeHaxe4Policy": "around",
"compressSuccessiveParenthesis": true,
"formatStringInterpolation": true,
"addLineCommentSpace": true
},
"wrapping": {
"maxLineLength": 160,
"arrayWrap": {
"defaultWrap": "noWrap",
"defaultLocation": "afterLast",
"defaultAdditionalIndent": 0
},
"mapWrap": {
"defaultWrap": "noWrap",
"defaultLocation": "afterLast",
"defaultAdditionalIndent": 0
},
"typeParameter": {
"defaultWrap": "noWrap"
}
},
"excludes": [
"_Provide a list of regular expressions to exclude from formatting.",
".haxelib",
".git",
"node_modules"
]
}
+703 -90
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -0,0 +1,49 @@
package scripts;
using DateTools;
class Postbuild
{
static function main()
{
/* trace('creating NG zip');
var binFolder:String = 'export/release/html5/bin/';
var date = Date.now();
var outputName:String = 'NG-' + date.format("%Y%m%d--%H%M") + '.zip';
var out = sys.io.File.write(outputName);
var zip = new haxe.zip.Writer(out);
zip.write(getEntries(binFolder));
trace('Finished creating ZIP');
*/
}
static function getEntries(dir:String, entries:List<haxe.zip.Entry> = null, inDir:Null<String> = null)
{
if (entries == null) entries = new List<haxe.zip.Entry>();
if (inDir == null) inDir = dir;
for (file in sys.FileSystem.readDirectory(dir))
{
var path = haxe.io.Path.join([dir, file]);
if (sys.FileSystem.isDirectory(path)) getEntries(path, entries, inDir);
else
{
var bytes:haxe.io.Bytes = haxe.io.Bytes.ofData(sys.io.File.getBytes(path).getData());
var entry:haxe.zip.Entry =
{
fileName: StringTools.replace(path, inDir, ""),
fileSize: bytes.length,
fileTime: Date.now(),
compressed: false,
dataSize: sys.FileSystem.stat(path).size,
data: bytes,
crc32: haxe.crypto.Crc32.make(bytes)
};
entries.push(entry);
}
}
return entries;
}
}
+180
View File
@@ -0,0 +1,180 @@
import haxe.Json;
import haxe.Log;
import haxe.macro.Compiler;
import sys.FileSystem;
import sys.io.File;
using StringTools;
#if !interp
package scripts;
// needs this thing to be run by interp, just for me lol!
#end
// general use of this!!
// if run in INTERP MODE:
// - scripts>haxe --main SongConverter --interp
// - song to convert: ../assets/preload/data/
// it will run through EVERY folder in there and convert the old songs to the new format!
// TODO make instructions for the C# script version!
class SongConverter
{
public static var VERSION:String = '0.1.0';
// TODO
// Update engine shit, to accomodate single file
static function main()
{
// trace(Compiler.get);
// trace(Compiler.getDefine('sysfsd'));
Sys.stdout().writeString('Please type directory you want to convert: ');
Sys.stdout().flush();
final input = Sys.stdin().readLine();
trace('Hello ${input}!');
/* Sys.stdout().writeString('What type of conversion do you need?\n\t0: Old charts to new chart\n\t1: new chart to .... new chart? lol\n>');
Sys.stdout().flush();
final deleteShitIDK = Std.parseInt(Sys.stdin().readLine());
trace(deleteShitIDK); */
var songCount:Int = 0;
for (fileThing in FileSystem.readDirectory('${input}/.'))
{
// trace(fileThing);
// trace(FileSystem.isDirectory(${input} + "/" + fileThing));
if (FileSystem.isDirectory(${input} + "/" + fileThing) && fileThing.toLowerCase() != 'smash')
{
trace('Formatting $fileThing');
formatSongs(fileThing, input);
songCount++;
}
}
trace('CONVERTED $songCount SONGS!!');
var timer:Int = 5;
while (timer > 0)
{
Sys.stdout().writeString('CLOSING IN $timer SECONDS!');
Sys.sleep(1);
Sys.print("\r");
timer -= 1;
}
}
public static function formatSongs(songName:String, root:String)
{
var existsArray:Array<Bool> = [];
var songMap:Map<String, Dynamic> = new Map();
var speedMap:Map<String, Dynamic> = new Map();
for (songFile in FileSystem.readDirectory('$root/$songName/'))
{
if (!songFile.endsWith('json') || !songFile.startsWith(songName)) continue; // ACCOMODATE FOR PICO SPEAKER EVENTUALLY!
var funnyFile:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songFile'));
var songSplit:Array<String> = songFile.split('-');
var songShit:String = 'normal';
if (songSplit.length > 1)
{
if (songSplit[1] == 'new.json')
{
FileSystem.deleteFile('$root/$songName/$songFile');
trace('DELETEING $songFile');
continue;
}
songShit = songSplit[1].substr(0, songSplit[1].length - 5);
}
if (songShit != 'normal')
{
songMap[songShit] = funnyFile.song.notes;
speedMap[songShit] = funnyFile.song.speed;
}
else
{
//
if (funnyFile.song.notes is Array)
{
songMap[songShit] = funnyFile.song.notes[1];
speedMap[songShit] = funnyFile.song.speed[1];
}
else if (Type.typeof(funnyFile.song.notes) == TObject)
{
var dipshitMap:Map<String, Dynamic> = cast funnyFile.song.notes;
// songMap[songShit] = dipshitMap.get('normal');
}
else
{
songMap[songShit] = funnyFile.song.notes;
speedMap[songShit] = funnyFile.song.speed;
}
}
}
var fileNormal:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songName.json'));
fileNormal.song.notes = songMap;
fileNormal.song.speed = speedMap;
fileNormal.song.hasDialogueFile = false;
fileNormal.song.stageDefault = getStage(songName);
fileNormal.version = 'FNF SongConverter $VERSION';
var daJson = Json.stringify(fileNormal, null, "\t");
// trace(daJson);
File.saveContent('$root/$songName/$songName.json', daJson);
}
static function noteCleaner(notes:Array<Dynamic>):Array<Dynamic>
{
var swagArray:Array<Dynamic> = [];
for (i in notes)
{
if (i.sectionNotes.length > 0) swagArray.push(i);
}
return swagArray;
}
/**
* Quicky lil function just for me formatting the old songs hehehe
*/
static function getStage(songName:String):String
{
switch (songName)
{
case 'spookeez' | 'monster' | 'south':
return "spooky";
case 'pico' | 'blammed' | 'philly':
return 'philly';
case "milf" | 'satin-panties' | 'high':
return 'limo';
case "cocoa" | 'eggnog':
return 'mall';
case 'winter-horrorland':
return 'mallEvil';
case 'senpai' | 'roses':
return 'school';
case 'thorns':
return 'schoolEvil';
case 'guns' | 'stress' | 'ugh':
return 'tank';
default:
return 'stage';
}
}
}
+99
View File
@@ -0,0 +1,99 @@
import csv
import re
import sys
import json
import subprocess
# This script imports a CSV file, and bulk updates the chart metadata files
#
# Requirements:
# python3 I believe
# and you need to have npm/npx, as we use that to run prettier to keep the same formatting
#
# Usage:
# In our Google Sheets, export as .csv file
#
# Then in here from within this directory, run:
# python3 sheetsToCharts.py path/to/csv/file.csv
#
# use from within this directory, as the paths to the chart metadata files are relative to this directory
# Get the command line argument, if one wasn't already provided
if len(sys.argv) < 2:
csv_file = input("Please provide the CSV file path: ")
else:
csv_file = sys.argv[1]
# Open the CSV file
with open(csv_file, 'r') as file:
reader = csv.reader(file)
# Which rows for each difficulty are we looking for
diff_rows = {}
# Iterate over each row in the CSV file
for idx, row in enumerate(reader):
# The first row has headers for the columns, stuff like "Easy - Difficulty", so we want
# to know which rows corresponde to which difficulty
if idx == 0:
pattern = r"\b(?:difficulty)\b"
diff_cols = []
diff_words = []
# Go through each column, and see if it includes the word "difficulty"
# then append the column index, and the first word of the column (usually what difficulty it is, "easy", "normal", "hard", etc.)
for colindex, column in enumerate(row):
txt = re.findall(pattern, column, flags=re.IGNORECASE)
if txt:
diff_cols.append(colindex)
diff_words.append(column.split()[0].lower())
# We then create a dictionary for use later, where the key is the difficulty, and the value is the column index
diff_rows = dict(zip(diff_words, diff_cols))
else:
# Some song title parsing, to match the filenames we used in for the chart files
song_title = row[1].lower() # we use lowercase for filenames, SOUTH -> south
song_title = song_title.replace(" ", "-") # usually we change spaces to dashes, PHILLY NICE -> philly-nice
song_title = song_title.replace(".", "") # M.I.L.F. -> milf
song_title = song_title.replace("'", "") # blazin' -> blazin
# we open the chart metadata json file for writing and reading ("r+")
with open(f"../../../assets/preload/data/songs/{song_title}/{song_title}-metadata.json", "r+") as chart_metadata_file:
json_data = json.load(chart_metadata_file) # load the file as a python data structure/dict
play_data = json_data["playData"] # we want to modify the "playData" section, as that holds the difficulty
# note to self, python equals(=) operator seems to create a reference for the variable,
# so modifying play_data will also modify json_data, so we can save json_data easily later
# if the chart metadata file doesn't already have a "ratings" dict/section, we create one here with 0 for each
if "ratings" not in play_data:
play_data["ratings"] = {'easy': 0, 'normal': 0, 'hard': 0}
ratings = play_data["ratings"]
# Now we go through our data from the csv file, and the data we kept from the columns there
# will be put into our new ratings var, if it exists
for diff, col in diff_rows.items():
if row[col] == "":
continue
if diff in ratings:
ratings[diff] = round(float(row[col])) # convert the string to a float, and then round it to nearest int
# Convert the python json_data dict back to a json string
json_output = json.dumps(json_data)
# Write the json string back to the file, and truncate the rest of the file
chart_metadata_file.seek(0)
chart_metadata_file.write(json_output)
chart_metadata_file.truncate()
chart_metadata_file.close()
# Bit hacky, but we simply run `npx prettier` using the same rules we use for FNF here
# essnetially running it via cli and passing in the songs folder and our prettier config file
# This should make the spacing and formatting consistent
command = "npx prettier ../../../assets/preload/data/songs --write --config ../../../.prettierrc.js"
subprocess.run(command, shell=True)
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# Find all files in OGG format and execute ffmpeg to convert them to MP3
# Skips files that are already in MP3 format
find "../assets/" -name "*.ogg" -exec sh -c 'ffmpeg -n -i {} "${0%.*}.mp3"' {} \;
+218
View File
@@ -0,0 +1,218 @@
import requests
import sys
PROJECT_ID = 5582053
TEMPLATE = 'jQuery("#new-event-name").val("{event}"); jQuery("#save-new-event").click();'
EVENT_TEMPLATES = [
"start-game",
"watch-cartoon",
"open-credits",
"start-song_{songId}-{variation}",
"blueballs_{songId}-{variation}",
"complete-song_{songId}-{variation}",
"start-level_{levelId}",
"complete-level_{levelId}",
"earn-rank_{rankName}",
]
SONG_IDS = [
{ "songId": "tutorial", "variation": "default" },
{ "songId": "tutorial", "variation": "erect" },
{ "songId": "tutorial", "variation": "pico" },
{ "songId": "bopeebo", "variation": "default" },
{ "songId": "bopeebo", "variation": "erect" },
{ "songId": "bopeebo", "variation": "pico" },
{ "songId": "fresh", "variation": "default" },
{ "songId": "fresh", "variation": "erect" },
{ "songId": "fresh", "variation": "pico" },
{ "songId": "dadbattle", "variation": "default" },
{ "songId": "dadbattle", "variation": "erect" },
{ "songId": "dadbattle", "variation": "pico" },
{ "songId": "spookeez", "variation": "default" },
{ "songId": "spookeez", "variation": "erect" },
{ "songId": "spookeez", "variation": "pico" },
{ "songId": "south", "variation": "default" },
{ "songId": "south", "variation": "erect" },
{ "songId": "south", "variation": "pico" },
{ "songId": "monster", "variation": "default" },
{ "songId": "monster", "variation": "erect" },
{ "songId": "monster", "variation": "pico" },
{ "songId": "pico", "variation": "default" },
{ "songId": "pico", "variation": "erect" },
{ "songId": "pico", "variation": "pico" },
{ "songId": "philly-nice", "variation": "default" },
{ "songId": "philly-nice", "variation": "erect" },
{ "songId": "philly-nice", "variation": "pico" },
{ "songId": "blammed", "variation": "default" },
{ "songId": "blammed", "variation": "erect" },
{ "songId": "blammed", "variation": "pico" },
{ "songId": "satin-panties", "variation": "default" },
{ "songId": "satin-panties", "variation": "erect" },
{ "songId": "satin-panties", "variation": "pico" },
{ "songId": "high", "variation": "default" },
{ "songId": "high", "variation": "erect" },
{ "songId": "high", "variation": "pico" },
{ "songId": "milf", "variation": "default" },
{ "songId": "milf", "variation": "erect" },
{ "songId": "milf", "variation": "pico" },
{ "songId": "cocoa", "variation": "default" },
{ "songId": "cocoa", "variation": "erect" },
{ "songId": "cocoa", "variation": "pico" },
{ "songId": "eggnog", "variation": "default" },
{ "songId": "eggnog", "variation": "erect" },
{ "songId": "eggnog", "variation": "pico" },
{ "songId": "winter-horrorland", "variation": "default" },
{ "songId": "winter-horrorland", "variation": "erect" },
{ "songId": "winter-horrorland", "variation": "pico" },
{ "songId": "senpai", "variation": "default" },
{ "songId": "senpai", "variation": "erect" },
{ "songId": "senpai", "variation": "pico" },
{ "songId": "roses", "variation": "default" },
{ "songId": "roses", "variation": "erect" },
{ "songId": "roses", "variation": "pico" },
{ "songId": "thorns", "variation": "default" },
{ "songId": "thorns", "variation": "erect" },
{ "songId": "thorns", "variation": "pico" },
{ "songId": "ugh", "variation": "default" },
{ "songId": "ugh", "variation": "erect" },
{ "songId": "ugh", "variation": "pico" },
{ "songId": "guns", "variation": "default" },
{ "songId": "guns", "variation": "erect" },
{ "songId": "guns", "variation": "pico" },
{ "songId": "stress", "variation": "default" },
{ "songId": "stress", "variation": "erect" },
{ "songId": "stress", "variation": "pico" },
{ "songId": "darnell", "variation": "default" },
{ "songId": "darnell", "variation": "erect" },
{ "songId": "darnell", "variation": "bf" },
{ "songId": "lit-up", "variation": "default" },
{ "songId": "lit-up", "variation": "erect" },
{ "songId": "lit-up", "variation": "bf" },
{ "songId": "2hot", "variation": "default" },
{ "songId": "2hot", "variation": "erect" },
{ "songId": "2hot", "variation": "bf" },
{ "songId": "blazin", "variation": "default" },
{ "songId": "blazin", "variation": "erect" },
{ "songId": "blazin", "variation": "bf" },
]
LEVEL_IDS = [
"tutorial",
"week1",
"week2",
"week3",
"week4",
"week5",
"week6",
"week7",
"weekend1",
"week8",
]
RANKS = [
"PERFECT_GOLD",
"PERFECT",
"EXCELLENT",
"GREAT",
"GOOD",
"SHIT",
]
def formatEventSong(event):
result = []
for song in SONG_IDS:
result.append(event.format(
songId = song["songId"],
variation = song["variation"]
))
return result
def formatEventLevel(event):
result = []
for level in LEVEL_IDS:
result.append(event.format(
levelId = level
))
return result
def formatEventRank(event):
result = []
for rank in RANKS:
result.append(event.format(
rankName = rank
))
return result
def formatEvent(event):
if "{songId}" in event or "{variation}" in event:
return formatEventSong(event)
if "{levelId}" in event:
return formatEventLevel(event)
if "{rankName}" in event:
return formatEventRank(event)
return [event]
def createEvent(event):
print(TEMPLATE.format(event=event))
def main():
event_list = []
for event in EVENT_TEMPLATES:
event_list.extend(formatEvent(event))
# Filter out duplicates then sort alphabetically
event_list = list(set(event_list))
event_list.sort()
for event in event_list:
createEvent(event)
if __name__ == "__main__":
main()
#function sleep(ms) {
# console.log("sleeping for " + ms + "ms");
# return new Promise(resolve => setTimeout(resolve, ms));
#}
#async function example() {
# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click();
# await sleep(1000);
# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click();
# await sleep(1000);
#}
#example();
@@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -final
echo UPLOADING TO ITCH
butler push ./export/release/html5/bin ninja-muffin24/funkin-secret:html5
butler status ninja-muffin24/funkin-secret:html5
echo ITCH SHIT UPDATED LMAOOOOO
pause
+11
View File
@@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -debug
echo UPLOADING TO ITCH
butler push ./export/debug/html5/bin ninja-muffin24/funkin-secret:html5
butler status ninja-muffin24/funkin-secret:html5
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,15 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build windows -final -clean
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
lime build windows -final -32
echo UPLOADING 32 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-64bit
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build windows -debug
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/debug/windows/bin ninja-muffin24/funkin:windows-secretBeta
butler status ninja-muffin24/funkin:windows-secretBeta
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,15 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build windows -release
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
lime build windows -release -32
echo UPLOADING 32 BIT VERSION TO ITCH
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-64bit
echo ITCH SHIT UPDATED LMAOOOOO
pause
@@ -0,0 +1,12 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -final -Dng
echo ZIPPING
cd export
7z a -tzip -r swag release\html5\bin
start .
start https://www.newgrounds.com/projects/games/1528775
pause
@@ -0,0 +1,27 @@
@echo off
cd ..
@echo on
echo REBUILDING LIME FOR EXPORT (VERBOSE)
@echo off
color 0b
@echo on
lime rebuild windows -clean -v
@echo off
color 0a
@echo on
lime rebuild switch -clean -v
@echo off
color 0e
@echo on
lime rebuild tools -clean -v
echo HOPE AND PRAY...
@echo off
color 0a
@echo on
echo BUILDING GAME
lime build switch -final -clean -v
@echo off
color 0b
@echo on
echo NSP FILE CREATED
pause
+110
View File
@@ -0,0 +1,110 @@
// Highlight keyframes in flash
// drag .jsfl script in
// will export the layer info of the first(?) element it finds?
// maybe someone can extend this to add more depth / functionality!
// see FramesJSFLParser.hx and CharSelectGF.hx for sample implementation
function splitArrayIntoChunks(array, chunkSize) {
var result = [];
for (var i = 0; i < array.length; i += chunkSize) {
var chunk = array.slice(i, i + chunkSize);
result.push(chunk);
}
return result;
}
var isJson = false;
function addField(name, variable, end)
{
return (isJson) ? addFJson(name, variable, end) : addF(variable, end);
}
function addFJson(name, variable, end) {
return "\"" + name + "\": " + variable + ((!end) ? "," : "");
}
function addF(variable, end) {
return variable + ((end)? "" : " ");
}
fl.outputPanel.clear();
var timeline = fl.getDocumentDOM().getTimeline();
var daSelection = timeline.getSelectedFrames();
var splitArrays = splitArrayIntoChunks(daSelection, 3);
var uri = undefined;
if (fl.version.substring(4).split(",")[0] < 13)
{
var macFormat = "Folder|TEXT[*.||";
var winFormat = "Folder|*.||";
var previewArea = {};
uri = fl.browseForFileURL("save", "where to save files pick folder...", previewArea, macFormat, winFormat);
}
else
uri = fl.browseForFileURL("save", "where to save files pick folder..", "Folder(*.)", "*.");
for (var i = 0; i < splitArrays.length; i+=1)
{
var curLayerInfo = splitArrays[i];
var curLayerInd = curLayerInfo[0];
var curLayer = fl.getDocumentDOM().getTimeline().layers[curLayerInd];
var layerURI = uri;
var fileName = "";
var uris = uri.split("/");
fileName = uris[uris.length - 1];
FLfile.createFolder(layerURI);
var fileData = "{\n";
if (!isJson)
fileData = "";
var selectedFrames = curLayer.frames;
var sliceLength = curLayerInfo[2] - curLayerInfo[1];
selectedFrames =selectedFrames.slice(curLayerInfo[1],curLayerInfo[2]);
if (isJson)
fileData += "\"frames\": [";
for (var frameInd = curLayerInfo[1]; frameInd < curLayerInfo[2]; frameInd+=1)
{
if (isJson)
fileData += "\n{"
var curFrame = curLayer.frames[frameInd];
if (curFrame.isEmpty)
{
continue;
}
for (var elementInd = 0; elementInd < curFrame.elements.length; elementInd+=1)
{
var curElement = curFrame.elements[elementInd];
fileData += addField("x", curElement.left, false);
fileData += addField("y", curElement.top, false);
fileData += addField("alpha", (curElement.colorAlphaPercent == undefined) ? 100 : curElement.colorAlphaPercent, false);
fileData += addField("scaleX", curElement.scaleX, false);
fileData += addField("scaleY", curElement.scaleY, true);
if (frameInd != curLayerInfo[2] - 1)
fileData += "\n";
}
if (isJson)
fileData += "},";
}
if (isJson)
fileData += "]";
if (isJson)
fileData += "\n}";
FLfile.write(layerURI + "/" + fileName + ".txt", fileData);
}
+6
View File
@@ -0,0 +1,6 @@
# README
jsfl is a scripting format with javascript syntax, I believe it's old js (no `let`, idk the spec!)
Official Adobe JSFL docuementation
[https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master](https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master)
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status
echo "Note! This should be run from the root of the main Funkin repo, after creating a universal macOS build!"
echo "This script will assist in codesigning the Funkin.app for macOS"
echo ""
TEAM_ID="Z7G7AVNGSH"
ISSUER_ID="5205caca-2a9b-4b5e-9390-1db124cf51f4"
DEVELOPER_CERT_NAME="Developer ID Application: The Funkin' Crew Incorporated ($TEAM_ID)"
APP_PATH="export/release/macosUniversal/Funkin.app"
# Check if app exists
if [ ! -d "$APP_PATH" ]; then
echo "Error: $APP_PATH does not exist! Please build the app first."
exit 1
fi
echo "Preparing/Checking the AuthKey"
team_key_path=~/.appstoreconnect/private_keys/
# Check if directory exists, if not, create it
if [ ! -d "$team_key_path" ]; then
echo "The directory $team_key_path does not exist! Creating it now."
mkdir -p "$team_key_path"
echo ""
echo "$team_key_path created, put the private key file in there (should be called 'AuthKey_XXXXXXX.p8')"
echo "Ask Cameron for the private key file!"
echo ""
fi
# wait for the user to put the file in the directory, if it's not there
while [ ! -f "${team_key_path}"AuthKey_*.p8 ]; do
echo "Waiting for the private key file to be placed in $team_key_path... (press Ctrl+C to exit)"
sleep 5
done
key_id=$(ls "$team_key_path" | grep -oE '[A-Z0-9]{10}' | head -n 1)
if [ -z "$key_id" ]; then
echo "Error: Could not extract key ID from the AuthKey file name"
exit 1
fi
echo "Found AuthKey with key id: $key_id"
key_path="${team_key_path}AuthKey_${key_id}.p8"
cd export/release/macosUniversal || { echo "Failed to change directory"; exit 1; }
echo "Ensuring properly formatted Entitlements.plist"
if [ ! -f "Funkin.app/Contents/Entitlements.plist" ]; then
echo "Error: Entitlements.plist not found"
exit 1
fi
plutil -convert xml1 Funkin.app/Contents/Entitlements.plist
plutil -convert xml1 Funkin.app/Contents/Info.plist
plutil -lint Funkin.app/Contents/Entitlements.plist
plutil -lint Funkin.app/Contents/Info.plist
echo "Entitlements.plist and Info.plist should be properly formatted..."
echo "Codesigning - Funkin.app"
# Check the keychain for the funkin crew certificate
echo "Checking for The Funkin' Crew certificate in the keychain..."
if ! security find-identity -p codesigning | grep -q "The Funkin' Crew Incorporated"; then
echo "The Funkin' Crew Developer ID Application certificate was not found in the keychain!"
echo "Download the certificate from:"
echo "https://developer.apple.com/account/resources/certificates/download/Z62SXF29U7"
exit 1
fi
echo "Codesigning - Funkin.app"
codesign --force --deep --timestamp --sign "$DEVELOPER_CERT_NAME" --options runtime Funkin.app || {
echo "Code signing failed"
exit 1
}
echo "Codesigning - Funkin.app done!"
# Debug info to see if the app is properly signed
codesign -vv --deep --strict Funkin.app || {
echo "Code signing verification failed"
exit 1
}
echo ""
echo "Zipping Funkin.app for notarization (using ditto)..."
# Check if Funkin.app.zip already exists, if so, delete it
if [ -f Funkin.app.zip ]; then
echo "Funkin.app.zip already exists, deleting it..."
rm -rf Funkin.app.zip
fi
ditto -c -k --keepParent Funkin.app Funkin.app.zip || {
echo "Failed to create zip file"
exit 1
}
echo "Funkin.app zipped, now sending to Apple for notarization..."
xcrun notarytool submit Funkin.app.zip --issuer "$ISSUER_ID" --key-id "$key_id" --key "$key_path" --wait || {
echo "Notarization submission failed"
exit 1
}
# Clean up zip file
rm -rf Funkin.app.zip
echo "Stapling Funkin.app"
xcrun stapler staple Funkin.app || {
echo "Stapling failed"
exit 1
}
echo "Code sign information!"
codesign -dv --verbose=4 Funkin.app
echo "Notarization information (stapler):"
xcrun stapler validate Funkin.app || {
echo "Stapler validation failed"
exit 1
}
cd ../../.. || { echo "Failed to return to original directory"; exit 1; }
echo "Notarization complete! Funkin.app is now codesigned and notarized!"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
echo "Note! This should be run from the root of the main Funkin repo, after creating a universal macOS build!"
echo "This script will assist in creating a universal build of Funkin.app for macOS"
echo ""
lime build mac -64 -release
cd export/release
rm -rf macosUniversal
mkdir macosUniversal
cp -r macos/bin/Funkin.app macosUniversal
cd ../..
lime build mac -arm64 -release
cd export/release
cp -r macos/bin/Funkin.app/Contents/MacOS/Funkin macosUniversal/Funkin.app/Contents/MacOS/FunkinArm
cp -r macos/bin/Funkin.app/Contents/MacOS/lime.ndll macosUniversal/Funkin.app/Contents/MacOS/limeArm.ndll
cd macosUniversal/Funkin.app/Contents/MacOS
mv Funkin Funkin64
mv lime.ndll lime64.ndll
lipo -create -output lime.ndll lime64.ndll limeArm.ndll
lipo -create -output Funkin Funkin64 FunkinArm
rm FunkinArm Funkin64 lime64.ndll limeArm.ndll
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Find all files in PNG format and execute oxipng to optimize file sizes
cd ../assets/ && oxipng -o 6 --strip safe --alpha {} **/*.png
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Resize each health icon to 512x512
find "../art/discord/" -name "icon-*.png" -exec sh -c 'ffmpeg -i {} -vf scale=512:512 "${0%.*}2.png"' {} \;
+88 -53
View File
@@ -1,18 +1,19 @@
package;
import lime.system.System;
import flixel.FlxG;
import flixel.FlxGame;
import flixel.FlxState;
import funkin.ui.FullScreenScaleMode;
import funkin.Preferences;
import funkin.PlayerSettings;
import funkin.util.logging.CrashHandler;
import funkin.ui.debug.MemoryCounter;
import funkin.ui.debug.FunkinDebugDisplay;
import funkin.ui.debug.FunkinDebugDisplay.DebugDisplayMode;
import funkin.save.Save;
import haxe.ui.Toolkit;
#if hxvlc
import hxvlc.util.Handle;
#end
import openfl.display.FPS;
import openfl.display.Sprite;
import openfl.events.Event;
import openfl.Lib;
@@ -20,6 +21,8 @@ import openfl.media.Video;
import openfl.net.NetStream;
import funkin.util.WindowUtil;
using funkin.util.AnsiUtil;
/**
* The main class which initializes HaxeFlixel and starts the game in its initial state.
*/
@@ -30,7 +33,6 @@ class Main extends Sprite
var initialState:Class<FlxState> = funkin.InitState; // The FlxState the game starts with.
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode.
var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets
// You can pretty much ignore everything from here on - your code should go in your states.
@@ -60,6 +62,10 @@ class Main extends Sprite
haxe.Log.trace = funkin.util.logging.AnsiTrace.trace;
funkin.util.logging.AnsiTrace.traceBF();
// Get OpenFL to stop complaining so much.
// You can remove this line if you want to read debug messages.
openfl.utils._internal.Log.level = openfl.utils._internal.Log.LogLevel.INFO;
// Load mods to override assets.
// TODO: Replace with loadEnabledMods() once the user can configure the mod list.
funkin.modding.PolymodHandler.loadAllMods();
@@ -81,36 +87,45 @@ class Main extends Sprite
removeEventListener(Event.ADDED_TO_STAGE, init);
}
// Manually crash the game when using a software renderer in order to give a nicer error message.
var context = stage.window.context.type;
if (context != WEBGL && context != OPENGL && context != OPENGLES)
{
var tech:String = #if web "WebGL" #elseif desktop "OpenGL" #else "OpenGL ES" #end;
var requiredVersion:String = #if web '$tech 1.0 or newer' #elseif desktop '$tech 3.0 or newer' #else '$tech 2.0 or newer' #end;
var desc:String = 'Failed to initialize the $tech rendering context!\n\n';
#if web
desc += 'Make sure your graphics card supports $requiredVersion, your graphics drivers are up to date, and hardware acceleration is enabled on your browser.';
#elseif desktop
desc += 'Make sure your graphics card supports $requiredVersion, and your graphics drivers are up to date.';
#else
desc += 'Make sure your device supports $requiredVersion.';
#end
WindowUtil.showError('Failed to initialize $tech', desc);
System.exit(1);
}
setupGame();
}
var video:Video;
var netStream:NetStream;
var overlay:Sprite;
/**
* A frame counter displayed at the top left.
* The debug display at the top left.
*/
public static var fpsCounter:FPS;
/**
* A RAM counter displayed at the top left.
*/
public static var memoryCounter:MemoryCounter;
public static var debugDisplay:FunkinDebugDisplay;
function setupGame():Void
{
#if FEATURE_HAXEUI
initHaxeUI();
// addChild gets called by the user settings code.
fpsCounter = new FPS(10, 3, 0xFFFFFF);
#if !html5
// addChild gets called by the user settings code.
// TODO: disabled on HTML5 (todo: find another method that works?)
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
#end
// addChild gets called by the user settings code.
debugDisplay = new FunkinDebugDisplay(10, 10, 0xFFFFFF);
// Add this signal so the player can toggle the debug display using a hotkey.
FlxG.signals.postUpdate.add(handleDebugDisplayKeys);
#if mobile
// Add this signal so we can reposition and resize the memory and fps counter.
FlxG.signals.preUpdate.add(repositionCounters.bind(true));
@@ -121,7 +136,17 @@ class Main extends Sprite
#if hxvlc
// Initialize hxvlc's Handle here so the videos are loading faster.
Handle.init();
Handle.initAsync(function(success:Bool):Void
{
if (success)
{
trace(' HXVLC '.bold().bg_orange() + ' LibVLC instance initialized!');
}
else
{
trace(' HXVLC '.bold().bg_orange() + ' LibVLC instance failed to initialize!');
}
});
#end
// Don't call anything from the preferences until the save is loaded!
@@ -133,7 +158,12 @@ class Main extends Sprite
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
// Force a `FunkinCamera` to be the default camera.
// This allows the blend mode shader to work everywhere.
untyped FlxG.cameras = new funkin.graphics.FunkinCameraFrontEnd();
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash,
(FlxG.stage.window.fullscreen || Preferences.autoFullscreen));
// FlxG.game._customSoundTray wants just the class, it calls new from
// create() in there, which gets called when it's added to the stage
@@ -164,20 +194,43 @@ class Main extends Sprite
#end
}
#if FEATURE_HAXEUI
function initHaxeUI():Void
{
// This has to come before Toolkit.init since locales get initialized there
haxe.ui.locale.LocaleManager.instance.autoSetLocale = false;
// Calling this before any HaxeUI components get used is important:
// - It initializes the theme styles.
// - It scans the class path and registers any HaxeUI components.
Toolkit.init();
Toolkit.theme = 'dark'; // don't be cringe
// Toolkit.theme = 'light'; // embrace cringe
Toolkit.autoScale = false;
haxe.ui.Toolkit.init();
haxe.ui.Toolkit.theme = 'dark'; // don't be cringe
// haxe.ui.Toolkit.theme = 'light'; // embrace cringe
haxe.ui.Toolkit.autoScale = false;
// Don't focus on UI elements when they first appear.
haxe.ui.focus.FocusManager.instance.autoFocus = false;
funkin.input.Cursor.registerHaxeUICursors();
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
}
#end
function handleDebugDisplayKeys():Void
{
if (PlayerSettings.player1.controls == null || !PlayerSettings.player1.controls.check(DEBUG_DISPLAY)) return;
var nextMode:DebugDisplayMode;
switch (Preferences.debugDisplay)
{
case DebugDisplayMode.Off:
nextMode = DebugDisplayMode.Simple;
case DebugDisplayMode.Simple:
nextMode = DebugDisplayMode.Advanced;
case DebugDisplayMode.Advanced:
nextMode = DebugDisplayMode.Off;
}
Preferences.debugDisplay = nextMode;
}
#if mobile
function repositionCounters(lerp:Bool):Void
@@ -191,41 +244,23 @@ class Main extends Sprite
scale = Math.min(scale, 1);
#end
final thypos:Float = Math.max(FullScreenScaleMode.notchSize.x, 10);
if (fpsCounter != null)
if (debugDisplay != null)
{
fpsCounter.scaleX = fpsCounter.scaleY = scale;
debugDisplay.scaleX = debugDisplay.scaleY = scale;
if (FlxG.game != null)
{
if (lerp)
{
fpsCounter.x = flixel.math.FlxMath.lerp(fpsCounter.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
debugDisplay.x = flixel.math.FlxMath.lerp(debugDisplay.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
}
else
{
fpsCounter.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
debugDisplay.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
}
fpsCounter.y = FlxG.game.y + (3 * scale);
}
}
if (memoryCounter != null)
{
memoryCounter.scaleX = memoryCounter.scaleY = scale;
if (FlxG.game != null)
{
if (lerp)
{
memoryCounter.x = flixel.math.FlxMath.lerp(memoryCounter.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
}
else
{
memoryCounter.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
}
memoryCounter.y = FlxG.game.y + (13 * scale);
debugDisplay.y = FlxG.game.y + (3 * scale);
}
}
}
+21 -5
View File
@@ -3,6 +3,9 @@ package source; // Yeah, I know...
import sys.FileSystem;
import sys.io.File;
using StringTools;
using tools.AnsiUtil;
/**
* A script which executes after the game is built.
*/
@@ -27,14 +30,27 @@ class Postbuild
sys.FileSystem.deleteFile(BUILD_TIME_FILE);
var buildTime:Float = roundToTwoDecimals(end - start);
Sys.println('[INFO] Build took: ${buildTime} seconds');
Sys.println(' INFO '.info() + ' Build took: ${format(end - start)}');
}
}
static function roundToTwoDecimals(value:Float):Float
static function format(time:Float, decimals:Int = 1):String
{
return Math.round(value * 100) / 100;
var units = [{name: "day", secs: 86400}, {name: "hour", secs: 3600}, {name: "minute", secs: 60}, {name: "second", secs: 1}];
var parts:Array<String> = [];
var remaining:Float = time;
var factor = Math.pow(10, decimals); // compute once because the old code was computing it twice.
for (u in units)
{
var value:Float = (u.name == "second") ? Math.round(remaining * factor) / factor : Math.floor(remaining / u.secs);
if (u.name != "second") remaining %= u.secs;
if (value > 0 || (u.name == "second" && parts.length == 0)) parts.push('${value} ${u.name}${value == 1 ? "" : "s"}');
}
return parts.join(" ");
}
}
+11 -20
View File
@@ -89,44 +89,35 @@ extern class Native_TracyProfiler
class Cppia_TracyProfiler
{
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMark)
public static function frameMark()
Native_TracyProfiler.frameMark();
public static function frameMark() Native_TracyProfiler.frameMark();
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkNamed)
public static function frameMarkNamed(_name:String)
Native_TracyProfiler.frameMarkNamed(_name);
public static function frameMarkNamed(_name:String) Native_TracyProfiler.frameMarkNamed(_name);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkStart)
public static function frameMarkStart(_name:String)
Native_TracyProfiler.frameMarkStart(_name);
public static function frameMarkStart(_name:String) Native_TracyProfiler.frameMarkStart(_name);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkEnd)
public static function frameMarkEnd(_name:String)
Native_TracyProfiler.frameMarkEnd(_name);
public static function frameMarkEnd(_name:String) Native_TracyProfiler.frameMarkEnd(_name);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.message)
public static function message(_msg:String, ?_color:Int = 0x000000)
Native_TracyProfiler.message(_msg, _color);
public static function message(_msg:String, ?_color:Int = 0x000000) Native_TracyProfiler.message(_msg, _color);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.messageAppInfo)
public static function messageAppInfo(_info:String)
Native_TracyProfiler.messageAppInfo(_info);
public static function messageAppInfo(_info:String) Native_TracyProfiler.messageAppInfo(_info);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plot)
public static function plot(_name:String, _val:Float)
Native_TracyProfiler.plot(_name, _val);
public static function plot(_name:String, _val:Float) Native_TracyProfiler.plot(_name, _val);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plotConfig)
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000)
Native_TracyProfiler.plotConfig(_name, _format, _step, _fill, _color);
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false,
?_color:Int = 0x000000) Native_TracyProfiler.plotConfig(_name, _format, _step, _fill, _color);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.setThreadName)
public static function setThreadName(_name:String, ?_groupHint:Int = 1)
Native_TracyProfiler.setThreadName(_name, _groupHint);
public static function setThreadName(_name:String, ?_groupHint:Int = 1) Native_TracyProfiler.setThreadName(_name, _groupHint);
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.zoneScoped)
public static function zoneScoped(_name:String)
Native_TracyProfiler.zoneScoped(_name);
public static function zoneScoped(_name:String) Native_TracyProfiler.zoneScoped(_name);
}
typedef TracyProfiler = Cppia_TracyProfiler;
+4 -3
View File
@@ -1,13 +1,14 @@
package funkin;
import openfl.utils.Future;
import funkin.util.macro.ConsoleMacro;
/**
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
* Later we'll add Funkin-specific caching to this.
*/
@:nullSafety
class Assets
class Assets implements ConsoleClass
{
/**
* The assets cache.
@@ -114,9 +115,9 @@ class Assets
* @param path The asset path to load from
* @return The loaded Bitmap image
*/
public static function getBitmapData(path:String):openfl.display.BitmapData
public static function getBitmapData(path:String, useCache:Bool = true):openfl.display.BitmapData
{
return openfl.utils.Assets.getBitmapData(path);
return openfl.utils.Assets.getBitmapData(path, useCache);
}
/**
+225 -43
View File
@@ -18,20 +18,32 @@ import flixel.sound.FlxSound;
@:nullSafety
class Conductor
{
// onBeatHit is called every quarter note
// onStepHit is called every sixteenth note
// onBeatHit is called on every note determined by the denominator (4 = quarter, 8 = eighth, etc.)
// onStepHit is called on every note which is a quarter of a beat (4 = sixteenth, 8 = thirty-second, etc.)
// 4/4 = 4 beats per measure = 16 steps per measure
// 120 BPM = 120 quarter notes per minute = 2 onBeatHit per second
// 120 BPM = 480 sixteenth notes per minute = 8 onStepHit per second
// 60 BPM = 60 quarter notes per minute = 1 onBeatHit per second
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
// 3/4 = 3 beats per measure = 12 steps per measure
// (IDENTICAL TO 4/4 but shorter measure length)
// (Identical to 4/4 but has a shorter measure length)
// 120 BPM = 120 quarter notes per minute = 2 onBeatHit per second
// 120 BPM = 480 sixteenth notes per minute = 8 onStepHit per second
// 60 BPM = 60 quarter notes per minute = 1 onBeatHit per second
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
// 7/8 = 3.5 beats per measure = 14 steps per measure
// 7/8 = 7 beats per measure = 28 steps per measure
// Beats are EIGHTH NOTES!!
// (Identical to 7/4 but beats happen twice as fast)
// 120 BPM = 240 eighth notes per minute = 4 onBeatHit per second
// 120 BPM = 960 twenty-second notes per minute = 16 onStepHit per second
// 15/16 = 15 beats per measure = 60 steps per measure
// Beats are SIXTEENTH NOTES!!
// 120 BPM = 480 sixteenth notes per minute = 8 onBeatHit per second
// 120 BPM = 1920 sixty-fourth notes per minute = 32 onStepHit per second
// 3/2 = 3 beats per measure = 12 steps per measure
// Beats are HALF NOTES!!
// 120 BPM = 60 half notes per minute = 1 onBeatHit per second
// 120 BPM = 240 eighth notes per minute = 4 onStepHit per second
/**
* The current instance of the Conductor.
@@ -113,7 +125,6 @@ class Conductor
if (bpmOverride != null) return bpmOverride;
if (currentTimeChange == null) return Constants.DEFAULT_BPM;
@:privateAccess
if (PlayState.instance != null && PlayState.instance.startingSong)
{
@@ -160,24 +171,24 @@ class Conductor
}
/**
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
* Duration of a beat in milliseconds. Calculated based on bpm.
*/
public var beatLengthMs(get, never):Float;
function get_beatLengthMs():Float
{
// Tied directly to BPM.
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC) * (4 / timeSignatureDenominator);
}
/**
* Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm.
* Duration of a step in milliseconds. Calculated based on bpm.
*/
public var stepLengthMs(get, never):Float;
function get_stepLengthMs():Float
{
return beatLengthMs / timeSignatureNumerator;
return beatLengthMs / Constants.STEPS_PER_BEAT;
}
/**
@@ -246,7 +257,7 @@ class Conductor
function get_instrumentalOffsetSteps():Float
{
var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator;
var startingStepLengthMs:Float = (((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) * (4 / timeSignatureDenominator)) / Constants.STEPS_PER_BEAT;
return instrumentalOffset / startingStepLengthMs;
}
@@ -288,26 +299,23 @@ class Conductor
}
/**
* The number of beats in a measure. May be fractional depending on the time signature.
* The number of beats in a measure.
*/
public var beatsPerMeasure(get, never):Float;
function get_beatsPerMeasure():Float
{
// NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure
return stepsPerMeasure / Constants.STEPS_PER_BEAT;
return timeSignatureNumerator;
}
/**
* The number of steps in a measure.
* TODO: I don't think this can be fractional?
*/
public var stepsPerMeasure(get, never):Int;
function get_stepsPerMeasure():Int
{
// TODO: Is this always an integer?
return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT);
return Std.int(timeSignatureNumerator * Constants.STEPS_PER_BEAT);
}
/**
@@ -373,7 +381,9 @@ class Conductor
/**
* The constructor.
*/
public function new() {}
public function new()
{
}
/**
* Forcibly defines the current BPM of the song.
@@ -389,11 +399,11 @@ class Conductor
{
if (bpm != null)
{
trace('[CONDUCTOR] Forcing BPM to ${bpm}');
log('Forcing BPM to ${bpm}');
}
else
{
trace('[CONDUCTOR] Resetting BPM to default');
log('Resetting BPM to default');
}
this.bpmOverride = bpm;
@@ -452,7 +462,7 @@ class Conductor
if (currentTimeChange == null && bpmOverride == null && FlxG.sound.music != null)
{
trace('WARNING: Conductor is broken, timeChanges is empty.');
log(' WARNING '.warning() + 'Conductor is broken, timeChanges is empty.');
}
else if (currentTimeChange != null && this.songPosition > 0.0)
{
@@ -460,7 +470,7 @@ class Conductor
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT)
+ (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
this.currentMeasureTime = currentStepTime / stepsPerMeasure;
this.currentMeasureTime = getTimeInMeasures(this.songPosition);
this.currentStep = Math.floor(currentStepTime);
this.currentBeat = Math.floor(currentBeatTime);
this.currentMeasure = Math.floor(currentMeasureTime);
@@ -517,7 +527,7 @@ class Conductor
/**
* Can be called in-between frames, usually for input related things
* that can potentially get processed on exact milliseconds/timestmaps.
* that can potentially get processed on exact milliseconds/timestamps.
* 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
@@ -526,12 +536,9 @@ class Conductor
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;
}
@@ -566,7 +573,8 @@ class Conductor
{
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
songTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
+ ((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
+
((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC * (prevTimeChange.timeSignatureDen / 4)),
4);
}
}
@@ -574,15 +582,118 @@ class Conductor
timeChanges.push(songTimeChange);
}
if (timeChanges.length > 0)
if (timeChanges.length == 1)
{
trace('Done mapping time changes: ${timeChanges}');
log('Done mapping single time change to ${timeChanges[0].bpm} BPM');
}
else if (timeChanges.length > 1)
{
log('Done mapping ${timeChanges.length} time changes (starting at ${timeChanges[0].bpm} BPM)');
}
else
{
log(' WARNING '.warning() + ' Conductor mapped no time changes?');
}
// Update currentStepTime
this.update(this.songPosition, false);
}
/**
* Given a time in milliseconds, return a time in measures.
* @param ms The time in milliseconds.
* @return The time in measures.
*/
public function getTimeInMeasures(ms:Float):Float
{
if (timeChanges.length == 0)
{
// Assume a constant BPM equal to the forced value.
return ms / stepLengthMs / stepsPerMeasure;
}
else
{
var resultMeasureTime:Float = 0;
ms = ms < 0 ? 0 : ms;
var lastTimeChange:SongTimeChange = timeChanges[0];
var i:Int = -1;
for (timeChange in timeChanges)
{
if (ms >= timeChange.timeStamp)
{
// We do NOT want to add the current time change's MEASURE LENGTH to the result, same for if we're inside the song's last time change.
if (ms < timeChange.timeStamp || i == timeChanges.length - 1)
{
// However, we still want this for the ending calculation.
lastTimeChange = timeChange;
break;
}
var currentStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
var currentStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
resultMeasureTime += (timeChange.timeStamp - lastTimeChange.timeStamp) / currentStepLengthMs / currentStepsPerMeasure;
lastTimeChange = timeChange;
}
i++;
}
var remainingStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
var remainingStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
var remainingFractionalMeasure:Float = (ms - lastTimeChange.timeStamp) / remainingStepLengthMs / remainingStepsPerMeasure;
resultMeasureTime += remainingFractionalMeasure;
return resultMeasureTime;
}
}
/**
* Given a time in measures and fractional measures, return a time in milliseconds.
* @param measureTime The time in measures.
* @return The time in milliseconds.
*/
public function getMeasureTimeInMs(measureTime:Float):Float
{
if (timeChanges.length == 0)
{
// Assume a constant BPM equal to the forced value.
return measureTime * stepLengthMs * stepsPerMeasure;
}
else
{
var resultMs:Float = 0;
measureTime = measureTime < 0 ? 0 : measureTime;
var lastTimeChange:SongTimeChange = timeChanges[0];
var i:Int = -1;
for (timeChange in timeChanges)
{
var currentTimeChangeMeasureTime:Float = getTimeInMeasures(timeChange.timeStamp);
if (measureTime >= currentTimeChangeMeasureTime)
{
// We do NOT want to add the current time change's MEASURE LENGTH to the result, same for if we're inside the song's last time change.
if (measureTime < currentTimeChangeMeasureTime || i == timeChanges.length - 1)
{
// However, we still want this for the ending calculation.
lastTimeChange = timeChange;
break;
}
var currentStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
var currentStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
resultMs += (currentTimeChangeMeasureTime - getTimeInMeasures(lastTimeChange.timeStamp)) * currentStepLengthMs * currentStepsPerMeasure;
lastTimeChange = timeChange;
}
i++;
}
var remainingStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
var remainingStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
var remainingFractionalMeasure:Float = (measureTime - getTimeInMeasures(lastTimeChange.timeStamp)) * remainingStepLengthMs * remainingStepsPerMeasure;
resultMs += remainingFractionalMeasure;
return resultMs;
}
}
/**
* Given a time in milliseconds, return a time in steps.
* @param ms The time in milliseconds.
@@ -598,23 +709,28 @@ class Conductor
else
{
var resultStep:Float = 0;
ms = ms < 0 ? 0 : ms;
var lastTimeChange:SongTimeChange = timeChanges[0];
var i:Int = -1;
for (timeChange in timeChanges)
{
if (ms >= timeChange.timeStamp)
{
// We do NOT want to add the current time change's STEP LENGTH to the result, same for if we're inside the song's last time change.
if (ms < timeChange.timeStamp || i == timeChanges.length - 1)
{
// However, we still want this for the ending calculation.
lastTimeChange = timeChange;
break;
}
resultStep += (timeChange.beatTime - lastTimeChange.beatTime) * Constants.STEPS_PER_BEAT;
lastTimeChange = timeChange;
resultStep = lastTimeChange.beatTime * Constants.STEPS_PER_BEAT;
}
else
{
// This time change is after the requested time.
break;
}
i++;
}
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
var lastStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs;
resultStep += resultFractionalStep;
@@ -637,23 +753,28 @@ class Conductor
else
{
var resultMs:Float = 0;
stepTime = stepTime < 0 ? 0 : stepTime;
var lastTimeChange:SongTimeChange = timeChanges[0];
var i:Int = -1;
for (timeChange in timeChanges)
{
if (stepTime >= timeChange.beatTime * Constants.STEPS_PER_BEAT)
{
// We do NOT want to add the current time change's TIME LENGTH to the result, same for if we're inside the song's last time change.
if (stepTime < (timeChange.beatTime * Constants.STEPS_PER_BEAT) || i == timeChanges.length - 1)
{
// However, we still want this for the ending calculation.
lastTimeChange = timeChange;
break;
}
resultMs += timeChange.timeStamp - lastTimeChange.timeStamp;
lastTimeChange = timeChange;
resultMs = lastTimeChange.timeStamp;
}
else
{
// This time change is after the requested time.
break;
}
i++;
}
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
var lastStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
resultMs += (stepTime - lastTimeChange.beatTime * Constants.STEPS_PER_BEAT) * lastStepLengthMs;
return resultMs;
@@ -691,13 +812,69 @@ class Conductor
}
}
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
var lastStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
resultMs += (beatTime - lastTimeChange.beatTime) * lastStepLengthMs * Constants.STEPS_PER_BEAT;
return resultMs;
}
}
/**
* Given a time in milliseconds, return the time change that time is inside of.
* @param ms The time in milliseconds.
* @return The resulting time change.
*/
public function getTimeChange(ms:Float):SongTimeChange
{
if (timeChanges.length == 0)
{
return new SongTimeChange(0, 100);
}
var i:Int = 0;
ms = ms < 0 ? 0 : ms;
for (timeChange in timeChanges)
{
i++;
if ((i == timeChanges.length && ms >= timeChange.timeStamp) || (ms >= timeChange.timeStamp && ms < timeChanges[i].timeStamp))
{
return timeChange;
}
}
return new SongTimeChange(0, 100);
}
/**
* An all-in-one function for getting either a step, beat, or measure's length in milliseconds from a given time change.
* @param ms The time in milliseconds. The time change is determined by this.
* @param type The type of length to return. Either "step", "beat", or "measure" works, along with their first character.
* @return The length of a step/beat/measure in milliseconds.
*/
public function getTypeLengthAtMs(ms:Float, type:String = "beat"):Float
{
if (timeChanges.length == 0) return 0;
var wantedTimeChange:SongTimeChange = timeChanges[0];
for (timeChange in timeChanges)
{
if (ms >= timeChange.timeStamp)
{
wantedTimeChange = timeChange;
}
else
{
// This time change is after the requested time.
break;
}
}
var wantedBeatLengthMs:Float = ((Constants.SECS_PER_MIN / wantedTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / wantedTimeChange.timeSignatureDen);
return switch (type.toLowerCase())
{
case "measure", "m": wantedBeatLengthMs * wantedTimeChange.timeSignatureNum;
case "beat", "b": wantedBeatLengthMs;
case "step", "s": wantedBeatLengthMs / Constants.STEPS_PER_BEAT;
default: wantedBeatLengthMs;
}
}
/**
* Adds Conductor fields to the Flixel debugger variable display.
* @param conductorToUse The conductor to use. Defaults to `Conductor.instance`.
@@ -712,4 +889,9 @@ class Conductor
FlxG.watch.addQuick('currentBeatTime', target.currentBeatTime);
FlxG.watch.addQuick('currentStepTime', target.currentStepTime);
}
static function log(message:String):Void
{
trace(' CONDUCTOR '.bg_purple().bold() + ' ${message}');
}
}
+74 -35
View File
@@ -12,7 +12,7 @@ import lime.app.Promise;
/**
* Handles caching of textures and sounds for the game.
* TODO: Remove this once Eric finishes the memory system.
* I did this hello, this can be improved later on and I have ideas on how, but for now this functions well enough. -Zack
*/
@:nullSafety
class FunkinMemory
@@ -21,7 +21,6 @@ class FunkinMemory
static var currentCachedTextures:Map<String, FlxGraphic> = [];
static var previousCachedTextures:Map<String, FlxGraphic> = [];
// waow
static var permanentCachedSounds:Map<String, Sound> = [];
static var currentCachedSounds:Map<String, Sound> = [];
static var previousCachedSounds:Map<String, Sound> = [];
@@ -97,6 +96,7 @@ class FunkinMemory
/**
* Clears the current texture and sound caches.
* @param callGarbageCollector Whether to call the system's garbage collector after purging.
*/
public static inline function purgeCache(callGarbageCollector:Bool = false):Void
{
@@ -135,7 +135,7 @@ class FunkinMemory
return;
}
trace('Successfully cached graphic: $key');
log('Cached asset $key');
graphic.persist = true;
currentCachedTextures.set(key, graphic);
forceRender(graphic);
@@ -156,36 +156,13 @@ class FunkinMemory
return;
}
trace('Successfully cached graphic: $key');
log('Cached graphic $key');
graphic.persist = true;
permanentCachedTextures.set(key, graphic);
forceRender(graphic);
currentCachedTextures = permanentCachedTextures;
currentCachedTextures = permanentCachedTextures.copy();
}
/**
* Forces the GPU to load and upload a FlxGraphic.
*/
private static function forceRender(graphic:FlxGraphic):Void
{
if (graphic == null) return;
var bmp:Null<FlxGraphic> = FlxG.bitmap.get(graphic.key);
if (bmp != null && bmp.bitmap != null) var _:Int = bmp.bitmap.width; // Trigger
// Draws sprite and actually caches it.
var sprite = new flixel.FlxSprite();
sprite.loadGraphic(graphic);
sprite.draw(); // Draw sprite and load it into game's memory.
sprite.destroy();
}
/**
* Checks, if graphic with given path cached in memory.
*/
public static inline function isGraphicCached(path:String):Bool
return permanentCachedTextures.exists(path) || currentCachedTextures.exists(path) || previousCachedTextures.exists(path);
public static function getCachedGraphic(path:String):Null<FlxGraphic>
{
if (permanentCachedTextures.exists(path)) return permanentCachedTextures.get(path);
@@ -200,7 +177,7 @@ class FunkinMemory
*/
public inline static function preparePurgeTextureCache():Void
{
previousCachedTextures = currentCachedTextures;
previousCachedTextures = currentCachedTextures.copy();
for (graphicKey in previousCachedTextures.keys())
{
@@ -210,7 +187,7 @@ class FunkinMemory
}
}
currentCachedTextures = permanentCachedTextures;
currentCachedTextures = permanentCachedTextures.copy();
}
/**
@@ -232,6 +209,7 @@ class FunkinMemory
if (graphic != null)
{
FlxG.bitmap.remove(graphic);
graphic.persist = false;
graphic.destroy();
previousCachedTextures.remove(graphicKey);
Assets.cache.clear(graphicKey);
@@ -249,7 +227,7 @@ class FunkinMemory
{
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
if (obj == null || obj.persist || permanentCachedTextures.exists(key) || key.contains("fonts"))
if (obj == null || (obj.persist && permanentCachedTextures.exists(key)) || key.contains("fonts"))
{
continue;
}
@@ -261,6 +239,7 @@ class FunkinMemory
if (key.contains(purgeEntry))
{
FlxG.bitmap.removeKey(key);
obj.persist = false;
obj.destroy();
}
}
@@ -268,8 +247,42 @@ class FunkinMemory
}
}
/**
* Forces the GPU to load and upload a FlxGraphic.
* @param graphic The graphic to force render.
*/
private static function forceRender(graphic:FlxGraphic):Void
{
if (graphic == null) return;
var bmp:Null<FlxGraphic> = FlxG.bitmap.get(graphic.key);
if (bmp != null && bmp.bitmap != null) var _:Int = bmp.bitmap.width; // Trigger
// Draws sprite and actually caches it.
var sprite = new flixel.FlxSprite();
sprite.loadGraphic(graphic);
sprite.draw(); // Draw sprite and load it into game's memory.
graphic.bitmap?.getTexture(FlxG.stage.context3D); // Just in case that didn't work...
sprite.destroy();
}
/**
* Determine whether the texture with the given key is cached.
* @param key The key of the texture to check.
* @return Whether the texture is cached.
*/
public static function isTextureCached(key:String):Bool
{
return FlxG.bitmap.get(key) != null
&& (permanentCachedTextures.exists(key) || currentCachedTextures.exists(key) || previousCachedTextures.exists(key));
}
///// NOTE STYLE //////
/**
* Caches all assets for the given note style.
* @param style The note style to cache.
*/
public static function cacheNoteStyle(style:NoteStyle):Void
{
// TODO: Texture paths should fall back to the default values.
@@ -312,6 +325,10 @@ class FunkinMemory
///// SOUND //////
/**
* Caches a sound with the given key.
* @param key The key of the sound to cache.
*/
public static function cacheSound(key:String):Void
{
if (currentCachedSounds.exists(key)) return;
@@ -331,6 +348,10 @@ class FunkinMemory
currentCachedSounds.set(key, sound);
}
/**
* Permanently caches a sound with the given key.
* @param key The key of the sound to cache.
*/
public static function permanentCacheSound(key:String):Void
{
if (permanentCachedSounds.exists(key)) return;
@@ -343,9 +364,12 @@ class FunkinMemory
if (sound != null) currentCachedSounds.set(key, sound);
}
/**
* Prepares the cache for purging unused sounds.
*/
public static function preparePurgeSoundCache():Void
{
previousCachedSounds = currentCachedSounds;
previousCachedSounds = currentCachedSounds.copy();
for (key in previousCachedSounds.keys())
{
@@ -355,7 +379,7 @@ class FunkinMemory
}
}
currentCachedSounds = permanentCachedSounds;
currentCachedSounds = permanentCachedSounds.copy();
}
/**
@@ -392,6 +416,9 @@ class FunkinMemory
///// MISC /////
/**
* Clears all Freeplay assets from memory.
*/
public static inline function clearFreeplay():Void
{
var keysToRemove:Array<String> = [];
@@ -408,7 +435,7 @@ class FunkinMemory
@:privateAccess
for (key in keysToRemove)
{
trace('Cleaning up $key');
log('Cleaning asset $key');
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
if (obj != null)
{
@@ -423,6 +450,9 @@ class FunkinMemory
purgeSoundCache();
}
/**
* Clears all sticker assets from memory.
*/
public static inline function clearStickers():Void
{
var keysToRemove:Array<String> = [];
@@ -439,7 +469,7 @@ class FunkinMemory
@:privateAccess
for (key in keysToRemove)
{
trace('Cleaning up $key');
log('Cleaning asset $key');
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
if (obj != null)
{
@@ -450,4 +480,13 @@ class FunkinMemory
Assets.cache.clear(key);
}
}
/**
* Sends a trace with fancy ANSI colors.
* @param message The message to log.
*/
private static function log(message:String):Void
{
trace(' MEMORY '.bg_bright_lilac().bold() + ' ${message}');
}
}
+13 -14
View File
@@ -49,20 +49,19 @@ abstract Tallies(RawTallies)
{
public function new()
{
this =
{
combo: 0,
missed: 0,
shit: 0,
bad: 0,
good: 0,
sick: 0,
totalNotes: 0,
totalNotesHit: 0,
maxCombo: 0,
score: 0,
isNewHighscore: false
}
this = {
combo: 0,
missed: 0,
shit: 0,
bad: 0,
good: 0,
sick: 0,
totalNotes: 0,
totalNotesHit: 0,
maxCombo: 0,
score: 0,
isNewHighscore: false
}
}
}
+157 -71
View File
@@ -20,11 +20,12 @@ import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongRegistry;
import funkin.data.stickers.StickerRegistry;
import funkin.play.event.SongEventHelper;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.story.level.LevelRegistry;
import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.PlayStatePlaylist;
import funkin.ui.debug.charting.ChartEditorState;
@@ -37,6 +38,7 @@ import funkin.util.macro.MacroUtil;
import funkin.util.TrackerUtil;
import funkin.util.WindowUtil;
import openfl.display.BitmapData;
import funkin.ui.debug.playtest.ChartPlaytestMenu;
#if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient;
#end
@@ -118,6 +120,11 @@ class InitState extends FlxState
funkin.mobile.util.InAppReviewUtil.init();
#end
#if FEATURE_MOBILE_WEBVIEW
// Setup WebView
funkin.mobile.util.WebViewUtil.init();
#end
#if android
// Setup Callback util.
funkin.external.android.CallbackUtil.init();
@@ -125,9 +132,16 @@ class InitState extends FlxState
#if ios
// Setup Audio session
funkin.external.ios.AudioSession.initialize();
funkin.external.apple.AudioSession.initialize();
#end
#if mobile
// Setup Mobile FNFC launcher.
funkin.mobile.util.FNFCUtil.init();
#end
SongEventHelper.generateEaseGraphsBitmaps();
// This ain't a pixel art game! (most of the time)
FlxSprite.defaultAntialiasing = true;
@@ -147,6 +161,11 @@ class InitState extends FlxState
// Set the game to a lower frame rate while it is in the background.
FlxG.game.focusLostFramerate = 30;
// Persist controls inputs between states.
// Without this, the game would release your keybinds when you switch states,
// and then act like you released and re-pressed them the frame after.
FlxG.inputs.resetOnStateSwitch = false;
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
FlxG.fixedTimestep = false;
@@ -169,7 +188,8 @@ class InitState extends FlxState
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxG.signals.gameResized.add(function(width:Int, height:Int) {
FlxG.signals.gameResized.add(function(width:Int, height:Int)
{
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), tileData,
@@ -180,10 +200,12 @@ class InitState extends FlxState
// Since we don't really need VSync on Android we're gonna forcefully disable it on these signals for now
// This is fixed on SDL3 from what I've heared but that doodoo isn't working poperly for Android
#if android
FlxG.signals.focusLost.add(function() {
FlxG.signals.focusLost.add(function()
{
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
});
FlxG.signals.focusGained.add(function() {
FlxG.signals.focusGained.add(function()
{
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
});
#end
@@ -201,11 +223,16 @@ class InitState extends FlxState
#if FEATURE_DISCORD_RPC
DiscordClient.instance.init();
lime.app.Application.current.onExit.add(function(exitCode) {
lime.app.Application.current.onExit.add(function(exitCode)
{
DiscordClient.instance.shutdown();
});
#end
#if FEATURE_LOST_FOCUS_VOLUME
FlxG.signals.focusLost.add(onLostFocus);
FlxG.signals.focusGained.add(onGainFocus);
#end
//
// ANDROID SETUP
//
@@ -267,7 +294,7 @@ class InitState extends FlxState
// Move it to use a BaseRegistry.
CharacterDataParser.loadCharacterCache();
NoteKindManager.loadScripts();
NoteKindManager.initialize();
ModuleHandler.buildModuleCallbacks();
ModuleHandler.loadModuleCache();
@@ -281,6 +308,23 @@ class InitState extends FlxState
#end
}
#if FEATURE_LOST_FOCUS_VOLUME
@:noCompletion var _lastFocusVolume:Null<Float>;
function onLostFocus()
{
if (FlxG.sound.muted || FlxG.sound.volume == 0 || FlxG.autoPause) return;
_lastFocusVolume = FlxG.sound.volume;
FlxG.sound.volume *= Constants.LOST_FOCUS_VOLUME_MULTIPLIER;
}
function onGainFocus()
{
if (FlxG.sound.muted || FlxG.autoPause) return;
if (_lastFocusVolume != null) FlxG.sound.volume = _lastFocusVolume;
}
#end
/**
* Start the game.
*
@@ -322,37 +366,34 @@ class InitState extends FlxState
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
#elseif RESULTS
// -DRESULTS
FlxG.switchState(() -> new funkin.play.ResultState(
{
storyMode: true,
title: "Cum Song Erect by Kawai Sprite",
songId: "cum",
characterId: "pico",
difficultyId: "hard",
isNewHighscore: true,
scoreData:
{
score: 1_234_567,
tallies:
{
sick: 130,
good: 60,
bad: 69,
shit: 69,
missed: 69,
combo: 69,
maxCombo: 69,
totalNotesHit: 140,
totalNotes: 240
}
// 2400 total notes = 7% = LOSS
// 275 total notes = 69% = NICE
// 240 total notes = 79% = GOOD
// 230 total notes = 82% = GREAT
// 210 total notes = 91% = EXCELLENT
// 190 total notes = PERFECT
},
}));
FlxG.switchState(() -> new funkin.play.ResultState({
storyMode: true,
title: "Cum Song Erect by Kawai Sprite",
songId: "cum",
characterId: "pico",
difficultyId: "hard",
isNewHighscore: true,
scoreData: {
score: 1_234_567,
tallies: {
sick: 130,
good: 60,
bad: 69,
shit: 69,
missed: 69,
combo: 69,
maxCombo: 69,
totalNotesHit: 140,
totalNotes: 240
}
// 2400 total notes = 7% = LOSS
// 275 total notes = 69% = NICE
// 240 total notes = 79% = GOOD
// 230 total notes = 82% = GREAT
// 210 total notes = 91% = EXCELLENT
// 190 total notes = PERFECT
},
}));
#elseif ANIMDEBUG
// -DANIMDEBUG
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());
@@ -374,22 +415,63 @@ class InitState extends FlxState
if (params.chart.shouldLoadChart)
{
FlxG.switchState(() -> new ChartEditorState(
{
fnfcTargetPath: params.chart.chartPath,
}));
#if FEATURE_CHART_EDITOR
FlxG.switchState(() -> new ChartEditorState({
fnfcTargetPath: params.chart.chartPath,
}));
#else
FlxG.switchState(() -> new TitleState());
#end
}
else if (params.stage.shouldLoadStage)
{
FlxG.switchState(() -> new StageEditorState(
{
fnfsTargetPath: params.stage.stagePath,
}));
#if FEATURE_STAGE_EDITOR
FlxG.switchState(() -> new StageEditorState({
fnfsTargetPath: params.stage.stagePath,
}));
#else
FlxG.switchState(() -> new TitleState());
#end
}
else if (params.song.shouldLoadSong && params.song.songPath != null)
{
#if sys
FlxG.switchState(() -> new ChartPlaytestMenu(params.song.songPath));
#else
FlxG.switchState(() -> new TitleState());
#end
}
else
{
// FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
#if mobile
funkin.mobile.util.FNFCUtil.onFNFCOpen.add(function(fnfcFile:String)
{
flixel.tweens.FlxTween.globalManager.clear();
flixel.util.FlxTimer.globalManager.clear();
@:nullSafety(Off)
if (FlxG.sound.music != null)
{
FlxG.sound.music.destroy();
FlxG.sound.music = null;
}
FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile));
});
final fnfcFile = funkin.mobile.util.FNFCUtil.queryFNFC();
if (fnfcFile != null)
{
trace('launching FNFC from $fnfcFile');
FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile));
}
else
{
FlxG.switchState(() -> new TitleState());
}
#else
FlxG.switchState(() -> new TitleState());
#end
}
}
@@ -438,11 +520,10 @@ class InitState extends FlxState
}
@:nullSafety(Off) // Cannot unify?
LoadingState.loadPlayState(
{
targetSong: songData,
targetDifficulty: difficultyId,
});
LoadingState.loadPlayState({
targetSong: songData,
targetDifficulty: difficultyId,
});
}
/**
@@ -481,11 +562,10 @@ class InitState extends FlxState
}
@:nullSafety(Off)
LoadingState.loadPlayState(
{
targetSong: targetSong,
targetDifficulty: difficultyId,
});
LoadingState.loadPlayState({
targetSong: targetSong,
targetDifficulty: difficultyId,
});
}
@:nullSafety(Off) // Meh, remove when flixel.system.debug.log.LogStyle is null safe
@@ -510,23 +590,11 @@ class InitState extends FlxState
// Disable using ~ to open the console (we use that for the Editor menu)
FlxG.debugger.toggleKeys = [F2];
TrackerUtil.initTrackers();
// Adds an additional Close Debugger button.
// This big obnoxious white button is for MOBILE, so that you can press it
// easily with your finger when debug bullshit pops up during testing lol!
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
FlxG.debugger.visible = false;
// Make errors and warnings less annoying.
// Forcing this always since I have never been happy to have the debugger to pop up
LogStyle.ERROR.openConsole = false;
LogStyle.ERROR.errorSound = null;
LogStyle.WARNING.openConsole = false;
LogStyle.WARNING.errorSound = null;
});
// Adds a red button to the debugger.
// This pauses the game AND the music! This ensures the Conductor stops.
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function()
{
if (FlxG.vcr.paused)
{
FlxG.vcr.resume();
@@ -553,7 +621,8 @@ class InitState extends FlxState
// Adds a blue button to the debugger.
// This skips forward in the song.
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function()
{
FlxG.game.debugger.vcr.onStep();
for (snd in FlxG.sound.list)
@@ -565,6 +634,23 @@ class InitState extends FlxState
FlxG.sound.music.pause();
FlxG.sound.music.time += FlxG.elapsed * 1000;
});
// Adds an additional Close Debugger button.
// This big obnoxious white button is for MOBILE, so that you can press it
// easily with your finger when debug bullshit pops up during testing lol!
#if mobile
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function()
{
FlxG.debugger.visible = false;
// Make errors and warnings less annoying.
// Forcing this always since I have never been happy to have the debugger to pop up
LogStyle.ERROR.openConsole = false;
LogStyle.ERROR.errorSound = null;
LogStyle.WARNING.openConsole = false;
LogStyle.WARNING.errorSound = null;
});
#end // mobile big butotn crap
#end
}
+59 -1
View File
@@ -1,13 +1,17 @@
package funkin;
import flixel.graphics.frames.FlxAtlasFrames;
import animate.FlxAnimateFrames;
import funkin.graphics.FunkinSprite.AtlasSpriteSettings;
import openfl.utils.AssetType;
import funkin.util.macro.ConsoleMacro;
import haxe.io.Path;
/**
* A core class which handles determining asset paths.
*/
@:nullSafety
class Paths
class Paths implements ConsoleClass
{
static var currentLevel:Null<String> = null;
@@ -103,6 +107,11 @@ class Paths
return getPath('data/$key.json', TEXT, library);
}
public static function srt(key:String, ?library:String, ?directory:String = "data/"):String
{
return getPath('$directory$key.srt', TEXT, library);
}
public static function sound(key:String, ?library:String):String
{
return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library);
@@ -120,6 +129,13 @@ class Paths
public static function videos(key:String, ?library:String):String
{
final path:Path = new Path(key);
if (path.ext != null)
{
return getPath('videos/${path.file}.${path.ext}', BINARY, library ?? 'videos');
}
return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library ?? 'videos');
}
@@ -163,6 +179,48 @@ class Paths
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
}
public static function getAnimateAtlas(key:String, ?library:String, settings:AtlasSpriteSettings):FlxAnimateFrames
{
var assetLibrary:String = library ?? "";
var graphicKey:String = "";
if (assetLibrary != "")
{
graphicKey = Paths.animateAtlas(key, assetLibrary);
}
else
{
graphicKey = Paths.animateAtlas(key);
}
var validatedSettings:AtlasSpriteSettings = {
swfMode: settings?.swfMode ?? false,
cacheOnLoad: settings?.cacheOnLoad ?? false,
filterQuality: settings?.filterQuality ?? MEDIUM,
spritemaps: settings?.spritemaps ?? null,
metadataJson: settings?.metadataJson ?? null,
cacheKey: settings?.cacheKey ?? null,
uniqueInCache: settings?.uniqueInCache ?? false,
onSymbolCreate: settings?.onSymbolCreate ?? null,
applyStageMatrix: settings?.applyStageMatrix ?? false,
useRenderTexture: settings?.useRenderTexture ?? false
};
// Validate asset path.
if (!Assets.exists('${graphicKey}/Animation.json'))
{
throw 'No Animation.json file exists at the specified path (${graphicKey})';
}
return FlxAnimateFrames.fromAnimate(graphicKey, validatedSettings.spritemaps, validatedSettings.metadataJson, validatedSettings.cacheKey,
validatedSettings.uniqueInCache, {
swfMode: validatedSettings.swfMode,
cacheOnLoad: validatedSettings.cacheOnLoad,
filterQuality: validatedSettings.filterQuality,
onSymbolCreate: validatedSettings.onSymbolCreate
});
}
public static function getPackerAtlas(key:String, ?library:String):FlxAtlasFrames
{
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
+91 -58
View File
@@ -7,6 +7,7 @@ import funkin.mobile.util.InAppPurchasesUtil;
import funkin.save.Save;
import funkin.util.WindowUtil;
import funkin.util.HapticUtil.HapticsMode;
import funkin.ui.debug.FunkinDebugDisplay.DebugDisplayMode;
/**
* A core class which provides a store of user-configurable, globally relevant values.
@@ -43,7 +44,7 @@ class Preferences
#else
var save:Save = Save.instance;
save.options.framerate = value;
save.flush();
Save.system.flush();
FlxG.updateFramerate = value;
FlxG.drawFramerate = value;
return value;
@@ -73,7 +74,7 @@ class Preferences
var save:Save = Save.instance;
save.options.naughtyness = value;
save.flush();
Save.system.flush();
return value;
}
@@ -92,7 +93,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.downscroll = value;
save.flush();
Save.system.flush();
return value;
}
@@ -111,7 +112,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.flashingLights = value;
save.flush();
Save.system.flush();
return value;
}
@@ -130,35 +131,54 @@ class Preferences
{
var save:Save = Save.instance;
save.options.zoomCamera = value;
save.flush();
Save.system.flush();
return value;
}
/**
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
* Always disabled on mobile.
* @default `false`
* Always disabled on release mobile builds.
* @default `Off`
*/
public static var debugDisplay(get, set):Bool;
public static var debugDisplay(get, set):DebugDisplayMode;
static function get_debugDisplay():Bool
static function get_debugDisplay():DebugDisplayMode
{
#if mobile
return false;
#if NO_FEATURE_DEBUG_DISPLAY
return DebugDisplayMode.Off;
#end
return Save?.instance?.options?.debugDisplay ?? false;
return Save?.instance?.options?.debugDisplay ?? 'Off';
}
static function set_debugDisplay(value:Bool):Bool
static function set_debugDisplay(value:DebugDisplayMode):DebugDisplayMode
{
if (value != Save.instance.options.debugDisplay)
{
toggleDebugDisplay(value);
}
if (value != Save.instance.options.debugDisplay) setDebugDisplayMode(value);
var save = Save.instance;
save.options.debugDisplay = value;
save.flush();
Save.system.flush();
return value;
}
/**
* Opacity of the debug display's background.
* @default `50`
*/
public static var debugDisplayBGOpacity(get, set):Int;
static function get_debugDisplayBGOpacity():Int
{
return Save?.instance?.options?.debugDisplayBGOpacity ?? 50;
}
static function set_debugDisplayBGOpacity(value:Int):Int
{
setDebugDisplayBGOpacity(value / 100);
var save:Save = Save.instance;
save.options.debugDisplayBGOpacity = value;
Save.system.flush();
return value;
}
@@ -199,7 +219,7 @@ class Preferences
var save:Save = Save.instance;
save.options.hapticsMode = string;
save.flush();
Save.system.flush();
return value;
}
@@ -218,7 +238,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.hapticsIntensityMultiplier = value;
save.flush();
Save.system.flush();
return value;
}
@@ -232,7 +252,7 @@ class Preferences
static function get_autoPause():Bool
{
#if mobile
return true;
return false;
#end
return Save?.instance?.options?.autoPause ?? true;
}
@@ -243,7 +263,7 @@ class Preferences
var save:Save = Save.instance;
save.options.autoPause = value;
save.flush();
Save.system.flush();
return value;
}
@@ -262,7 +282,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.autoFullscreen = value;
save.flush();
Save.system.flush();
return value;
}
@@ -282,7 +302,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.globalOffset = value;
save.flush();
Save.system.flush();
return value;
}
@@ -329,7 +349,7 @@ class Preferences
var save:Save = Save.instance;
save.options.vsyncMode = string;
save.flush();
Save.system.flush();
return value;
}
@@ -351,7 +371,7 @@ class Preferences
var save:Save = Save.instance;
save.options.unlockedFramerate = value;
save.flush();
Save.system.flush();
return value;
}
@@ -364,7 +384,8 @@ class Preferences
{
var currTime = Date.now().getTime();
var timeToCall = 0;
var id = js.Browser.window.setTimeout(function() {
var id = js.Browser.window.setTimeout(function()
{
callback(currTime + timeToCall);
}, timeToCall);
return id;
@@ -391,7 +412,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.strumlineBackgroundOpacity = value;
save.flush();
Save.system.flush();
return value;
}
@@ -410,7 +431,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.screenshot.shouldHideMouse = value;
save.flush();
Save.system.flush();
return value;
}
@@ -429,7 +450,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.screenshot.fancyPreview = value;
save.flush();
Save.system.flush();
return value;
}
@@ -448,7 +469,7 @@ class Preferences
{
var save:Save = Save.instance;
save.options.screenshot.previewOnSave = value;
save.flush();
Save.system.flush();
return value;
}
@@ -459,18 +480,15 @@ class Preferences
{
// Apply the autoPause setting (enables automatic pausing on focus lost).
FlxG.autoPause = Preferences.autoPause;
// WindowUtil.setVSyncMode(Preferences.vsyncMode);
// Apply the debugDisplay setting (enables the FPS and RAM display).
toggleDebugDisplay(Preferences.debugDisplay);
setDebugDisplayMode(Preferences.debugDisplay);
setDebugDisplayBGOpacity(Preferences.debugDisplayBGOpacity / 100);
#if web
toggleFramerateCap(Preferences.unlockedFramerate);
#end
#if desktop
// Apply the autoFullscreen setting (launches the game in fullscreen automatically)
FlxG.fullscreen = Preferences.autoFullscreen;
#end
#if mobile
// Apply the allowScreenTimeout setting.
lime.system.System.allowScreenTimeout = Preferences.screenTimeout;
@@ -485,26 +503,41 @@ class Preferences
#end
}
static function toggleDebugDisplay(show:Bool):Void
public static function setDebugDisplayMode(mode:DebugDisplayMode):Void
{
if (show)
{
// Enable the debug display.
FlxG.game.parent.addChild(Main.fpsCounter);
if (FlxG.game.parent.contains(Main.debugDisplay)) FlxG.game.parent.removeChild(Main.debugDisplay);
#if !html5
FlxG.game.parent.addChild(Main.memoryCounter);
#end
}
else
{
// Disable the debug display.
FlxG.game.parent.removeChild(Main.fpsCounter);
if (mode == DebugDisplayMode.Off) return;
#if !html5
FlxG.game.parent.removeChild(Main.memoryCounter);
#end
}
Main.debugDisplay.isAdvanced = (mode == DebugDisplayMode.Advanced);
FlxG.game.parent.addChild(Main.debugDisplay);
}
static function setDebugDisplayBGOpacity(value:Float):Void
{
if (Main.debugDisplay == null) return;
Main.debugDisplay.backgroundOpacity = value;
}
/**
* If enabled, subtitles will appear during some songs and cutscenes.
* @default `true`
*/
public static var subtitles(get, set):Bool;
static function get_subtitles():Bool
{
return Save?.instance?.options?.subtitles ?? true;
}
static function set_subtitles(value:Bool):Bool
{
var save:Save = Save.instance;
save.options.subtitles = value;
Save.system.flush();
return value;
}
#if mobile
@@ -525,7 +558,7 @@ class Preferences
var save:Save = Save.instance;
save.mobileOptions.screenTimeout = value;
save.flush();
Save.system.flush();
return value;
}
@@ -544,7 +577,7 @@ class Preferences
{
var save:Save = Save.instance;
save.mobileOptions.controlsScheme = value;
save.flush();
Save.system.flush();
return value;
}
@@ -569,7 +602,7 @@ class Preferences
{
var save:Save = Save.instance;
save.mobileOptions.noAds = value;
save.flush();
Save.system.flush();
return value;
}
#end
+20 -24
View File
@@ -1,6 +1,5 @@
package funkin.api.discord;
import funkin.util.macro.EnvironmentConfigMacro;
#if FEATURE_DISCORD_RPC
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types.DiscordButton;
@@ -9,10 +8,12 @@ import hxdiscord_rpc.Types.DiscordRichPresence;
import hxdiscord_rpc.Types.DiscordUser;
import sys.thread.Thread;
@:build(funkin.util.macro.EnvironmentMacro.build())
@:nullSafety
class DiscordClient
{
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
@:envField
static final DISCORD_CLIENT_ID:Null<String>;
public static var instance(get, never):DiscordClient;
static var _instance:Null<DiscordClient> = null;
@@ -28,9 +29,9 @@ class DiscordClient
private function new()
{
trace('[DISCORD] Initializing event handlers...');
trace(' DISCORD '.bold().bg_blue() + ' Initializing event handlers...');
handlers = DiscordEventHandlers.create();
handlers = new DiscordEventHandlers();
handlers.ready = cpp.Function.fromStaticFunction(onReady);
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
@@ -39,7 +40,7 @@ class DiscordClient
public function init():Void
{
trace('[DISCORD] Initializing connection...');
trace(' DISCORD '.bold().bg_blue() + ' Initializing connection...');
if (!hasValidCredentials())
{
@@ -49,7 +50,7 @@ class DiscordClient
@:nullSafety(Off)
{
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
Discord.Initialize(DISCORD_CLIENT_ID, cpp.RawPointer.addressOf(handlers), false, "");
}
createDaemon();
@@ -60,7 +61,7 @@ class DiscordClient
*/
static function hasValidCredentials():Bool
{
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
return !(DISCORD_CLIENT_ID == null || DISCORD_CLIENT_ID == "" || (DISCORD_CLIENT_ID != null && DISCORD_CLIENT_ID.contains(" ")));
}
var daemon:Null<Thread> = null;
@@ -78,26 +79,21 @@ class DiscordClient
Discord.updateConnection();
#end
Discord.runCallbacks();
Discord.RunCallbacks();
Sys.sleep(2);
}
}
public function shutdown():Void
{
trace('[DISCORD] Shutting down...');
trace(' DISCORD '.bold().bg_blue() + ' Shutting down...');
Discord.shutdown();
Discord.Shutdown();
}
public function setPresence(params:DiscordClientPresenceParams):Void
{
Discord.updatePresence(buildPresence(params));
}
function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence
{
var presence = DiscordRichPresence.create();
var presence:DiscordRichPresence = new DiscordRichPresence();
// Presence should always be playing the game.
presence.type = DiscordActivityType_Playing;
@@ -128,23 +124,23 @@ class DiscordClient
// presence.startTimestamp = time - 10;
// presence.endTimestamp = time + 30;
final button1:DiscordButton = DiscordButton.create();
final button1:DiscordButton = new DiscordButton();
button1.label = "Play on Web";
button1.url = Constants.URL_NEWGROUNDS;
presence.buttons[0] = button1;
final button2:DiscordButton = DiscordButton.create();
final button2:DiscordButton = new DiscordButton();
button2.label = "Download";
button2.url = Constants.URL_ITCH;
presence.buttons[1] = button2;
return presence;
Discord.UpdatePresence(cpp.RawConstPointer.addressOf(presence));
}
// TODO: WHAT THE FUCK get this pointer bullfuckery out of here
private static function onReady(request:cpp.RawConstPointer<DiscordUser>):Void
{
trace('[DISCORD] Client has connected!');
trace(' DISCORD '.bold().bg_blue() + ' Client has connected!');
final username:String = request[0].username;
final globalName:String = request[0].username;
@@ -152,22 +148,22 @@ class DiscordClient
if (discriminator != null && discriminator != 0)
{
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
trace(' DISCORD '.bold().bg_blue() + ' User: ${username}#${discriminator} (${globalName})');
}
else
{
trace('[DISCORD] User: @${username} (${globalName})');
trace(' DISCORD '.bold().bg_blue() + ' User: @${username} (${globalName})');
}
}
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
{
trace('[DISCORD] Client has disconnected! ($errorCode) "${cast (message, String)}"');
trace(' DISCORD '.bold().bg_blue() + ' Client has disconnected! ($errorCode) "${cast (message, String)}"');
}
private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void
{
trace('[DISCORD] Client has received an error! ($errorCode) "${cast (message, String)}"');
trace(' DISCORD '.bold().bg_blue() + ' Client has received an error! ($errorCode) "${cast (message, String)}"');
}
// public var type(get, set):DiscordActivityType;
+5 -5
View File
@@ -39,21 +39,21 @@ class Events
switch (outcome)
{
case SUCCESS(data):
trace('[NEWGROUNDS] Logged event: ${data.eventName}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logged event: ${data.eventName}');
case FAIL(outcome):
switch (outcome)
{
case HTTP(error):
trace('[NEWGROUNDS] HTTP error while logging event: ${error}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' HTTP error while logging event: ${error}');
case RESPONSE(error):
trace('[NEWGROUNDS] Response error (${error.code}) while logging event: ${error.message}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Response error (${error.code}) while logging event: ${error.message}');
case RESULT(error):
switch (error.code)
{
case 103: // Invalid custom event name
trace('[NEWGROUNDS] Invalid custom event name: ${eventName}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Invalid custom event name: ${eventName}');
default:
trace('[NEWGROUNDS] Result error (${error.code}) while logging event: ${error.message}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Result error (${error.code}) while logging event: ${error.message}');
}
}
}
+142 -93
View File
@@ -2,22 +2,29 @@ package funkin.api.newgrounds;
#if FEATURE_NEWGROUNDS
import io.newgrounds.Call.CallError;
import io.newgrounds.components.ScoreBoardComponent;
import io.newgrounds.components.ScoreBoardComponent.Period;
import io.newgrounds.objects.Score;
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
import io.newgrounds.objects.User;
import io.newgrounds.objects.events.Outcome;
import io.newgrounds.utils.ScoreBoardList;
/**
* Handles interactions with the leaderboards on the Newgrounds website.
*/
@:nullSafety
class Leaderboards
{
/**
* Retrieve the leaderboard data via the Newgrounds API.
* @return The leaderboard data.
*/
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
{
var leaderboardList:Null<ScoreBoardList> = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null)
{
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Not logged in, cannot fetch medal data!');
return [];
}
@@ -33,24 +40,26 @@ class Leaderboards
public static function submitScore(leaderboard:Leaderboard, score:Int, ?tag:String):Void
{
// Silently reject submissions for unknown leaderboards.
if (leaderboard == Leaderboard.Unknown) return;
if (leaderboard == Leaderboard.Unknown)
{
trace(' NEWGROUNDS '.bold().bg_orange() + ' Unknown leaderboard, skipping score submission...');
return;
}
if (NewgroundsClient.instance.isLoggedIn())
{
var leaderboardList = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null) return;
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
var leaderboardData:Null<LeaderboardData> = listLeaderboardData().get(leaderboard.getId());
if (leaderboardData != null)
{
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void
{
switch (outcome)
{
case SUCCESS:
trace('[NEWGROUNDS] Submitted score!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitted leaderboard score!');
case FAIL(error):
trace('[NEWGROUNDS] Failed to submit score!');
trace(error);
trace(' NEWGROUNDS '.bold().bg_orange() + ' ERROR '.error() + ' Failed to submit leaderboard score!');
trace(' ERROR '.error() + error);
}
});
}
@@ -67,26 +76,24 @@ class Leaderboards
// Silently reject retrieving scores from unknown leaderboards.
if (leaderboard == Leaderboard.Unknown) return;
var leaderboardList = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null) return;
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
var leaderboardData:Null<LeaderboardData> = listLeaderboardData().get(leaderboard.getId());
if (leaderboardData == null) return;
var user:Null<User> = null;
if ((params?.useCurrentUser ?? false) && NewgroundsClient.instance.isLoggedIn()) user = NewgroundsClient.instance.user;
leaderboardData.requestScores(params?.limit ?? 10, params?.skip ?? 0, params?.period ?? ALL, params?.social ?? false, params?.tag, user,
function(outcome:Outcome<CallError>):Void {
function(outcome:Outcome<CallError>):Void
{
switch (outcome)
{
case SUCCESS:
trace('[NEWGROUNDS] Fetched scores!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched scores!');
if (params != null && params.onComplete != null) params.onComplete(leaderboardData.scores);
case FAIL(error):
trace('[NEWGROUNDS] Failed to fetch scores!');
trace(error);
trace(' NEWGROUNDS '.bold().bg_orange() + ' ERROR '.error() + ' Failed to fetch scores!');
trace(' ERROR '.error() + error);
if (params != null && params.onFail != null) params.onFail();
}
});
@@ -94,19 +101,29 @@ class Leaderboards
/**
* Submit a score for a Story Level to Newgrounds.
*
* @param levelId The ID for the story level.
* @param difficultyId The current difficulty.
* @param score The score to submit.
*/
public static function submitLevelScore(levelId:String, difficultyId:String, score:Int):Void
{
var tag = '${difficultyId}';
trace(' NEWGROUNDS '.bold().bg_orange() + 'Submitting score for level "${levelId}"...');
Leaderboards.submitScore(Leaderboard.getLeaderboardByLevel(levelId), score, tag);
}
/**
* Submit a score for a song to Newgrounds.
*
* @param songId The ID for the song.
* @param difficultyId The current difficulty.
* @param score The score to submit.
*/
public static function submitSongScore(songId:String, difficultyId:String, score:Int):Void
{
var tag = '${difficultyId}';
trace(' NEWGROUNDS '.bold().bg_orange() + 'Submitting score for song "${songId}" (${difficultyId})...');
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
}
}
@@ -117,16 +134,33 @@ class Leaderboards
@:nullSafety
class LeaderboardsSandboxed
{
public static function getLeaderboardBySong(songId:String, difficultyId:String)
/**
* Get the leaderboard for a given song and difficulty.
*
* @param songId The ID for the song.
* @param difficultyId The current difficulty, suffixed with the variation, like `easy-pico` or `nightmare`.
* @return The Leaderboard ID for the given level and difficulty.
*/
public static function getLeaderboardBySong(songId:String, difficultyId:String):Leaderboard
{
return Leaderboard.getLeaderboardBySong(songId, difficultyId);
}
public static function getLeaderboardByLevel(levelId:String)
/**
* Get the leaderboard for a given story level and difficulty.
* @param levelId The ID for the story level.
* @return The Leaderboard ID for the given level and difficulty.
*/
public static function getLeaderboardByLevel(levelId:String):Leaderboard
{
return Leaderboard.getLeaderboardByLevel(levelId);
}
/**
* Request to receive scores from Newgrounds.
* @param leaderboard The leaderboard to fetch scores from.
* @param params Additional parameters for fetching the score.
*/
public function requestScores(leaderboard:Leaderboard, params:RequestScoresParams)
{
Leaderboards.requestScores(leaderboard, params);
@@ -180,106 +214,118 @@ typedef RequestScoresParams =
}
#end
/**
* An enumeration of Newgrounds leaderboards for the game's levels and songs.
*/
enum abstract Leaderboard(Int) from Int to Int
{
/**
* Represents an undefined or invalid leaderboard.
*/
var Unknown = -1;
public var Unknown = -1;
//
// STORY LEVELS
//
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14239 #else 9615 #end;
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14240 #else 9616 #end;
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14242 #else 9767 #end;
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14241 #else 9866 #end;
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14243 #else 9956 #end;
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14244 #else 9957 #end;
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14245 #else 14682 #end;
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14237 #else 14683 #end;
// Tutorial only has one song.
public var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14239 #else 9615 #end;
public var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14240 #else 9616 #end;
public var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14242 #else 9767 #end;
public var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14241 #else 9866 #end;
public var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14243 #else 9956 #end;
public var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14244 #else 9957 #end;
public var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14245 #else 14682 #end;
public var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14237 #else 14683 #end;
// Collab 1 only has one song.
//
// SONGS
//
// Tutorial
var Tutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14249 #else 14684 #end;
public var Tutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14249 #else 14684 #end;
// Week 1
var Bopeebo = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14246 #else 9603 #end;
var BopeeboErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14685 #end;
var BopeeboPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14686 #end;
var Fresh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14247 #else 9602 #end;
var FreshErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14687 #end;
var FreshPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14688 #end;
var DadBattle = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14248 #else 9605 #end;
var DadBattleErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14689 #end;
var DadBattlePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14690 #end;
public var Bopeebo = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14246 #else 9603 #end;
public var BopeeboErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14685 #end;
public var BopeeboPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14686 #end;
public var Fresh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14247 #else 9602 #end;
public var FreshErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14687 #end;
public var FreshPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14688 #end;
public var DadBattle = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14248 #else 9605 #end;
public var DadBattleErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14689 #end;
public var DadBattlePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14690 #end;
// Week 2
var Spookeez = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9604 #end;
var SpookeezErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14691 #end;
var SpookeezPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14692 #end;
var South = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9606 #end;
var SouthErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14693 #end;
var SouthPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14694 #end;
var Monster = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14703 #end;
public var Spookeez = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9604 #end;
public var SpookeezErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14691 #end;
public var SpookeezPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14692 #end;
public var South = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9606 #end;
public var SouthErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14693 #end;
public var SouthPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14694 #end;
public var Monster = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14703 #end;
// Week 3
var Pico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9766 #end;
var PicoErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14695 #end;
var PicoPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14696 #end;
var PhillyNice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9769 #end;
var PhillyNiceErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14697 #end;
var PhillyNicePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14698 #end;
var Blammed = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9768 #end;
var BlammedErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14704 #end;
var BlammedPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14705 #end;
public var Pico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9766 #end;
public var PicoErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14695 #end;
public var PicoPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14696 #end;
public var PhillyNice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9769 #end;
public var PhillyNiceErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14697 #end;
public var PhillyNicePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14698 #end;
public var Blammed = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9768 #end;
public var BlammedErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14704 #end;
public var BlammedPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14705 #end;
// Week 4
var SatinPanties = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9869 #end;
var SatinPantiesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14701 #end;
var High = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9867 #end;
var HighErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14699 #end;
var MILF = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9868 #end;
public var SatinPanties = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9869 #end;
public var SatinPantiesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14701 #end;
public var High = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9867 #end;
public var HighErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14699 #end;
public var MILF = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9868 #end;
// Week 5
var Cocoa = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14706 #end;
var CocoaErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14707 #end;
var CocoaPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14708 #end;
var Eggnog = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14709 #end;
var EggnogErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14711 #end;
var EggnogPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14710 #end;
var WinterHorrorland = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14712 #end;
public var Cocoa = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14706 #end;
public var CocoaErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14707 #end;
public var CocoaPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14708 #end;
public var Eggnog = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14709 #end;
public var EggnogErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14711 #end;
public var EggnogPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14710 #end;
public var WinterHorrorland = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14712 #end;
// Week 6
var Senpai = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9958 #end;
var SenpaiErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14713 #end;
var SenpaiPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14716 #end;
var Roses = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9959 #end;
var RosesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14714 #end;
var RosesPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14717 #end;
var Thorns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9960 #end;
var ThornsErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14715 #end;
public var Senpai = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9958 #end;
public var SenpaiErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14713 #end;
public var SenpaiPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14716 #end;
public var Roses = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9959 #end;
public var RosesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14714 #end;
public var RosesPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14717 #end;
public var Thorns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9960 #end;
public var ThornsErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14715 #end;
// Week 7
var Ugh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14718 #end;
var UghErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14722 #end;
var UghPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14721 #end;
var Guns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14719 #end;
var GunsPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14723 #end;
var Stress = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14720 #end;
var StressPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14724 #end;
public var Ugh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14718 #end;
public var UghErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14722 #end;
public var UghPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14721 #end;
public var Guns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14719 #end;
public var GunsPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14723 #end;
public var Stress = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14720 #end;
public var StressPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14724 #end;
// Weekend 1
var Darnell = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14725 #end;
var DarnellErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14727 #end;
var DarnellBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14726 #end;
var LitUp = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14728 #end;
var LitUpBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14729 #end;
var TwoHot = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14730 #end; // Variable names can't start with a number!
var Blazin = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14731 #end;
public var Darnell = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14725 #end;
public var DarnellErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14727 #end;
public var DarnellBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14726 #end;
public var LitUp = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14728 #end;
public var LitUpBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14729 #end;
public var TwoHot = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14730 #end; // Variable names can't start with a number!
public var Blazin = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14731 #end;
// Collab 1
public var Spaghetti = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 15448 #else 15451 #end;
/**
* Get the numeric ID for the leaderboard on the Newgrounds site.
* @return The numeric ID for the leaderboard.
*/
public function getId():Int
{
return this;
@@ -288,7 +334,6 @@ enum abstract Leaderboard(Int) from Int to Int
/**
* Get the leaderboard for a given level and difficulty.
* @param levelId The ID for the story level.
* @param difficulty The current difficulty.
* @return The Leaderboard ID for the given level and difficulty.
*/
public static function getLeaderboardByLevel(levelId:String):Leaderboard
@@ -311,14 +356,16 @@ enum abstract Leaderboard(Int) from Int to Int
return StoryWeek7;
case "weekend1":
return StoryWeekend1;
// Collab 1 has only one song.
default:
return Unknown;
}
}
/**
* Get the leaderboard for a given level and difficulty.
* @param levelId The ID for the story level.
* Get the leaderboard for a given song and difficulty.
*
* @param songId The ID for the song.
* @param difficulty The current difficulty, suffixed with the variation, like `easy-pico` or `nightmare`.
* @return The Leaderboard ID for the given level and difficulty.
*/
@@ -479,6 +526,8 @@ enum abstract Leaderboard(Int) from Int to Int
return TwoHot;
case "blazin":
return Blazin;
case "spaghetti":
return Spaghetti;
default:
return Unknown;
}
+101 -68
View File
@@ -5,51 +5,61 @@ import io.newgrounds.objects.Medal as MedalData;
import funkin.util.plugins.NewgroundsMedalPlugin;
import flixel.graphics.FlxGraphic;
import openfl.display.BitmapData;
import io.newgrounds.utils.MedalList;
import haxe.Json;
/**
* Handles interactions with the medals on the Newgrounds website.
*/
@:nullSafety
class Medals
{
/**
* A cache for the medal data fetched from the local `medals.json`.
*/
public static var medalJSON:Array<MedalJSON> = [];
/**
* Retrieve the leaderboard data via the Newgrounds API.
* @return The leaderboard data.
*/
public static function listMedalData():Map<Medal, MedalData>
{
var medalList = NewgroundsClient.instance.medals;
if (medalList == null)
{
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Not logged in, cannot fetch medal data!');
return [];
}
return @:privateAccess medalList._map?.copy() ?? [];
}
/**
* Awards the player a specific medal, updating their Newgrounds account and
* displaying the corresponding popup.
* @param medal The medal to be awarded.
*/
public static function award(medal:Medal):Void
{
if (NewgroundsClient.instance.isLoggedIn())
{
var medalList = NewgroundsClient.instance.medals;
@:privateAccess
if (medalList == null || medalList._map == null) return;
var medalData:Null<MedalData> = medalList.get(medal.getId());
var medalData:Null<MedalData> = listMedalData().get(medal.getId());
@:privateAccess
if (medalData == null || medalData._data == null)
{
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Could not retrieve data for medal: ${medal}');
return;
}
else if (!medalData.unlocked)
{
trace('[NEWGROUNDS] Awarding medal (${medal}).');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Awarding medal (${medal}).');
medalData.sendUnlock();
// Play the medal unlock animation, but only if the user has not already unlocked it.
#if html5
// Web builds support parsing the bitmap data from the URL directly.
BitmapData.loadFromFile("https:" + medalData.icon).onComplete(function(bmp:BitmapData) {
BitmapData.loadFromFile("https:" + medalData.icon).onComplete(function(bmp:BitmapData)
{
var medalGraphic = FlxGraphic.fromBitmapData(bmp);
medalGraphic.persist = true;
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
@@ -59,7 +69,8 @@ class Medals
// We have to use a medal image from the game files. We use a Base64 encoded image that NG spits out.
// TODO: Wait, don't they give us the medal icon?
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal)
{
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
return medal == jsonMedal.idTest;
#else
@@ -85,15 +96,18 @@ class Medals
}
else
{
trace('[NEWGROUNDS] User already has medal (${medal}).');
trace(' NEWGROUNDS '.bold().bg_orange() + ' User already has medal (${medal}).');
}
}
else
{
trace('[NEWGROUNDS] Attempted to award medal (${medal}), but not logged into Newgrounds.');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Attempted to award medal (${medal}), but not logged into Newgrounds.');
}
}
/**
* Loads the full medal data from local JSON data.
*/
public static function loadMedalJSON():Void
{
var jsonPath = Paths.json('medals');
@@ -102,12 +116,12 @@ class Medals
var parser = new json2object.JsonParser<Array<MedalJSON>>();
parser.ignoreUnknownVariables = false;
trace('[NEWGROUNDS] Parsing local medal data...');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Parsing local medal data...');
parser.fromJson(jsonString, jsonPath);
if (parser.errors.length > 0)
{
trace('[NEWGROUNDS] Failed to parse local medal data!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to parse local medal data!');
for (error in parser.errors)
funkin.data.DataError.printError(error);
medalJSON = [];
@@ -118,17 +132,18 @@ class Medals
}
}
/**
* Fetches the data for a medal from the Newgrounds API.
* @param medal The ID of the medal to fetch.
* @return The fetched medal data.
*/
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
{
var medalList = NewgroundsClient.instance.medals;
@:privateAccess
if (medalList == null || medalList._map == null) return null;
var medalData:Null<MedalData> = medalList.get(medal.getId());
var medalData:Null<MedalData> = listMedalData().get(medal.getId());
@:privateAccess
if (medalData == null || medalData._data == null)
{
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Could not retrieve data for medal: ${medal}');
return null;
}
@@ -144,12 +159,16 @@ class Medals
}
}
/**
* Awards the player a medal for completing a specific story level.
* @param id The level ID for the story level that was completed.
*/
public static function awardStoryLevel(id:String):Void
{
var medal:Medal = Medal.getMedalByStoryLevel(id);
if (medal == Medal.Unknown)
{
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Story level does not have a medal! (${id}).');
return;
}
Medals.award(medal);
@@ -161,16 +180,30 @@ class Medals
*/
class MedalsSandboxed
{
/**
* Fetches the data for a medal from the Newgrounds API.
* @param medal The ID of the medal to fetch.
* @return The fetched medal data.
*/
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
{
return Medals.fetchMedalData(medal);
}
/**
* Retrieves the medal for completing a specific story level.
* @param id The ID for the story level that was completed.
* @return The medal to award.
*/
public static function getMedalByStoryLevel(id:String):Medal
{
return Medal.getMedalByStoryLevel(id);
}
/**
* Lists all medals aside from the `Unknown` one.
* @return An array of all medals.
*/
public static function getAllMedals():Array<Medal>
{
return Medal.getAllMedals();
@@ -219,140 +252,157 @@ typedef MedalJSON =
var icon:String;
}
/**
* An enumeration of all the game's medals.
*/
enum abstract Medal(Int) from Int to Int
{
/**
* Represents an undefined or invalid medal.
*/
var Unknown = -1;
public var Unknown = -1;
/**
* I Said Funkin'!
* Start the game for the first time.
*/
var StartGame = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80894 #else 60960 #end;
public var StartGame = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80894 #else 60960 #end;
/**
* That's How You Do It!
* Beat Tutoria l in Story Mode (on any difficulty).
* Beat Tutorial in Story Mode (on any difficulty).
*/
var StoryTutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80906 #else 83647 #end;
public var StoryTutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80906 #else 83647 #end;
/**
* More Like Daddy Queerest
* Beat Week 1 in Story Mode (on any difficulty).
*/
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80899 #else 60961 #end;
public var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80899 #else 60961 #end;
/**
* IT IS THE SPOOKY MONTH
* Beat Week 2 in Story Mode (on any difficulty).
*/
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80900 #else 83648 #end;
public var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80900 #else 83648 #end;
/**
* Pico Funny
* Beat Week 3 in Story Mode (on any difficulty).
*/
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80901 #else 83649 #end;
public var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80901 #else 83649 #end;
/**
* Mommy Must Murder
* Beat Week 4 in Story Mode (on any difficulty).
*/
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80902 #else 83650 #end;
public var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80902 #else 83650 #end;
/**
* Yule Tide Joy
* Beat Week 5 in Story Mode (on any difficulty).
*/
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80903 #else 83651 #end;
public var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80903 #else 83651 #end;
/**
* A Visual Novelty
* Beat Week 6 in Story Mode (on any difficulty).
*/
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80904 #else 83652 #end;
public var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80904 #else 83652 #end;
/**
* I <3 JohnnyUtah
* Beat Week 7 in Story Mode (on any difficulty).
*/
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80905 #else 83653 #end;
public var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80905 #else 83653 #end;
/**
* Yo, Really Think So?
* Beat Weekend 1 in Story Mode (on any difficulty).
*/
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80907 #else 83654 #end;
public var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80907 #else 83654 #end;
/**
* Eat It Up!
* Beat Collab 1 in Story Mode (on any difficulty).
*/
public var StoryCollab1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 87697 #else 87714 #end;
/**
* Stay Funky
* Press TAB in Freeplay and unlock your first character.
*/
var CharSelect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83633 #else 83655 #end;
public var CharSelect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83633 #else 83655 #end;
/**
* A Challenger Appears
* Beat any Pico remix in Freeplay (on any difficulty).
*/
var FreeplayPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80910 #else 83656 #end;
public var FreeplayPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80910 #else 83656 #end;
/**
* De-Stressing
* Beat Stress (Pico Mix) in Freeplay (on Normal difficulty or higher).
*/
var FreeplayStressPico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83629 #else 83657 #end;
public var FreeplayStressPico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83629 #else 83657 #end;
/**
* L
* Earn a Loss rating on any song (on any difficulty).
*/
var LossRating = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80915 #else 83658 #end;
public var LossRating = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80915 #else 83658 #end;
/**
* Getting Freaky
* Earn a Perfect rating on any song on Hard difficulty or higher.
* NOTE: Should also be awarded for a Gold Perfect because otherwise that would be annoying.
*/
var PerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80908 #else 83659 #end;
public var PerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80908 #else 83659 #end;
/**
* You Should Drink More Water
* Earn a Golden Perfect rating on any song on Hard difficulty or higher.
*/
var GoldPerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80909 #else 83660 #end;
public var GoldPerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80909 #else 83660 #end;
/**
* Harder Than Hard
* Beat any Erect remix in Freeplay on Erect or Nightmare difficulty.
*/
var ErectDifficulty = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80911 #else 83661 #end;
public var ErectDifficulty = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80911 #else 83661 #end;
/**
* The Rap God
* Earn a Gold Perfect rating on any song on Nightmare difficulty.
*/
var GoldPerfectRatingNightmare = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80912 #else 83662 #end;
public var GoldPerfectRatingNightmare = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80912 #else 83662 #end;
/**
* Just like the game!
* Get freaky on a Friday.
* NOTE: You must beat at least one song on any difficulty.
*/
var FridayNight = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80913 #else 61034 #end;
public var FridayNight = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80913 #else 61034 #end;
/**
* Nice
* Earn a rating of EXACTLY 69% (good luck).
*/
var Nice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80914 #else 83646 #end;
public var Nice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80914 #else 83646 #end;
/**
* @return The internal ID for the medal.
*/
public function getId():Int
{
return this;
}
/**
* Retrieves the medal for completing a specific story level.
* @param levelId The ID for the story level that was completed.
* @return The medal to award.
*/
public static function getMedalByStoryLevel(levelId:String):Medal
{
switch (levelId)
@@ -375,6 +425,8 @@ enum abstract Medal(Int) from Int to Int
return StoryWeek7;
case "weekend1":
return StoryWeekend1;
case "sserafim":
return StoryCollab1;
default:
return Unknown;
}
@@ -382,30 +434,11 @@ enum abstract Medal(Int) from Int to Int
/**
* Lists all medals aside from the `Unknown` one.
* @return An array of all medals.
*/
public static function getAllMedals()
public static function getAllMedals():Array<Medal>
{
return [
StartGame,
StoryTutorial,
StoryWeek1,
StoryWeek2,
StoryWeek3,
StoryWeek4,
StoryWeek5,
StoryWeek6,
StoryWeek7,
StoryWeekend1,
CharSelect,
FreeplayPicoMix,
FreeplayStressPico,
LossRating,
PerfectRatingHard,
GoldPerfectRatingHard,
ErectDifficulty,
GoldPerfectRatingNightmare,
FridayNight,
Nice
];
return
[StartGame, StoryTutorial, StoryWeek1, StoryWeek2, StoryWeek3, StoryWeek4, StoryWeek5, StoryWeek6, StoryWeek7, StoryWeekend1, CharSelect, FreeplayPicoMix, FreeplayStressPico, LossRating, PerfectRatingHard, GoldPerfectRatingHard, ErectDifficulty, GoldPerfectRatingNightmare, FridayNight, Nice];
}
}
+20 -22
View File
@@ -25,7 +25,7 @@ class NGSaveSlot
public static function loadInstance():NGSaveSlot
{
var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT);
var loadedSave:NGSaveSlot = loadSlot(Constants.BASE_SAVE_SLOT);
if (_instance == null) _instance = loadedSave;
return loadedSave;
@@ -33,7 +33,7 @@ class NGSaveSlot
static function loadSlot(slot:Int):NGSaveSlot
{
trace('[NEWGROUNDS] Getting save slot from ID $slot');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Getting save slot from ID $slot');
var saveSlot:Null<SaveSlot> = NewgroundsClient.instance.saveSlots?.getById(slot);
@@ -46,11 +46,6 @@ class NGSaveSlot
public function new(?ngSaveSlot:Null<SaveSlot>)
{
this.ngSaveSlot = ngSaveSlot;
#if FLX_DEBUG
FlxG.console.registerClass(NGSaveSlot);
FlxG.console.registerClass(Save);
#end
}
/**
@@ -63,20 +58,21 @@ class NGSaveSlot
try
{
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>) {
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>)
{
switch (outcome)
{
case SUCCESS:
trace('[NEWGROUNDS] Successfully saved save data to save slot!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Successfully saved save data to save slot!');
case FAIL(error):
trace('[NEWGROUNDS] Failed to save data to save slot!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to save data to save slot!');
trace(error);
}
});
}
catch (error:String)
{
trace('[NEWGROUNDS] Failed to save data to save slot!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to save data to save slot!');
trace(error);
}
}
@@ -85,11 +81,12 @@ class NGSaveSlot
{
try
{
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void {
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void
{
switch (outcome)
{
case SUCCESS(value):
trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Loaded save slot with the ID of ${ngSaveSlot?.id}!');
#if FEATURE_DEBUG_FUNCTIONS
trace('Save Slot Data:');
trace(value);
@@ -101,7 +98,7 @@ class NGSaveSlot
onComplete(decodedData);
}
case FAIL(error):
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
trace(error);
if (onError != null)
@@ -113,7 +110,7 @@ class NGSaveSlot
}
catch (error:String)
{
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
trace(error);
if (onError != null)
@@ -127,30 +124,31 @@ class NGSaveSlot
{
try
{
ngSaveSlot?.clear(function(outcome:Outcome<CallError>) {
ngSaveSlot?.clear(function(outcome:Outcome<CallError>)
{
switch (outcome)
{
case SUCCESS:
trace('[NEWGROUNDS] Successfully cleared save slot!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Successfully cleared save slot!');
case FAIL(error):
trace('[NEWGROUNDS] Failed to clear save slot!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to clear save slot!');
trace(error);
}
});
}
catch (error:String)
{
trace('[NEWGROUNDS] Failed to clear save slot!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to clear save slot!');
trace(error);
}
}
public function checkSlot():Void
{
trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Checking save slot with the ID of ${ngSaveSlot?.id}...');
trace(' Is null? ${ngSaveSlot == null}');
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
trace(' Is null? ${ngSaveSlot == null}');
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
}
}
#end
@@ -1,6 +1,5 @@
package funkin.api.newgrounds;
import funkin.util.macro.EnvironmentConfigMacro;
import funkin.save.Save;
import funkin.api.newgrounds.Medals.Medal;
#if FEATURE_NEWGROUNDS
@@ -14,12 +13,19 @@ import io.newgrounds.utils.MedalList;
import io.newgrounds.utils.SaveSlotList;
import io.newgrounds.utils.ScoreBoardList;
import io.newgrounds.objects.User;
#if FEATURE_MOBILE_WEBVIEW
import funkin.mobile.util.WebViewUtil;
#end
@:build(funkin.util.macro.EnvironmentMacro.build())
@:nullSafety
class NewgroundsClient
{
static final APP_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID");
static final ENCRYPTION_KEY:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY");
@:envField
static final API_NG_APP_ID:Null<String>;
@:envField
static final API_NG_ENC_KEY:Null<String>;
public static var instance(get, never):NewgroundsClient;
@@ -39,11 +45,11 @@ class NewgroundsClient
private function new()
{
trace('[NEWGROUNDS] Initializing client...');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Initializing client...');
#if FEATURE_NEWGROUNDS_DEBUG
trace('[NEWGROUNDS] App ID: ${APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' App ID: ${API_NG_APP_ID}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Encryption Key: ${API_NG_ENC_KEY}');
#end
if (!hasValidCredentials())
@@ -54,9 +60,9 @@ class NewgroundsClient
@:nullSafety(Off)
{
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
NG.create(API_NG_APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
NG.core.setupEncryption(ENCRYPTION_KEY);
NG.core.setupEncryption(API_NG_ENC_KEY);
}
}
@@ -64,7 +70,7 @@ class NewgroundsClient
{
if (NG.core == null) return;
trace('[NEWGROUNDS] Setting up connection...');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Setting up connection...');
#if FEATURE_NEWGROUNDS_DEBUG
NG.core.verbose = true;
@@ -75,16 +81,16 @@ class NewgroundsClient
if (NG.core.attemptingLogin)
{
// Session ID was valid and we should be logged in soon.
trace('[NEWGROUNDS] Waiting for existing login!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Waiting for existing login!');
}
else
{
#if FEATURE_NEWGROUNDS_AUTOLOGIN
// Attempt an automatic login.
trace('[NEWGROUNDS] Attempting new login immediately!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Attempting new login immediately!');
this.autoLogin();
#else
trace('[NEWGROUNDS] Not logged in, you have to login manually!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Not logged in, you have to login manually!');
#end
}
}
@@ -101,14 +107,41 @@ class NewgroundsClient
FlxG.log.warn("No Newgrounds client initialized! Are your credentials invalid?");
return;
}
if (NG.core.attemptingLogin)
{
trace(" NEWGROUNDS '.bold().bg_orange() + ' Login attempt ongoing, will not login until finished.");
return;
}
var passportHandler:String->Void = function(passportUrl:String)
{
// This exists so we can create a popup on mobile but with a WebView instead.
#if FEATURE_MOBILE_WEBVIEW
if (passportUrl != null)
{
NG.core.logVerbose('Loading passport from WebView: ${passportUrl}');
WebViewUtil.openURL(passportUrl, function():Void
{
NG.core.cancelLoginRequest();
});
NG.core.onPassportUrlOpen();
}
else
NG.core.logError("Cannot open passport");
#else
NG.core.openPassportUrl();
#end
};
if (onSuccess != null && onError != null)
{
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError));
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError), passportHandler);
}
else
{
NG.core.requestLogin(onLoginResolved);
NG.core.requestLogin(onLoginResolved, passportHandler);
}
}
@@ -120,7 +153,8 @@ class NewgroundsClient
return;
}
var dummyPassport:String->Void = function(_) {
var dummyPassport:String->Void = function(_)
{
// just a dummy passport, so we don't create a popup
// otherwise `NG.core.requestLogin()` will automatically attempt to open a tab at the beginning of the game
// users should go to the Options Menu to login to NG
@@ -156,7 +190,7 @@ class NewgroundsClient
}
}
Save.instance.ngSessionId = null;
Save.instance.ngSessionId.value = null;
}
/**
@@ -176,16 +210,20 @@ class NewgroundsClient
*/
static function hasValidCredentials():Bool
{
return !(APP_ID == null
|| APP_ID == ""
|| (APP_ID != null && APP_ID.contains(" "))
|| ENCRYPTION_KEY == null
|| ENCRYPTION_KEY == ""
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
return !(API_NG_APP_ID == null
|| API_NG_APP_ID == ""
|| (API_NG_APP_ID != null && API_NG_APP_ID.contains(" "))
|| API_NG_ENC_KEY == null
|| API_NG_ENC_KEY == ""
|| (API_NG_ENC_KEY != null && API_NG_ENC_KEY.contains(" ")));
}
function onLoginResolved(outcome:LoginOutcome):Void
{
#if FEATURE_MOBILE_WEBVIEW
WebViewUtil.close();
#end
switch (outcome)
{
case SUCCESS:
@@ -236,17 +274,19 @@ class NewgroundsClient
{
if (NG.core == null) return;
trace('[NEWGROUNDS] Login successful!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login successful!');
// Persist the session ID.
Save.instance.ngSessionId = NG.core.sessionId;
Save.instance.ngSessionId.value = NG.core.sessionId;
trace('[NEWGROUNDS] Submitting medal request...');
trace(NG.core.sessionId);
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitting medal request...');
NG.core.requestMedals(onFetchedMedals);
trace('[NEWGROUNDS] Submitting leaderboard request...');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitting leaderboard request...');
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
trace('[NEWGROUNDS] Submitting save slot request...');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitting save slot request...');
NG.core.saveSlots.loadList(onFetchedSaveSlots);
}
@@ -258,32 +298,32 @@ class NewgroundsClient
switch (type)
{
case PASSPORT:
trace('[NEWGROUNDS] Login cancelled by passport website.');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login cancelled by passport website.');
case MANUAL:
trace('[NEWGROUNDS] Login cancelled by application.');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login cancelled by application.');
default:
trace('[NEWGROUNDS] Login cancelled by unknown source.');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login cancelled by unknown source.');
}
case ERROR(error):
switch (error)
{
case HTTP(error):
trace('[NEWGROUNDS] Login failed due to HTTP error: ${error}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to HTTP error: ${error}');
case RESPONSE(error):
trace('[NEWGROUNDS] Login failed due to response error: ${error.message} (${error.code})');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to response error: ${error.message} (${error.code})');
case RESULT(error):
trace('[NEWGROUNDS] Login failed due to result error: ${error.message} (${error.code})');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to result error: ${error.message} (${error.code})');
default:
trace('[NEWGROUNDS] Login failed due to unknown error: ${error}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to unknown error: ${error}');
}
default:
trace('[NEWGROUNDS] Login failed due to unknown reason.');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to unknown reason.');
}
}
function onLogoutSuccessful():Void
{
trace('[NEWGROUNDS] Logout successful!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout successful!');
}
function onLogoutFailed(result:CallError):Void
@@ -291,31 +331,31 @@ class NewgroundsClient
switch (result)
{
case HTTP(error):
trace('[NEWGROUNDS] Logout failed due to HTTP error: ${error}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to HTTP error: ${error}');
case RESPONSE(error):
trace('[NEWGROUNDS] Logout failed due to response error: ${error.message} (${error.code})');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to response error: ${error.message} (${error.code})');
case RESULT(error):
trace('[NEWGROUNDS] Logout failed due to result error: ${error.message} (${error.code})');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to result error: ${error.message} (${error.code})');
default:
trace('[NEWGROUNDS] Logout failed due to unknown error: ${result}');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to unknown error: ${result}');
}
}
function onFetchedMedals(outcome:Outcome<CallError>):Void
{
trace('[NEWGROUNDS] Fetched medals!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched medals!');
}
function onFetchedLeaderboards(outcome:Outcome<CallError>):Void
{
trace('[NEWGROUNDS] Fetched leaderboards!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched leaderboards!');
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
}
function onFetchedSaveSlots(outcome:Outcome<CallError>):Void
{
trace('[NEWGROUNDS] Fetched save slots!');
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched save slots!');
NGSaveSlot.instance.checkSlot();
}
@@ -353,7 +393,7 @@ class NewgroundsClient
#end
// We have to fetch the session ID from the save file.
return Save.instance.ngSessionId;
return Save.instance.ngSessionId.value;
}
}
+2 -1
View File
@@ -23,7 +23,8 @@ class Referral
{
NG.core?.calls.loader.loadReferral(false)
.addComponentParameter("referral_name", referralName)
.addResponseHandler(response -> {
.addResponseHandler(response ->
{
trace(response);
if (response.success)
{
+21 -44
View File
@@ -42,7 +42,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
if (_onVolumeChanged == null)
{
_onVolumeChanged = new FlxTypedSignal<Float->Void>();
FlxG.sound.onVolumeChange.add(function(volume:Float) {
FlxG.sound.onVolumeChange.add(function(volume:Float)
{
_onVolumeChanged.dispatch(volume);
});
}
@@ -124,11 +125,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
*/
var _label:String = "unknown";
/**
* Whether we received a focus lost event.
*/
var _lostFocus:Bool = false;
public function new()
{
super();
@@ -229,37 +225,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
return this;
}
/**
* Called when the user clicks to focus on the window.
*/
override function onFocus():Void
{
// Flixel can sometimes toss spurious `onFocus` events, e.g. if the Flixel debugger is toggled
// on and off. We only want to resume the sound if we actually lost focus, and if we weren't
// already paused before we lost focus.
if (_lostFocus && !_alreadyPaused)
{
// trace('Resuming audio (${this._label}) on focus!');
resume();
}
else
{
// trace('Not resuming audio (${this._label}) on focus!');
}
_lostFocus = false;
}
/**
* Called when the user tabs away from the window.
*/
override function onFocusLost():Void
{
// trace('Focus lost, pausing audio!');
_lostFocus = true;
_alreadyPaused = _paused;
pause();
}
public override function resume():FunkinSound
{
if (this._time < 0)
@@ -386,7 +351,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
partialQueue.push(music);
@:nullSafety(Off)
music.future.onComplete(function(partialMusic:Null<FunkinSound>) {
music.future.onComplete(function(partialMusic:Null<FunkinSound>)
{
FlxG.sound.music = partialMusic;
FlxG.sound.list.remove(FlxG.sound.music);
@@ -405,10 +371,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.persist ?? false, params.onComplete);
if (music != null)
{
FlxG.sound.music = music;
// Prevent repeat update() and onFocus() calls.
FlxG.sound.list.remove(FlxG.sound.music);
setMusic(music);
if (FlxG.sound.music != null && params.onLoad != null) params.onLoad();
@@ -421,6 +384,18 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
}
}
/**
* Replaces the Flixel current music object with the given `FunkinSound` object.
* @param newMusic The new music to be set as the current music.
*/
public static function setMusic(newMusic:FunkinSound):Void
{
FlxG.sound.music = newMusic;
// Prevent repeat update() and onFocus() calls.
FlxG.sound.list.remove(FlxG.sound.music);
}
public static function emptyPartialQueue():Void
{
while (partialQueue.length > 0)
@@ -523,11 +498,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
}
else
{
promise.future.onError(function(e) {
promise.future.onError(function(e)
{
soundRequest.error("Sound loading was errored or cancelled");
});
soundRequest.future.onComplete(function(partialSound) {
soundRequest.future.onComplete(function(partialSound)
{
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, false, onComplete, onLoad);
promise.complete(snd);
});
+27 -13
View File
@@ -58,7 +58,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
{
var error:Float = 0;
forEachAlive(function(snd) {
forEachAlive(function(snd)
{
if (targetTime == null) targetTime = snd.time;
else
{
@@ -85,7 +86,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
// result.pause();
result.time = this.time;
result.onComplete = function() {
result.onComplete = function()
{
this.onComplete();
}
@@ -96,14 +98,17 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
return result;
}
public dynamic function onComplete():Void {}
public dynamic function onComplete():Void
{
}
/**
* Pause all the sounds in the group.
*/
public function pause()
{
forEachAlive(function(sound:FunkinSound) {
forEachAlive(function(sound:FunkinSound)
{
sound.pause();
});
}
@@ -113,7 +118,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
*/
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
{
forEachAlive(function(sound:FunkinSound) {
forEachAlive(function(sound:FunkinSound)
{
if (sound.length < startTime)
{
// trace('Queuing sound (${sound.toString()} past its length! Skipping...)');
@@ -128,7 +134,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
*/
public function resume()
{
forEachAlive(function(sound:FunkinSound) {
forEachAlive(function(sound:FunkinSound)
{
sound.resume();
});
}
@@ -139,7 +146,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
@:nullSafety(Off)
public function fadeIn(duration:Float, ?from:Float = 0.0, ?to:Float = 1.0, ?onComplete:FlxTween->Void):Void
{
forEachAlive(function(sound:FunkinSound) {
forEachAlive(function(sound:FunkinSound)
{
sound.fadeIn(duration, from, to, onComplete);
});
}
@@ -150,7 +158,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
@:nullSafety(Off)
public function fadeOut(duration:Float, ?to:Float = 0.0, ?onComplete:FlxTween->Void):Void
{
forEachAlive(function(sound:FunkinSound) {
forEachAlive(function(sound:FunkinSound)
{
sound.fadeOut(duration, to, onComplete);
});
}
@@ -162,7 +171,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
{
if (members != null)
{
forEachAlive(function(sound:FunkinSound) {
forEachAlive(function(sound:FunkinSound)
{
sound.stop();
});
}
@@ -198,7 +208,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
function set_time(time:Float):Float
{
forEachAlive(function(snd:FunkinSound) {
forEachAlive(function(snd:FunkinSound)
{
// account for different offsets per sound?
snd.time = time;
});
@@ -233,7 +244,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
function set_volume(volume:Float):Float
{
forEachAlive(function(snd:FunkinSound) {
forEachAlive(function(snd:FunkinSound)
{
snd.volume = volume;
});
@@ -249,7 +261,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
function set_muted(muted:Bool):Bool
{
forEachAlive(function(snd:FunkinSound) {
forEachAlive(function(snd:FunkinSound)
{
snd.muted = muted;
});
@@ -269,7 +282,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
{
#if FLX_PITCH
trace('Setting audio pitch to ' + val);
forEachAlive(function(snd:FunkinSound) {
forEachAlive(function(snd:FunkinSound)
{
snd.pitch = val;
});
#end
+14 -7
View File
@@ -47,7 +47,8 @@ class VoicesGroup extends SoundGroup
function set_playerVolume(volume:Float):Float
{
playerVoices?.forEachAlive(function(voice:FunkinSound) {
playerVoices?.forEachAlive(function(voice:FunkinSound)
{
voice.volume = volume;
});
return playerVolume = volume;
@@ -55,15 +56,18 @@ class VoicesGroup extends SoundGroup
override function set_time(time:Float):Float
{
forEachAlive(function(snd) {
forEachAlive(function(snd)
{
// account for different offsets per sound?
snd.time = time;
});
playerVoices?.forEachAlive(function(voice:FunkinSound) {
playerVoices?.forEachAlive(function(voice:FunkinSound)
{
voice.time -= playerVoicesOffset;
});
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
opponentVoices?.forEachAlive(function(voice:FunkinSound)
{
voice.time -= opponentVoicesOffset;
});
@@ -72,7 +76,8 @@ class VoicesGroup extends SoundGroup
function set_playerVoicesOffset(offset:Float):Float
{
playerVoices?.forEachAlive(function(voice:FunkinSound) {
playerVoices?.forEachAlive(function(voice:FunkinSound)
{
voice.time += playerVoicesOffset;
voice.time -= offset;
});
@@ -81,7 +86,8 @@ class VoicesGroup extends SoundGroup
function set_opponentVoicesOffset(offset:Float):Float
{
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
opponentVoices?.forEachAlive(function(voice:FunkinSound)
{
voice.time += opponentVoicesOffset;
voice.time -= offset;
});
@@ -99,7 +105,8 @@ class VoicesGroup extends SoundGroup
function set_opponentVolume(volume:Float):Float
{
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
opponentVoices?.forEachAlive(function(voice:FunkinSound)
{
voice.volume = volume;
});
return opponentVolume = volume;
+4 -3
View File
@@ -14,6 +14,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
{
// public var vis:VisShit;
var analyzer:Null<SpectralAnalyzer> = null;
var analyzerLevelsCache:Array<Bar> = new Array<Bar>();
var volumes:Array<Float> = [];
@@ -112,11 +113,11 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
*/
function drawFFT():Void
{
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
analyzerLevelsCache = (analyzer != null) ? analyzer.getLevels(analyzerLevelsCache) : getDefaultLevels();
for (i in 0...min(group.members.length, levels.length))
for (i in 0...min(group.members.length, analyzerLevelsCache.length))
{
var animFrame:Int = (FlxG.sound.volume == 0 || FlxG.sound.muted) ? 0 : Math.round(levels[i].value * 6);
var animFrame:Int = (FlxG.sound.volume == 0 || FlxG.sound.muted) ? 0 : Math.round(analyzerLevelsCache[i].value * 6);
// don't display if we're at 0 volume from the level
group.members[i].visible = animFrame > 0;
@@ -69,7 +69,8 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
default:
}
forEach(spr -> {
forEach(spr ->
{
spr.visible = spr.ID % wavOptimiz == 0;
});
@@ -176,7 +177,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
for (i in 0...group.members.length)
{
// needs to be exponential growth / scaling
// still need to optmize the FFT to run better, gets only samples needed?
// still need to optimize the FFT to run better, gets only samples needed?
// not every frequency is built the same!
// 20hz to 40z is a LOT of subtle low ends, but somethin like 20,000hz to 20,020hz, the difference is NOT the same!
+5 -6
View File
@@ -40,7 +40,8 @@ class VisShit
// helpers, note that spectrum indexes suppose non-negative frequencies
final binSize = fs / fftN;
final indexToFreq = function(k:Int) {
final indexToFreq = function(k:Int)
{
var powShit:Float = FlxMath.remapToRange(k, 0, halfN, 0, MathUtil.logBase(10, halfN)); // 4.3 is almost the log of 20Khz or so. Close enuf lol
return 1.0 * (Math.pow(10, powShit)); // we need the `1.0` to avoid overflows
@@ -49,7 +50,8 @@ class VisShit
// "melodic" band-pass filter
final minFreq = 20.70;
final maxFreq = 4000.01;
final melodicBandPass = function(k:Int, s:Float) {
final melodicBandPass = function(k:Int, s:Float)
{
final freq = indexToFreq(k);
final filter = freq > minFreq - binSize && freq < maxFreq + binSize ? 1 : 0;
return s;
@@ -62,10 +64,7 @@ class VisShit
while (c < samples.length)
{
// take a chunk (zero-padded if needed) and apply the window
final chunk = [
for (n in 0...fftN)
(c + n < samples.length ? samples[c + n] : 0.0) * window(n)
];
final chunk = [for (n in 0...fftN) (c + n < samples.length ? samples[c + n] : 0.0) * window(n)];
// compute positive spectrum with sampling correction and BP filter
final freqs = FFT.rfft(chunk).map(z -> z.scale(1 / fftN).magnitude).mapi(melodicBandPass);
+11 -22
View File
@@ -10,58 +10,48 @@ abstract Complex({
final imag:Float;
})
{
public inline function new(real:Float, imag:Float)
this = {real: real, imag: imag};
public inline function new(real:Float, imag:Float) this = {real: real, imag: imag};
/**
Makes a Complex number with the given Float as its real part and a zero imag part.
**/
@:from
public static inline function fromReal(r:Float)
return new Complex(r, 0);
public static inline function fromReal(r:Float) return new Complex(r, 0);
/**
Complex argument, in radians.
**/
public var angle(get, never):Float;
inline function get_angle()
return Math.atan2(this.imag, this.real);
inline function get_angle() return Math.atan2(this.imag, this.real);
/**
Complex module.
**/
public var magnitude(get, never):Float;
inline function get_magnitude()
return Math.sqrt(this.real * this.real + this.imag * this.imag);
inline function get_magnitude() return Math.sqrt(this.real * this.real + this.imag * this.imag);
@:op(A + B)
public inline function add(rhs:Complex):Complex
return new Complex(this.real + rhs.real, this.imag + rhs.imag);
public inline function add(rhs:Complex):Complex return new Complex(this.real + rhs.real, this.imag + rhs.imag);
@:op(A - B)
public inline function sub(rhs:Complex):Complex
return new Complex(this.real - rhs.real, this.imag - rhs.imag);
public inline function sub(rhs:Complex):Complex return new Complex(this.real - rhs.real, this.imag - rhs.imag);
@:op(A * B)
public inline function mult(rhs:Complex):Complex
return new Complex(this.real * rhs.real - this.imag * rhs.imag, this.real * rhs.imag + this.imag * rhs.real);
public inline function mult(rhs:Complex):Complex return new Complex(this.real * rhs.real - this.imag * rhs.imag, this.real * rhs.imag + this.imag * rhs.real);
/**
Returns the complex conjugate, does not modify this object.
**/
public inline function conj():Complex
return new Complex(this.real, -this.imag);
public inline function conj():Complex return new Complex(this.real, -this.imag);
/**
Multiplication by a real factor, does not modify this object.
**/
public inline function scale(k:Float):Complex
return new Complex(this.real * k, this.imag * k);
public inline function scale(k:Float):Complex return new Complex(this.real * k, this.imag * k);
public inline function copy():Complex
return new Complex(this.real, this.imag);
public inline function copy():Complex return new Complex(this.real, this.imag);
/**
The imaginary unit.
@@ -76,6 +66,5 @@ abstract Complex({
/**
Computes the complex exponential `e^(iw)`.
**/
public static inline function exp(w:Float)
return new Complex(Math.cos(w), Math.sin(w));
public static inline function exp(w:Float) return new Complex(Math.cos(w), Math.sin(w));
}
+2 -4
View File
@@ -19,8 +19,7 @@ class FFT
samples from the Discrete-Time Fourier Transform (DTFT) - which is Fs-periodic -
with a spacing of Fs/N Hz between them and a scaling factor of Fs.
**/
public static function fft(input:Array<Complex>):Array<Complex>
return do_fft(input, false);
public static function fft(input:Array<Complex>):Array<Complex> return do_fft(input, false);
/**
Like `fft`, but for a real (Float) sequence input.
@@ -41,8 +40,7 @@ class FFT
from each other, the result will consist of N data points as sampled
from a time signal at intervals of 1/Fs with a scaling factor of 1/Fs.
**/
public static function ifft(input:Array<Complex>):Array<Complex>
return do_fft(input, true);
public static function ifft(input:Array<Complex>):Array<Complex> return do_fft(input, true);
// Handles padding and scaling for forwards and inverse FFTs.
static function do_fft(input:Array<Complex>, inverse:Bool):Array<Complex>
@@ -12,35 +12,28 @@ abstract OffsetArray<T>({
final offset:Int;
})
{
public inline function new(array:Array<T>, offset:Int)
this = {array: array, offset: offset};
public inline function new(array:Array<T>, offset:Int) this = {array: array, offset: offset};
public var length(get, never):Int;
inline function get_length()
return this.array.length;
inline function get_length() return this.array.length;
@:arrayAccess
public inline function get(index:Int):T
return this.array[index - this.offset];
public inline function get(index:Int):T return this.array[index - this.offset];
@:arrayAccess
public inline function set(index:Int, value:T):Void
this.array[index - this.offset] = value;
public inline function set(index:Int, value:T):Void this.array[index - this.offset] = value;
/**
Iterates through items in their original order while providing the altered indexes as keys.
**/
public inline function keyValueIterator():KeyValueIterator<Int, T>
return new OffsetArrayIterator(this.array, this.offset);
public inline function keyValueIterator():KeyValueIterator<Int, T> return new OffsetArrayIterator(this.array, this.offset);
@:from
static inline function fromArray<T>(array:Array<T>)
return new OffsetArray(array, 0);
static inline function fromArray<T>(array:Array<T>) return new OffsetArray(array, 0);
@:to
inline function toArray()
return this.array;
inline function toArray() return this.array;
/**
Makes a shifted version of the given `array`, where elements are in the
@@ -76,12 +69,12 @@ private class OffsetArrayIterator<T>
this.enumeration = 0;
}
public inline function next():{key:Int, value:T}
public inline function next():
{key:Int, value:T}
{
final i = this.enumeration++;
return {key: i + this.offset, value: this.array[i]};
}
public inline function hasNext():Bool
return this.enumeration < this.array.length;
public inline function hasNext():Bool return this.enumeration < this.array.length;
}
+5 -10
View File
@@ -86,30 +86,25 @@ class Signal
/**
Returns the average value of an array.
**/
public static function mean(y:Array<Float>):Float
return sum(y) / y.length;
public static function mean(y:Array<Float>):Float return sum(y) / y.length;
/**
Returns the global maximum.
**/
public static function max(y:Array<Float>):Float
return y.fold(Math.max, y[0]);
public static function max(y:Array<Float>):Float return y.fold(Math.max, y[0]);
/**
Returns the global maximum's index.
**/
public static function maxi(y:Array<Float>):Int
return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0);
public static function maxi(y:Array<Float>):Int return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0);
/**
Returns the global minimum.
**/
public static function min(y:Array<Float>):Float
return y.fold(Math.min, y[0]);
public static function min(y:Array<Float>):Float return y.fold(Math.min, y[0]);
/**
Returns the global minimum's index.
**/
public static function mini(y:Array<Float>):Int
return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0);
public static function mini(y:Array<Float>):Int return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0);
}
+1 -1
View File
@@ -166,7 +166,7 @@ class WaveformData
var ratio = newSamplesPerPoint / samplesPerPoint;
if (ratio == 1) return result;
if (ratio < 1) trace('[WARNING] Downsampling will result in a low precision.');
if (ratio < 1) trace(' WARNING '.warning() + ' Downsampling will result in a low precision.');
var inputSampleCount = this.lenSamples();
var outputSampleCount = Std.int(inputSampleCount * ratio);
+69 -14
View File
@@ -2,7 +2,12 @@ package funkin.audio.waveform;
import funkin.graphics.rendering.MeshRender;
import flixel.util.FlxColor;
import funkin.play.character.BaseCharacter.CharacterType;
/**
* A sprite which displays the waveform of audio data.
* Generate a WaveformData and provide it to this sprite.
*/
class WaveformSprite extends MeshRender
{
static final DEFAULT_COLOR:FlxColor = FlxColor.WHITE;
@@ -13,19 +18,24 @@ class WaveformSprite extends MeshRender
static final DEFAULT_WIDTH:Float = 100.0;
static final DEFAULT_HEIGHT:Float = 100.0;
public var iconId:CharacterType;
/**
* Set this to true to tell the waveform to rebuild itself.
* Do this any time the data or drawable area of the waveform changes.
* This often (but not always) needs to be done every frame.
*/
var isWaveformDirty:Bool = true;
var isWaveformDirty:Bool;
/**
* If true, force the waveform to redraw every frame.
* Useful if the waveform's clipRect is constantly changing.
*/
public var forceUpdate:Bool = false;
public var forceUpdate:Bool;
/**
* The data to render the waveform with.
*/
public var waveformData(default, set):Null<WaveformData>;
function set_waveformData(value:Null<WaveformData>):Null<WaveformData>
@@ -52,6 +62,9 @@ class WaveformSprite extends MeshRender
return waveformColor;
}
/**
* Whether the Waveform is horizontal or vertical.
*/
public var orientation(default, set):WaveformOrientation;
function set_orientation(value:WaveformOrientation):WaveformOrientation
@@ -68,7 +81,7 @@ class WaveformSprite extends MeshRender
*/
public var time(default, set):Float;
function set_time(value:Float)
function set_time(value:Float):Float
{
if (time == value) return value;
@@ -77,13 +90,22 @@ class WaveformSprite extends MeshRender
return time;
}
override function set_visible(value:Bool):Bool
{
if (visible == value) return value;
visible = value;
isWaveformDirty = true;
return visible;
}
/**
* The duration, in seconds, that the waveform represents.
* The section of waveform from `time` to `time + duration` and `width` are used to determine how many samples each pixel represents.
*/
public var duration(default, set):Float;
function set_duration(value:Float)
function set_duration(value:Float):Float
{
if (duration == value) return value;
@@ -120,13 +142,13 @@ class WaveformSprite extends MeshRender
*
* NOTE: This is technically doubled since it's applied above and below the center of the waveform.
*/
public var minWaveformSize:Int = 1;
public var minWaveformSize:Int;
/**
* A multiplier on the size of the waveform.
* Still capped at the width and height set for the sprite.
*/
public var amplitude:Float = 1.0;
public var amplitude:Float;
public function new(?waveformData:WaveformData, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float)
{
@@ -135,6 +157,11 @@ class WaveformSprite extends MeshRender
this.width = DEFAULT_WIDTH;
this.height = DEFAULT_HEIGHT;
this.minWaveformSize = 1;
this.amplitude = 1.0;
this.isWaveformDirty = true;
this.forceUpdate = false;
this.waveformData = waveformData;
this.orientation = orientation ?? DEFAULT_ORIENTATION;
this.time = 0.0;
@@ -151,7 +178,7 @@ class WaveformSprite extends MeshRender
isWaveformDirty = true;
}
public override function update(elapsed:Float)
public override function update(elapsed:Float):Void
{
super.update(elapsed);
@@ -170,6 +197,11 @@ class WaveformSprite extends MeshRender
makeGraphic(1, 1, this.waveformColor);
}
public override function draw():Void
{
super.draw();
}
/**
* @param offsetX Horizontal offset to draw the waveform at, in samples.
*/
@@ -183,12 +215,12 @@ class WaveformSprite extends MeshRender
this.clear();
if (waveformData == null) return;
if (waveformData == null || !this.visible) return;
// Center point of the waveform. When horizontal this is half the height, when vertical this is half the width.
var waveformCenterPos:Int = orientation == HORIZONTAL ? Std.int(this.height / 2) : Std.int(this.width / 2);
var oneSecondInIndices:Int = waveformData.secondsToIndex(1);
// var oneSecondInIndices:Int = waveformData.secondsToIndex(1)
var startTime:Float = time;
var endTime:Float = time + duration;
@@ -223,7 +255,10 @@ class WaveformSprite extends MeshRender
var isBeforeClipRect:Bool = (clipRect != null) && ((orientation == HORIZONTAL) ? pixelPos < clipRect.x : pixelPos < clipRect.y);
if (isBeforeClipRect) continue;
if (isBeforeClipRect)
{
continue;
}
var isAfterClipRect:Bool = (clipRect != null)
&& ((orientation == HORIZONTAL) ? pixelPos > (clipRect.x + clipRect.width) : pixelPos > (clipRect.y + clipRect.height));
@@ -426,20 +461,40 @@ class WaveformSprite extends MeshRender
}
}
public static function buildFromWaveformData(data:WaveformData, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float)
/**
* Build a WaveformSprite from waveform data.
* @param data The data for the waveform to use.
* @param orientation Whether the waveform should be horizontal or vertical.
* @param color The color of the waveform.
* @param duration The width of the waveform, in seconds.
*
* @return The resulting WaveformSprite.
*/
public static function buildFromWaveformData(data:WaveformData, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float):WaveformSprite
{
return new WaveformSprite(data, orientation, color, duration);
}
public static function buildFromFunkinSound(sound:FunkinSound, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float)
/**
* Build a WaveformSprite from a FunkinSound's waveform data.
* @param sound The audio for the waveform to use.
* @param orientation Whether the waveform should be horizontal or vertical.
* @param color The color of the waveform.
* @param duration The width of the waveform, in seconds.
*
* @return The resulting WaveformSprite.
*/
public static function buildFromFunkinSound(sound:FunkinSound, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float):WaveformSprite
{
// TODO: Build waveform data from FunkinSound.
var data = null;
var data = WaveformDataParser.interpretFlxSound(sound);
return buildFromWaveformData(data, orientation, color, duration);
}
}
/**
* The possible orientations of a waveform.
*/
enum WaveformOrientation
{
HORIZONTAL;
+11 -10
View File
@@ -18,7 +18,7 @@ typedef EntryConstructorFunction = (String, ?Dynamic) -> Void;
*/
@:nullSafety
@:generic
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J, P>
{
/**
@@ -78,7 +78,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
// SCRIPTED ENTRIES
//
var scriptedEntryClassNames:Array<String> = getScriptedClassNames();
log('Parsing ${scriptedEntryClassNames.length} scripted entries...');
log(' INFO '.info() + 'Parsing ${scriptedEntryClassNames.length} scripted entries...');
for (entryCls in scriptedEntryClassNames)
{
@@ -109,10 +109,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
// UNSCRIPTED ENTRIES
//
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('${dataFilePath}/');
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool
{
return !entries.exists(entryId);
});
log('Parsing ${unscriptedEntryIds.length} unscripted entries...');
log(' INFO '.info() + 'Parsing ${unscriptedEntryIds.length} unscripted entries...');
for (entryId in unscriptedEntryIds)
{
try
@@ -120,14 +121,14 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
var entry:Null<T> = createEntry(entryId);
if (entry != null)
{
trace(' Loaded entry data: ${entry}');
log('Loaded entry data: ${entry}');
entries.set(entry.id, entry);
}
}
catch (e)
{
// Print the error.
trace(' Failed to load entry data: ${entryId}');
log(' WARNING '.warning() + ' Failed to load entry data: ${entryId}');
trace(e);
continue;
}
@@ -157,7 +158,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
* @param id The ID of the entry.
* @return `true` if the entry has an attached script, `false` otherwise.
*/
public function isScriptedEntry(id:String):Bool
public function isScriptedEntry(id:String, ?params:Null<P>):Bool
{
return scriptedEntryIds.exists(id);
}
@@ -167,7 +168,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
* @param id The ID of the entry.
* @return The class name, or `null` if it does not exist.
*/
public function getScriptedEntryClassName(id:String):Null<String>
public function getScriptedEntryClassName(id:String, ?params:Null<P>):Null<String>
{
return scriptedEntryIds.get(id);
}
@@ -224,7 +225,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
function log(message:String):Void
{
trace('[' + registryId + '] ' + message);
trace(' $registryId '.bold().bg_note_down() + ' $message');
}
function loadEntryFile(id:String):JsonFile
@@ -333,7 +334,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
function printErrors(errors:Array<json2object.Error>, id:String = ''):Void
{
trace('[${registryId}] Failed to parse entry data: ${id}');
trace(' $registryId '.bold().bg_note_down() + ' ERROR '.error() + 'Failed to parse entry data: ${id}');
for (error in errors)
{
+12 -12
View File
@@ -12,27 +12,27 @@ class DataError
switch (error)
{
case IncorrectType(vari, expected, pos):
trace(' Expected field "$vari" to be of type "$expected".');
trace(' ERROR '.error() + 'Expected field "$vari" to be of type "$expected".');
printPos(pos);
case IncorrectEnumValue(value, expected, pos):
trace(' Invalid enum value (expected "$expected", got "$value")');
trace(' ERROR '.error() + 'Invalid enum value (expected "$expected", got "$value")');
printPos(pos);
case InvalidEnumConstructor(value, expected, pos):
trace(' Invalid enum constructor (epxected "$expected", got "$value")');
trace(' ERROR '.error() + 'Invalid enum constructor (epxected "$expected", got "$value")');
printPos(pos);
case UninitializedVariable(vari, pos):
trace(' Uninitialized variable "$vari"');
trace(' ERROR '.error() + 'Uninitialized variable "$vari"');
printPos(pos);
case UnknownVariable(vari, pos):
trace(' Unknown variable "$vari"');
trace(' ERROR '.error() + 'Unknown variable "$vari"');
printPos(pos);
case ParserError(message, pos):
trace(' Parsing error: ${message}');
trace(' ERROR '.error() + 'Parsing error: ${message}');
printPos(pos);
case CustomFunctionException(e, pos):
if (Std.isOfType(e, String))
{
trace(' ${e}');
trace(' ERROR '.error() + '${e}');
}
else
{
@@ -49,11 +49,11 @@ class DataError
switch (Type.typeof(e))
{
case TClass(c):
trace(' [${Type.getClassName(c)}] ${e.toString()}');
trace(' ERROR '.error() + '(${Type.getClassName(c)}) ${e.toString()}');
case TEnum(c):
trace(' [${Type.getEnumName(c)}] ${e.toString()}');
trace(' ERROR '.error() + '(${Type.getEnumName(c)}) ${e.toString()}');
default:
trace(' [${Type.typeof(e)}] ${e.toString()}');
trace(' ERROR '.error() + '(${Type.typeof(e)}) ${e.toString()}');
}
}
@@ -66,11 +66,11 @@ class DataError
{
if (pos.lines[0].number == pos.lines[pos.lines.length - 1].number)
{
trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}');
trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}');
}
else
{
trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}-${pos.lines[pos.lines.length - 1].number}');
trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}-${pos.lines[pos.lines.length - 1].number}');
}
}
}
+4 -5
View File
@@ -243,11 +243,10 @@ class DataParse
switch (json.value)
{
case JObject(fields):
var result:LegacyNoteSection =
{
mustHitSection: false,
sectionNotes: [],
};
var result:LegacyNoteSection = {
mustHitSection: false,
sectionNotes: [],
};
for (field in fields)
{
switch (field.name)
+3 -1
View File
@@ -6,4 +6,6 @@ package funkin.data;
* @see `funkin.data.BaseRegistry`
*/
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
interface DefaultRegistryImpl {}
interface DefaultRegistryImpl
{
}
+26 -1
View File
@@ -83,7 +83,7 @@ typedef UnnamedAnimationData =
/**
* Optionally specify an asset path to use for this specific animation.
* ONLY for use by MultiSparrow characters.
* ONLY for use by MultiSparrow and MultiAnimateAtlas characters.
* @default The assetPath of the parent sprite
*/
@:optional
@@ -138,4 +138,29 @@ typedef UnnamedAnimationData =
@:default([])
@:optional
var frameIndices:Null<Array<Int>>;
/**
* The type of animation to use.
* Only available for texture atlases.
* Options: "framelabel", "symbol"
*/
@:default("framelabel")
@:optional
var animType:String;
/**
* The render type of the animation.
* Only available for multi-sparrow and multi-animateatlas characters.
* Options: "sparrow", "animateatlas"
*/
@:default("")
@:optional
var renderType:String;
/**
* Various settings for the sprite.
* Only available for texture atlases.
*/
@:optional
var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
}
@@ -1,4 +1,4 @@
package funkin.play.character;
package funkin.data.character;
import funkin.data.animation.AnimationData;
import funkin.modding.events.ScriptEvent;
@@ -6,8 +6,15 @@ import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.ScriptedCharacter.ScriptedAnimateAtlasCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedBaseCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedMultiSparrowCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedMultiAnimateAtlasCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
import funkin.play.character.AnimateAtlasCharacter;
import funkin.play.character.BaseCharacter;
import funkin.play.character.SparrowCharacter;
import funkin.play.character.MultiSparrowCharacter;
import funkin.play.character.MultiAnimateAtlasCharacter;
import funkin.play.character.PackerCharacter;
import funkin.util.assets.DataAssets;
import funkin.util.VersionUtil;
import haxe.Json;
@@ -44,16 +51,17 @@ class CharacterDataParser
{
// Clear any stages that are cached if there were any.
clearCharacterCache();
trace('[CHARACTER] Parsing all entries...');
log(' INFO '.info() + 'Parsing all entries...');
//
// UNSCRIPTED CHARACTERS
//
var charIdList:Array<String> = DataAssets.listDataFilesInPath('characters/');
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool {
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool
{
return !characterCache.exists(charId);
});
trace(' Fetching data for ${unscriptedCharIds.length} characters...');
log('Fetching data for ${unscriptedCharIds.length} characters...');
for (charId in unscriptedCharIds)
{
try
@@ -61,7 +69,7 @@ class CharacterDataParser
var charData:Null<CharacterData> = parseCharacterData(charId);
if (charData != null)
{
trace(' Loaded character data: ${charId}');
log('Loaded character "${charId}"');
characterCache.set(charId, charData);
}
}
@@ -81,19 +89,19 @@ class CharacterDataParser
var scriptedCharClassNames1:Array<String> = ScriptedSparrowCharacter.listScriptClasses();
if (scriptedCharClassNames1.length > 0)
{
trace(' Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
log('Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
for (charCls in scriptedCharClassNames1)
{
try
{
var character:SparrowCharacter = ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
trace(' Initialized character ${character.characterName}');
var character:SparrowCharacter = ScriptedSparrowCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
log('Loaded character ${character.characterName} (scripted: $charCls)');
characterScriptedClass.set(character.characterId, charCls);
}
catch (e)
{
trace(' FAILED to instantiate scripted Sparrow character: ${charCls}');
trace(e);
log(' ERROR '.error() + 'Failed to initialize scripted Sparrow character: $charCls');
log(' ERROR '.error() + '$e');
}
}
}
@@ -101,18 +109,19 @@ class CharacterDataParser
var scriptedCharClassNames2:Array<String> = ScriptedPackerCharacter.listScriptClasses();
if (scriptedCharClassNames2.length > 0)
{
trace(' Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
log('Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
for (charCls in scriptedCharClassNames2)
{
try
{
var character:PackerCharacter = ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID);
var character:PackerCharacter = ScriptedPackerCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
log('Loaded character ${character.characterName} (scripted: $charCls)');
characterScriptedClass.set(character.characterId, charCls);
}
catch (e)
{
trace(' FAILED to instantiate scripted Packer character: ${charCls}');
trace(e);
log(' ERROR '.error() + 'Failed to initialize scripted Packer character: $charCls');
log(' ERROR '.error() + '$e');
}
}
}
@@ -120,18 +129,19 @@ class CharacterDataParser
var scriptedCharClassNames3:Array<String> = ScriptedMultiSparrowCharacter.listScriptClasses();
if (scriptedCharClassNames3.length > 0)
{
trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
log('Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
for (charCls in scriptedCharClassNames3)
{
try
{
var character:MultiSparrowCharacter = ScriptedMultiSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
var character:MultiSparrowCharacter = ScriptedMultiSparrowCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
log('Loaded character ${character.characterName} (scripted: $charCls)');
characterScriptedClass.set(character.characterId, charCls);
}
catch (e)
{
trace(' FAILED to instantiate scripted Multi-Sparrow character: ${charCls}');
trace(e);
log(' ERROR '.error() + 'Failed to initialize scripted Multi-Sparrow character: $charCls');
log(' ERROR '.error() + '$e');
}
}
}
@@ -139,18 +149,39 @@ class CharacterDataParser
var scriptedCharClassNames4:Array<String> = ScriptedAnimateAtlasCharacter.listScriptClasses();
if (scriptedCharClassNames4.length > 0)
{
trace(' Instantiating ${scriptedCharClassNames4.length} (Animate Atlas) scripted characters...');
log('Instantiating ${scriptedCharClassNames4.length} (Animate Atlas) scripted characters...');
for (charCls in scriptedCharClassNames4)
{
try
{
var character:AnimateAtlasCharacter = ScriptedAnimateAtlasCharacter.init(charCls, DEFAULT_CHAR_ID);
var character:AnimateAtlasCharacter = ScriptedAnimateAtlasCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
log('Loaded character ${character.characterName} (scripted: $charCls)');
characterScriptedClass.set(character.characterId, charCls);
}
catch (e)
{
trace(' FAILED to instantiate scripted Animate Atlas character: ${charCls}');
trace(e);
log(' ERROR '.error() + 'Failed to initialize scripted Animate Atlas character: $charCls');
log(' ERROR '.error() + '$e');
}
}
}
var scriptedCharClassNames5:Array<String> = ScriptedMultiAnimateAtlasCharacter.listScriptClasses();
if (scriptedCharClassNames5.length > 0)
{
log('Instantiating ${scriptedCharClassNames5.length} (Multi-Animate Atlas) scripted characters...');
for (charCls in scriptedCharClassNames5)
{
try
{
var character:MultiAnimateAtlasCharacter = ScriptedMultiAnimateAtlasCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
log('Loaded character ${character.characterName} (scripted: $charCls)');
characterScriptedClass.set(character.characterId, charCls);
}
catch (e)
{
log(' ERROR '.error() + 'Failed to initialize scripted Multi-Animate Atlas character: $charCls');
log(' ERROR '.error() + '$e');
}
}
}
@@ -158,33 +189,35 @@ class CharacterDataParser
// NOTE: Only instantiate the ones not populated above.
// ScriptedBaseCharacter.listScriptClasses() will pick up scripts extending the other classes.
var scriptedCharClassNames:Array<String> = ScriptedBaseCharacter.listScriptClasses();
scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool {
scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool
{
return !(scriptedCharClassNames1.contains(charCls)
|| scriptedCharClassNames2.contains(charCls)
|| scriptedCharClassNames3.contains(charCls)
|| scriptedCharClassNames4.contains(charCls));
|| scriptedCharClassNames4.contains(charCls)
|| scriptedCharClassNames5.contains(charCls));
});
if (scriptedCharClassNames.length > 0)
{
trace(' Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...');
log('Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...');
for (charCls in scriptedCharClassNames)
{
var character:BaseCharacter = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID, Custom);
var character:BaseCharacter = ScriptedBaseCharacter.scriptInit(charCls, DEFAULT_CHAR_ID, Custom);
if (character == null)
{
trace(' Failed to instantiate scripted character: ${charCls}');
log(' ERROR '.error() + 'Failed to initialize scripted character: $charCls');
continue;
}
else
{
trace(' Successfully instantiated scripted character: ${charCls}');
log('Loaded character ${character.characterName} (scripted: $charCls)');
characterScriptedClass.set(character.characterId, charCls);
}
}
}
trace(' Successfully loaded ${Lambda.count(characterCache)} stages.');
log(' INFO '.info() + 'Successfully loaded ${characterCache.size()} stages.');
}
/**
@@ -214,16 +247,18 @@ class CharacterDataParser
if (charData != null) switch (charData.renderType)
{
case CharacterRenderType.AnimateAtlas:
char = ScriptedAnimateAtlasCharacter.init(charScriptClass, charId);
char = ScriptedAnimateAtlasCharacter.scriptInit(charScriptClass, charId);
case CharacterRenderType.MultiSparrow:
char = ScriptedMultiSparrowCharacter.init(charScriptClass, charId);
char = ScriptedMultiSparrowCharacter.scriptInit(charScriptClass, charId);
case CharacterRenderType.Sparrow:
char = ScriptedSparrowCharacter.init(charScriptClass, charId);
char = ScriptedSparrowCharacter.scriptInit(charScriptClass, charId);
case CharacterRenderType.Packer:
char = ScriptedPackerCharacter.init(charScriptClass, charId);
char = ScriptedPackerCharacter.scriptInit(charScriptClass, charId);
case CharacterRenderType.MultiAnimateAtlas:
char = ScriptedMultiAnimateAtlasCharacter.scriptInit(charScriptClass, charId);
default:
// We're going to assume that the script class does the rendering.
char = ScriptedBaseCharacter.init(charScriptClass, charId, CharacterRenderType.Custom);
char = ScriptedBaseCharacter.scriptInit(charScriptClass, charId, CharacterRenderType.Custom);
}
}
else
@@ -238,8 +273,10 @@ class CharacterDataParser
char = new SparrowCharacter(charId);
case CharacterRenderType.Packer:
char = new PackerCharacter(charId);
case CharacterRenderType.MultiAnimateAtlas:
char = new MultiAnimateAtlasCharacter(charId);
default:
trace('[WARN] Creating character with undefined renderType ${charData.renderType}');
trace(' WARNING '.warning() + ' Creating character with undefined renderType ${charData.renderType}');
char = new BaseCharacter(charId, CharacterRenderType.Custom);
}
}
@@ -307,7 +344,7 @@ class CharacterDataParser
if (!Assets.exists(Paths.image(charPath)))
{
trace('[WARN] Character ${char} has no freeplay icon.');
trace(' WARNING '.warning() + ' Character ${char} has no freeplay icon.');
return null;
}
@@ -318,13 +355,14 @@ class CharacterDataParser
{
var frames = Paths.getSparrowAtlas(charPath);
var idleFrame:Null<FlxFrame> = frames.frames.find(function(frame:FlxFrame):Bool {
var idleFrame:Null<FlxFrame> = frames.frames.find(function(frame:FlxFrame):Bool
{
return frame.name.startsWith('idle');
});
if (idleFrame == null)
{
trace('[WARN] Character ${char} has no idle in their freeplay icon.');
trace(' WARNING '.warning() + ' Character ${char} has no idle in their freeplay icon.');
return null;
}
@@ -399,8 +437,8 @@ class CharacterDataParser
}
catch (e)
{
trace(' Error parsing data for character: ${charId}');
trace(' ${e}');
trace(' Error parsing data for character: ${charId}');
trace(' ${e}');
return null;
}
}
@@ -426,6 +464,15 @@ class CharacterDataParser
public static final DEFAULT_SCALE:Float = 1;
public static final DEFAULT_SCROLL:Array<Float> = [0, 0];
public static final DEFAULT_STARTINGANIM:String = 'idle';
public static final DEFAULT_APPLYSTAGEMATRIX:Bool = false;
public static final DEFAULT_ANIMTYPE:String = "framelabel";
public static final DEFAULT_ATLASSETTINGS:funkin.data.stage.StageData.TextureAtlasData = {
swfMode: true,
cacheOnLoad: false,
filterQuality: 1,
applyStageMatrix: false,
useRenderTexture: false
};
/**
* Set unspecified parameters to their defaults.
@@ -483,14 +530,13 @@ class CharacterDataParser
if (input.healthIcon == null)
{
input.healthIcon =
{
id: null,
scale: null,
flipX: null,
isPixel: null,
offsets: null
};
input.healthIcon = {
id: null,
scale: null,
flipX: null,
isPixel: null,
offsets: null
};
}
if (input.healthIcon.id == null)
@@ -554,6 +600,16 @@ class CharacterDataParser
input.flipX = DEFAULT_FLIPX;
}
if (input.applyStageMatrix == null)
{
input.applyStageMatrix = DEFAULT_APPLYSTAGEMATRIX;
}
if (input.atlasSettings == null)
{
input.atlasSettings = DEFAULT_ATLASSETTINGS;
}
if (input.animations.length == 0 && input.startingAnimation != null)
{
return null;
@@ -591,11 +647,21 @@ class CharacterDataParser
{
inputAnimation.flipY = DEFAULT_FLIPY;
}
if (inputAnimation.animType == null)
{
inputAnimation.animType = DEFAULT_ANIMTYPE;
}
}
// All good!
return input;
}
static function log(message:String):Void
{
trace(' CHARACTER '.bold().bg_note_down() + ' $message');
}
}
/**
@@ -619,10 +685,15 @@ enum abstract CharacterRenderType(String) from String to String
public var MultiSparrow = 'multisparrow';
/**
* Renders the character using a spritesheet of symbols and JSON data.
* Renders the character using a single spritesheet of symbols and JSON data.
*/
public var AnimateAtlas = 'animateatlas';
/**
* Renders the character using multiple spritesheets of symbols and JSON data.
*/
public var MultiAnimateAtlas = 'multianimateatlas';
/**
* Renders the character using a custom method.
*/
@@ -635,7 +706,7 @@ enum abstract CharacterRenderType(String) from String to String
typedef CharacterData =
{
/**
* The sematic version number of the character data JSON format.
* The semantic version number of the character data JSON format.
*/
var version:String;
@@ -653,7 +724,7 @@ typedef CharacterData =
/**
* Behavior varies by render type:
* - SPARROW: Path to retrieve both the spritesheet and the XML data from.
* - PACKER: Path to retrieve both the spritsheet and the TXT data from.
* - PACKER: Path to retrieve both the spritesheet and the TXT data from.
*/
var assetPath:String;
@@ -669,6 +740,9 @@ typedef CharacterData =
*/
var healthIcon:Null<HealthIconData>;
/**
* Optional data about the death animation for the character.
*/
var death:Null<DeathData>;
/**
@@ -729,6 +803,23 @@ typedef CharacterData =
* @default false
*/
var flipX:Null<Bool>;
/**
* NOTE: This only applies to animate atlas characters.
*
* Whether to apply the stage matrix, if it was exported from a symbol instance.
* Also positions the Texture Atlas as it displays in Animate.
* Turning this on is only recommended if you prepositioned the character in Animate.
* For other cases, it should be turned off to act similarly to a normal FlxSprite.
*/
var applyStageMatrix:Null<Bool>;
/**
* Various settings for the prop.
* Only available for texture atlases.
*/
@:optional
var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
};
/**
@@ -23,4 +23,6 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData, SpeakerEntryPar
}
}
typedef SpeakerEntryParams = {}
typedef SpeakerEntryParams =
{
}
+21 -14
View File
@@ -1,9 +1,11 @@
package funkin.data.event;
import funkin.play.event.SongEvent;
import flixel.util.FlxSort;
import funkin.data.song.SongData.SongEventData;
import funkin.util.macro.ClassMacro;
import funkin.play.event.ScriptedSongEvent;
import funkin.play.event.SongEvent;
import funkin.util.macro.ClassMacro;
import funkin.util.SortUtil;
/**
* This class statically handles the parsing of internal and scripted song event handlers.
@@ -13,7 +15,7 @@ class SongEventRegistry
{
/**
* Every built-in event class must be added to this list.
* Thankfully, with the power of `SongEventMacro`, this is done automatically.
* Thankfully, with the power of `ClassMacro`, this is done automatically.
*/
static final BUILTIN_EVENTS:List<Class<SongEvent>> = ClassMacro.listSubclassesOf(SongEvent);
@@ -46,12 +48,12 @@ class SongEventRegistry
if (event != null)
{
trace(' Loaded built-in song event: ${event.id}');
trace(' Loaded built-in song event: ${event.id}');
eventCache.set(event.id, event);
}
else
{
trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}');
trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}');
}
}
}
@@ -64,16 +66,16 @@ class SongEventRegistry
for (eventCls in scriptedEventClassNames)
{
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
var event:SongEvent = ScriptedSongEvent.scriptInit(eventCls, "UKNOWN");
if (event != null)
{
trace(' Loaded scripted song event: ${event.id}');
trace(' Loaded scripted song event: ${event.id}');
eventCache.set(event.id, event);
}
else
{
trace(' Failed to instantiate scripted song event class: ${eventCls}');
trace(' Failed to instantiate scripted song event class: ${eventCls}');
}
}
}
@@ -108,8 +110,7 @@ class SongEventRegistry
public static function handleEvent(data:SongEventData):Void
{
var eventKind:String = data.eventKind;
var eventHandler:Null<SongEvent> = eventCache.get(eventKind);
var eventHandler:Null<SongEvent> = getEvent(data.eventKind);
if (eventHandler != null)
{
@@ -117,7 +118,7 @@ class SongEventRegistry
}
else
{
trace('WARNING: No event handler for event with kind: ${eventKind}');
trace('WARNING: No event handler for event with kind: ${data.eventKind}');
}
data.activated = true;
@@ -132,12 +133,14 @@ class SongEventRegistry
}
/**
* Given a list of song events and the current timestamp,
* return a list of events that should be handled.
* @param events The list of available song events.
* @param currentTime The current time in milliseconds.
* @return The list of events which haven't been handled yet.
*/
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool {
var result:Array<SongEventData> = events.filter(function(event:SongEventData):Bool
{
// If the event is already activated, don't activate it again.
if (event.activated) return false;
@@ -146,6 +149,10 @@ class SongEventRegistry
return true;
});
result.sort(SortUtil.eventDataByTime.bind(FlxSort.ASCENDING));
return result;
}
/**
+137 -14
View File
@@ -1,5 +1,6 @@
package funkin.data.event;
@:nullSafety
@:forward(name, title, type, keys, min, max, step, units, defaultValue, iterator)
abstract SongEventSchema(SongEventSchemaRaw)
{
@@ -10,13 +11,22 @@ abstract SongEventSchema(SongEventSchemaRaw)
public function new(?fields:Array<SongEventSchemaField>)
{
this = fields;
this = fields ?? [];
}
/**
* Retrieve a SongEventSchemaField by name. This works even if the field is inside a Frame.
* You can use array access to call this function; `schema["field_name"]`
*
* @param name The name of the field to retreive.
* @return The retrieved field, or null if not found.
*/
@:arrayAccess
public function getByName(name:String):SongEventSchemaField
public function getByName(name:String):Null<SongEventSchemaField>
{
for (field in this)
var allFields = listAllFields(this);
for (field in allFields)
{
if (field.name == name) return field;
}
@@ -24,26 +34,70 @@ abstract SongEventSchema(SongEventSchemaRaw)
return null;
}
public function getFirstField():SongEventSchemaField
/**
* Return whether the field with the given name exists.
* @param name The name of the field to check.
* @return Whether the field exists.
*/
public function hasField(name:String):Bool
{
return abstract.getByName(name) != null;
}
/**
* Retrieve the first field in the schema.
* @return The first field.
*/
public function getFirstField():Null<SongEventSchemaField>
{
return this[0];
}
/**
* Retrieve a field from the schema by numeric index.
* @param key The index of the field to retrieve.
* @return The retrieved field.
*/
@:arrayAccess
public inline function get(key:Int)
public inline function get(key:Int):Null<SongEventSchemaField>
{
return this[key];
}
/**
* Write a field to the schema by numeric index.
* @param k The index of the field to write.
* @param v The new field value to write.
* @return The assigned value.
*/
@:arrayAccess
public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField
{
return this[k] = v;
}
/**
* For a given song event field, retrieve its default value.
* @param name The name of the field to retrieve.
* @return The default value of the field, or null if not found.
*/
public function getDefaultFieldValue(name:String):Null<Dynamic>
{
return getByName(name)?.defaultValue;
}
/**
* For a given song event field, convert the value into a string.
* This is particularly useful for ENUM fields.
*
* @param name The name of the field to display.
* @param value The value of the field to convert.
* @param addUnits Whether to add the units specified by the schema to the resulting string.
* @return The resulting string.
*/
public function stringifyFieldValue(name:String, value:Dynamic, addUnits:Bool = true):String
{
var field:SongEventSchemaField = getByName(name);
var field:Null<SongEventSchemaField> = getByName(name);
if (field == null) return 'Unknown';
switch (field.type)
@@ -62,10 +116,12 @@ abstract SongEventSchema(SongEventSchemaRaw)
return Std.string(value);
case SongEventFieldType.ENUM:
var valueString:String = Std.string(value);
for (key in field.keys.keys())
var fieldKeys:Array<String> = field.keys?.keyValues() ?? [];
for (key in fieldKeys)
{
var value:Null<Dynamic> = field.keys?.get(key) ?? null;
// Comparing these values as strings because comparing Dynamic variables is jank.
if (Std.string(field.keys.get(key)) == valueString) return key;
if (Std.string(value) == valueString) return key;
}
return valueString;
default:
@@ -73,7 +129,13 @@ abstract SongEventSchema(SongEventSchemaRaw)
}
}
function addUnitsToString(value:String, field:SongEventSchemaField)
/**
* Apply the song event field's specified units to the value.
* @param value The value to add the units to.
* @param field The field to get the units from.
* @return The resulting string.
*/
function addUnitsToString(value:String, field:SongEventSchemaField):String
{
if (field.units == null || field.units == '') return value;
@@ -81,10 +143,50 @@ abstract SongEventSchema(SongEventSchemaRaw)
return value + (NO_SPACE_UNITS.contains(unit) ? '' : ' ') + '${unit}';
}
/**
* Build a flat list of all the fields in the schema. Frames containing children are parsed recursively.
*
* @param schema The song event schema schema to parse.
* @return The array of fields, parsed recursively from the schema and its child frames.
*/
function listAllFields(schema:SongEventSchemaRaw):Array<SongEventSchemaField>
{
var result:Array<SongEventSchemaField> = [];
for (field in schema)
{
if (field.children == null)
{
result.push(field);
}
else
{
result = result.concat(field.children);
}
}
return result;
}
/**
* Get a list of all the field names in the schema, so they can be iterated over and retrieved.
* @return The list of field names.
*/
public function listAllFieldNames():Array<String>
{
return listAllFields(this).map((field:SongEventSchemaField) -> field.name);
}
}
/**
* The raw underlying data for a song event schema is an array of fields.
*/
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;
/**
* The individual fields of a song event schema.
*/
typedef SongEventSchemaField =
{
/**
@@ -135,37 +237,58 @@ typedef SongEventSchemaField =
*/
?units:String,
/**
* Used for FRAME values.
* The child components that this frame contains.
*/
?children:SongEventSchemaRaw,
/**
* Used for FRAME values.
* Whether to make the frame be collapsible.
*/
?collapsible:Bool,
/**
* An optional default value for the field.
*/
?defaultValue:Dynamic,
}
/**
* The available field types for a song event schema.
*/
enum abstract SongEventFieldType(String) from String to String
{
/**
* The STRING type will display as a text field.
*/
var STRING = "string";
public var STRING = "string";
/**
* The INTEGER type will display as a text field that only accepts numbers.
*/
var INTEGER = "integer";
public var INTEGER = "integer";
/**
* The FLOAT type will display as a text field that only accepts numbers.
*/
var FLOAT = "float";
public var FLOAT = "float";
/**
* The BOOL type will display as a checkbox.
*/
var BOOL = "bool";
public var BOOL = "bool";
/**
* The ENUM type will display as a dropdown.
* Make sure to specify the `keys` field in the schema.
*/
var ENUM = "enum";
public var ENUM = "enum";
/**
* The FRAME type will display a frame with child components.
* Make sure to specify the `children` field in the schema.
*/
public var FRAME = "frame";
}
@@ -24,4 +24,6 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData, AlbumEntryParams> imp
}
}
typedef AlbumEntryParams = {}
typedef AlbumEntryParams =
{
}
@@ -89,7 +89,7 @@ class PlayerData
updateVersionToLatest();
var writer = new json2object.JsonWriter<PlayerData>();
return writer.write(this, pretty ? ' ' : null);
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
@@ -103,6 +103,10 @@ class PlayerFreeplayDJData
var assetPath:String;
var animations:Array<AnimationData>;
@:optional
@:default(false)
var applyStageMatrix:Bool;
@:optional
@:default("BOYFRIEND")
var text1:String;
@@ -125,17 +129,33 @@ class PlayerFreeplayDJData
var charSelect:Null<PlayerFreeplayDJCharSelectData>;
@:optional
var cartoon:Null<PlayerFreeplayDJCartoonData>;
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
@:optional
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
public var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
@:optional
@:default("animateatlas")
public var renderType:Null<String>;
@:optional
@:default("")
public var scriptClass:Null<String>;
@:optional
@:default(false)
public var useAnimatePosition:Bool;
@:optional
@:default([0, 0])
var offsets:Array<Float>;
public function new()
{
animationMap = new Map();
}
function mapAnimations()
function mapAnimations():Void
{
if (animationMap == null) animationMap = new Map();
if (prefixToOffsetsMap == null) prefixToOffsetsMap = new Map();
@@ -149,9 +169,37 @@ class PlayerFreeplayDJData
}
}
public function getAtlasPath():String
public inline function getAssetPath():String return assetPath; // return assetPath;
public inline function getAnimationsList():Array<AnimationData> return animations;
public function useApplyStageMatrix():Bool
{
return Paths.animateAtlas(assetPath);
return applyStageMatrix;
}
public function getGlobalOffsets():Array<Float>
{
return offsets;
}
/**
* Normally, we'd let `FunkinSprite` handle the settings validation, but
* Freeplay DJs have a special case where the turntable lights use a movieclip
* that remains static without SWF mode enabled!
* So we have to manually validate the settings to have SWF mode enabled by default.
*
* @return The configuration for the texture atlas.
*/
public function getAtlasSettings():funkin.graphics.FunkinSprite.AtlasSpriteSettings
{
return {
swfMode: atlasSettings?.swfMode ?? true,
cacheOnLoad: atlasSettings?.cacheOnLoad ?? false,
filterQuality: cast atlasSettings?.filterQuality ?? animate.FlxAnimateFrames.FilterQuality.MEDIUM,
applyStageMatrix: atlasSettings?.applyStageMatrix ?? false,
useRenderTexture: atlasSettings?.useRenderTexture ?? false
}
}
public function getFreeplayDJText(index:Int):String
@@ -190,33 +238,6 @@ class PlayerFreeplayDJData
return getAnimationOffsetsByPrefix(getAnimationPrefix(name));
}
// TODO: These should really be frame labels, ehe.
public function getCartoonSoundClickFrame():Int
{
return cartoon?.soundClickFrame ?? 80;
}
public function getCartoonSoundCartoonFrame():Int
{
return cartoon?.soundCartoonFrame ?? 85;
}
public function getCartoonLoopBlinkFrame():Int
{
return cartoon?.loopBlinkFrame ?? 112;
}
public function getCartoonLoopFrame():Int
{
return cartoon?.loopFrame ?? 166;
}
public function getCartoonChannelChangeFrame():Int
{
return cartoon?.channelChangeFrame ?? 60;
}
public function getFistPumpIntroStartFrame():Int
{
return fistPump?.introStartFrame ?? 0;
@@ -334,6 +355,10 @@ typedef PlayerResultsAnimationData =
*/
var renderType:String;
@:optional
@:default(false)
var applyStageMatrix:Bool;
@:optional
var assetPath:Null<String>;
@@ -384,15 +409,6 @@ typedef PlayerFreeplayDJCharSelectData =
var transitionDelay:Float;
}
typedef PlayerFreeplayDJCartoonData =
{
var soundClickFrame:Int;
var soundCartoonFrame:Int;
var loopBlinkFrame:Int;
var loopFrame:Int;
var channelChangeFrame:Int;
}
typedef PlayerFreeplayDJFistPumpData =
{
@:default(0)
@@ -70,7 +70,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
public function hasNewCharacter():Bool
{
#if (!UNLOCK_EVERYTHING)
var charactersSeen = Save.instance.charactersSeen.clone();
var charactersSeen = Save.instance.charactersSeen.value.clone();
for (charId in listEntryIds())
{
@@ -94,7 +94,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
var result = [];
#if (!UNLOCK_EVERYTHING)
var charactersSeen = Save.instance.charactersSeen.clone();
var charactersSeen = Save.instance.charactersSeen.value.clone();
for (charId in listEntryIds())
{
var player = fetchEntry(charId);
@@ -143,9 +143,11 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
#if UNLOCK_EVERYTHING
return true;
#else
return Save.instance.charactersSeen.contains(characterId);
return Save.instance.charactersSeen.value.contains(characterId);
#end
}
}
typedef PlayerEntryParams = {}
typedef PlayerEntryParams =
{
}
@@ -25,4 +25,6 @@ class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleDat
}
}
typedef FreeplayStyleEntryParams = {}
typedef FreeplayStyleEntryParams =
{
}
+11 -3
View File
@@ -219,9 +219,17 @@ typedef NoteStyleData_Countdown =
var audioPath:String;
}
typedef NoteStyleData_HoldNote = {}
typedef NoteStyleData_Judgement = {}
typedef NoteStyleData_ComboNum = {}
typedef NoteStyleData_HoldNote =
{
}
typedef NoteStyleData_Judgement =
{
}
typedef NoteStyleData_ComboNum =
{
}
/**
* Data on animations for each direction of the strumline.
@@ -31,4 +31,6 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData, NoteStyle
}
}
typedef NoteStyleEntryParams = {}
typedef NoteStyleEntryParams =
{
}
+213 -20
View File
@@ -19,7 +19,6 @@ class SongMetadata implements ICloneable<SongMetadata>
* A semantic versioning string for the song data format.
*
*/
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version;
@@ -53,7 +52,6 @@ class SongMetadata implements ICloneable<SongMetadata>
*/
public var playData:SongPlayData;
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
public var generatedBy:String;
@:optional
@@ -126,7 +124,7 @@ class SongMetadata implements ICloneable<SongMetadata>
// I believe @:jignored should be ignored by the writer?
// var output = this.clone();
// output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer.
return writer.write(this, pretty ? ' ' : null);
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
@@ -352,7 +350,6 @@ class SongMusicData implements ICloneable<SongMusicData>
* A semantic versioning string for the song data format.
*
*/
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version;
@@ -463,6 +460,7 @@ class SongPlayData implements ICloneable<SongPlayData>
*/
@:optional
@:default(['normal' => 0])
@:order(funkin.util.Constants.DEFAULT_DIFFICULTY_LIST_FULL)
public var ratings:Map<String, Int>;
/**
@@ -568,7 +566,7 @@ class SongCharacterData implements ICloneable<SongCharacterData>
this.opponent = opponent;
this.instrumental = instrumental;
this.altInstrumentals = altInstrumentals;
this.altInstrumentals = altInstrumentals ?? [];
this.opponentVocals = opponentVocals;
this.playerVocals = playerVocals;
@@ -595,16 +593,16 @@ class SongCharacterData implements ICloneable<SongCharacterData>
class SongChartData implements ICloneable<SongChartData>
{
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version;
@:order(funkin.util.Constants.DEFAULT_DIFFICULTY_LIST_FULL)
public var scrollSpeed:Map<String, Float>;
public var events:Array<SongEventData>;
@:order(funkin.util.Constants.DEFAULT_DIFFICULTY_LIST_FULL)
public var notes:Map<String, Array<SongNoteData>>;
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
public var generatedBy:String;
/**
@@ -664,7 +662,7 @@ class SongChartData implements ICloneable<SongChartData>
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<SongChartData>(ignoreNullOptionals);
return writer.write(this, pretty ? ' ' : null);
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
@@ -700,6 +698,10 @@ class SongChartData implements ICloneable<SongChartData>
}
}
/**
* The raw underlying data for a song event.
* This value gets wrapped in an `abstract` to allow operators to be overloaded.
*/
class SongEventDataRaw implements ICloneable<SongEventDataRaw>
{
/**
@@ -735,7 +737,7 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
/**
* Whether this event has been activated.
* This is only used internally by the game. It should not be serialized.
* This is only used internally by the game during gameplay. It should not be serialized.
*/
@:jignored
public var activated:Bool = false;
@@ -747,9 +749,19 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
this.value = value;
}
/**
* The cached step time of the event. Should not be serialized in case the BPM of the song changes.
*/
@:jignored
var _stepTime:Null<Float> = null;
/**
* Get the position of the event in the song, in steps.
* This value is cached for performance. Use `force` to require a recalculation.
*
* @param force Force the value to be recalculated.
* @return The position of the event in the song, in steps.
*/
public function getStepTime(force:Bool = false):Float
{
if (_stepTime != null && !force) return _stepTime;
@@ -757,6 +769,10 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
return _stepTime = Conductor.instance.getTimeInSteps(this.time);
}
/**
* Clone this event data, creating an independent instance with identical data.
* @return The newly created event data.
*/
public function clone():SongEventDataRaw
{
return new SongEventDataRaw(this.time, this.eventKind, this.value);
@@ -764,7 +780,9 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
{
if (this.value == null) return {};
if (this.value == null) return
{
};
if (Std.isOfType(this.value, Array))
{
var result:haxe.DynamicAccess<Dynamic> = {};
@@ -784,26 +802,49 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
}
}
/**
* Retrieve the SongEvent handler class for this event.
* @return The handler class, or `null` if not found.
*/
public function getHandler():Null<SongEvent>
{
return SongEventRegistry.getEvent(this.eventKind);
}
/**
* Retrieve the SongEventSchema for this event.
* @return The schema, or `null` if not found.
*/
public function getSchema():Null<SongEventSchema>
{
return SongEventRegistry.getEventSchema(this.eventKind);
}
/**
* Retrieve a field from this event's data, as a `Dynamic` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getDynamic(key:String):Null<Dynamic>
{
return this.value == null ? null : Reflect.field(this.value, key);
}
/**
* Retrieve a field from this event's data, as a `Bool` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getBool(key:String):Null<Bool>
{
return this.value == null ? null : cast Reflect.field(this.value, key);
}
/**
* Retrieve a field from this event's data, as an `Int` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getInt(key:String):Null<Int>
{
if (this.value == null) return null;
@@ -814,6 +855,11 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
return cast result;
}
/**
* Retrieve a field from this event's data, as a `Float` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getFloat(key:String):Null<Float>
{
if (this.value == null) return null;
@@ -824,21 +870,60 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
return cast result;
}
/**
* Retrieve a field from this event's data, as a `String` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getString(key:String):String
{
return this.value == null ? null : cast Reflect.field(this.value, key);
}
/**
* Retrieve a field from this event's data, as an `Array<Dynamic>` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getArray(key:String):Array<Dynamic>
{
return this.value == null ? null : cast Reflect.field(this.value, key);
}
/**
* Retrieve a field from this event's data, as an `Array<Bool>` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getBoolArray(key:String):Array<Bool>
{
return this.value == null ? null : cast Reflect.field(this.value, key);
}
/**
* Retrieve a field from this event's data, as an `Array<Float>` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getFloatArray(key:String):Array<Float>
{
return this.value == null ? null : cast Reflect.field(this.value, key);
}
/**
* Retrieve a field from this event's data, as an `Array<String>` value.
* @param key The name of the field to retrieve.
* @return The field value, or `null` if not provided.
*/
public function getStringArray(key:String):Array<Float>
{
return this.value == null ? null : cast Reflect.field(this.value, key);
}
/**
* Build the tooltip string for this particular instance of the song event.
* @return A tooltip string, containing readable values for the event's data.
*/
public function buildTooltip():String
{
var eventHandler = getHandler();
@@ -851,15 +936,26 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
var defaultKey = eventSchema.getFirstField()?.name;
var valueStruct:haxe.DynamicAccess<Dynamic> = valueAsStruct(defaultKey);
for (fieldName in eventSchema.listAllFieldNames())
{
var fieldValue:Dynamic = valueStruct.exists(fieldName) ? valueStruct.get(fieldName) : eventSchema.getDefaultFieldValue(fieldName);
var title:String = eventSchema.getByName(fieldName)?.title ?? 'UnknownField';
var valueStr:String = eventSchema.stringifyFieldValue(fieldName, fieldValue) ?? 'UnknownValue';
result += '\n- ${title}: ${valueStr}';
}
for (pair in valueStruct.keyValueIterator())
{
var key = pair.key;
var value = pair.value;
var fieldName:String = pair.key;
var fieldValue:Dynamic = pair.value;
var title = eventSchema.getByName(key)?.title ?? 'UnknownField';
// Already populated above.
if (eventSchema.hasField(fieldName)) continue;
// if (eventSchema.stringifyFieldValue(key, value) != null) trace(eventSchema.stringifyFieldValue(key, value));
var valueStr = eventSchema.stringifyFieldValue(key, value) ?? 'UnknownValue';
var title:String = eventSchema.getByName(fieldName)?.title ?? 'Unknown Field ($fieldName)';
var valueStr:String = eventSchema.stringifyFieldValue(fieldName, fieldValue) ?? 'Unknown Value ($fieldValue)';
result += '\n- ${title}: ${valueStr}';
}
@@ -880,41 +976,87 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
this = new SongEventDataRaw(time, eventKind, value);
}
/**
* Create an independent copy of this event with the same underlying data.
* @return The newly created event.
*/
public function clone():SongEventData
{
return new SongEventData(this.time, this.eventKind, this.value);
}
/**
* Check whether this event is equal to another event.
* Ex. `event1 == event2`
*
* @param other The other event to compare.
* @return Whether the events have the same time, kind, and data.
*/
@:op(A == B)
public function op_equals(other:SongEventData):Bool
{
return this.time == other.time && this.eventKind == other.eventKind && this.value == other.value;
}
/**
* Check whether this event is not equal to another event.
* Ex. `event1 != event2`
*
* @param other The other event to compare.
* @return Whether the events have a different same time, kind, or data.
*/
@:op(A != B)
public function op_notEquals(other:SongEventData):Bool
{
return this.time != other.time || this.eventKind != other.eventKind || this.value != other.value;
}
/**
* Check whether this event is later than another event.
* Ex. `event1 > event2`
*
* @param other The other event to compare.
* @return Bool
*/
@:op(A > B)
public function op_greaterThan(other:SongEventData):Bool
{
return this.time > other.time;
}
/**
* Check whether this event is earlier than another event.
* Ex. `event1 < event2`
*
* @param other The other event to compare.
* @return Whether this event is earlier than the other event.
*/
@:op(A < B)
public function op_lessThan(other:SongEventData):Bool
{
return this.time < other.time;
}
/**
* Check whether this event is later than or at the same time as another event.
* Ex. `event1 >= event2`
*
* @param other The other event to compare.
* @return Whether this event is later than or at the same time as the other event.
*/
@:op(A >= B)
public function op_greaterThanOrEquals(other:SongEventData):Bool
{
return this.time >= other.time;
}
/**
* Check whether this event is earlier than or at the same time as another event.
* Ex. `event1 <= event2`
*
* @param other The other event to compare.
* @return Whether this event is earlier than or at the same time as the other event.
*/
@:op(A <= B)
public function op_lessThanOrEquals(other:SongEventData):Bool
{
@@ -922,7 +1064,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
}
/**
* Produces a string representation suitable for debugging.
* Produces a string representation of this event, suitable for debugging.
*/
public function toString():String
{
@@ -930,6 +1072,9 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
}
}
/**
* The underlying data for a single note.
*/
class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
{
/**
@@ -991,6 +1136,10 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
return this.kind = value;
}
/**
* The parameters for the note.
* Used for custom behavior on custom note kinds. Defaults to an empty array.
*/
@:alias("p")
@:default([])
@:optional
@@ -1010,12 +1159,22 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
* Strips the strumline index from the data.
*
* 0 = left, 1 = down, 2 = up, 3 = right
*
* @param strumlineSize The size of the strumline for the current song. Defaults to 4.
* @return The direction of the note as an index.
*/
public inline function getDirection(strumlineSize:Int = 4):Int
{
return this.data % strumlineSize;
}
/**
* The name of the direction of the note, if applicable.
* Strips the strumline index from the data.
*
* @param strumlineSize The size of the strumline for the current song. Defaults to 4.
* @return The direction of the note as a name.
*/
public function getDirectionName(strumlineSize:Int = 4):String
{
return SongNoteData.buildDirectionName(this.data, strumlineSize);
@@ -1026,6 +1185,9 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
* Strips the direction from the data.
*
* 0 = player, 1 = opponent, etc.
*
* @param strumlineSize The size of the strumline for the current song. Defaults to 4.
* @return The strumline index of the note.
*/
public function getStrumlineIndex(strumlineSize:Int = 4):Int
{
@@ -1043,6 +1205,11 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
return getStrumlineIndex(strumlineSize) == 0;
}
/**
* The position of the note in the song, in steps.
* Calculated from the time and the BPM.
* Cached for performance. Set to `null` to recalculate.
*/
@:jignored
var _stepTime:Null<Float> = null;
@@ -1078,6 +1245,10 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
return _stepLength = Conductor.instance.getTimeInSteps(this.time + this.length) - getStepTime();
}
/**
* Set the length of the hold note, in steps.
* @param value The desired length of the hold note, in steps. Use `0` to make this a tap note.
*/
public function setStepLength(value:Float):Void
{
if (value <= 0)
@@ -1097,16 +1268,24 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
_stepLength = null;
}
/**
* Clone the `params` data for this node, creating a new independent instance.
* @return A new array of cloned `NoteParamData` instances.
*/
public function cloneParams():Array<NoteParamData>
{
var params:Array<NoteParamData> = [];
var newParams:Array<NoteParamData> = [];
for (param in this.params)
{
params.push(param.clone());
newParams.push(param.clone());
}
return params;
return newParams;
}
/**
* Clone this song note data, creating a new independent instance with identical data.
* @return The newly created song note data.
*/
public function clone():SongNoteDataRaw
{
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
@@ -1118,6 +1297,10 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
}
/**
* Build a tooltip string for this note, as seen when hovering in the Chart Editor.
* @return The tooltip string to display.
*/
public function buildTooltip():String
{
if ((this.kind?.length ?? 0) == 0) return "";
@@ -1164,14 +1347,24 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
}
}
/**
* Whether this note is a hold note.
*/
@:jignored
public var isHoldNote(get, never):Bool;
public function get_isHoldNote():Bool
function get_isHoldNote():Bool
{
return this.length > 0;
}
/**
* Determine if two notes are equal.
* Ex. `note1 == note2`
*
* @param other The other note to compare.
* @return Whether the two notes have equal data.
*/
@:op(A == B)
public function op_equals(other:SongNoteData):Bool
{
+88 -16
View File
@@ -24,7 +24,8 @@ class SongDataUtils
*/
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Float):Array<SongNoteData>
{
var offsetNote = function(note:SongNoteData):SongNoteData {
var offsetNote = function(note:SongNoteData):SongNoteData
{
var time:Float = note.time + offset;
var data:Int = note.data;
var length:Float = note.length;
@@ -46,7 +47,8 @@ class SongDataUtils
*/
public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
{
return events.map(function(event:SongEventData):SongEventData {
return events.map(function(event:SongEventData):SongEventData
{
return new SongEventData(event.time + offset, event.eventKind, event.value);
});
}
@@ -61,7 +63,8 @@ class SongDataUtils
*/
public static function clampSongNoteData(notes:Array<SongNoteData>, startTime:Float, endTime:Float):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool {
return notes.filter(function(note:SongNoteData):Bool
{
return note.time >= startTime && note.time <= endTime;
});
}
@@ -76,7 +79,8 @@ class SongDataUtils
*/
public static function clampSongEventData(events:Array<SongEventData>, startTime:Float, endTime:Float):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool {
return events.filter(function(event:SongEventData):Bool
{
return event.time >= startTime && event.time <= endTime;
});
}
@@ -92,7 +96,8 @@ class SongDataUtils
{
if (notes.length == 0 || subtrahend.length == 0) return notes;
var result = notes.filter(function(note:SongNoteData):Bool {
var result = notes.filter(function(note:SongNoteData):Bool
{
for (x in subtrahend)
{
// The currently iterated note is in the subtrahend array.
@@ -117,7 +122,8 @@ class SongDataUtils
{
if (events.length == 0 || subtrahend.length == 0) return events;
return events.filter(function(event:SongEventData):Bool {
return events.filter(function(event:SongEventData):Bool
{
for (x in subtrahend)
{
// The currently iterated event is in the subtrahend array.
@@ -134,7 +140,8 @@ class SongDataUtils
*/
public static function flipNotes(notes:Array<SongNoteData>, strumlineSize:Int = 4):Array<SongNoteData>
{
return notes.map(function(note:SongNoteData):SongNoteData {
return notes.map(function(note:SongNoteData):SongNoteData
{
var newData = note.data;
if (newData < strumlineSize) newData += strumlineSize;
@@ -145,6 +152,63 @@ class SongDataUtils
});
}
/**
* Create an array of notes whose note data is mirrored.
* Does not mutate the original array.
*
* @param flip Flip the notes if the notes given are in both strumlines, so that result isn't inverted when mirrored.
* @param mirrorX Mirror along the X axis, aka the directions of the notes.
* @param mirrorY Mirror along the Y axis, aka the time of the notes.
*/
public static function mirrorNotes(notes:Array<SongNoteData>, strumlineSize:Int = 4, flip:Bool = false, mirrorX:Bool = true,
mirrorY:Bool = true):Array<SongNoteData>
{
var minTime = notes[0].time;
var maxTime = notes[0].time;
var minStrumline = notes[0].data;
var maxStrumline = notes[0].data;
for (note in notes)
{
// Find the maximum and minimum time and strumline positions
// I wish there was a better way of doing this
if (flip)
{
if (note.data < minStrumline) minStrumline = note.data;
else if (note.data > maxStrumline) maxStrumline = note.data;
}
if (note.time < minTime) minTime = note.time;
else if (note.time > maxTime) maxTime = note.time;
}
var timeDiff = minTime + (maxTime - minTime) / 2;
if (flip && minStrumline < strumlineSize && strumlineSize < maxStrumline)
{
// Flip the notes if one of the notes is on the other strum
// Otherwise they'll be inverted when mirrored
notes = flipNotes(notes);
}
return notes.map(function(note:SongNoteData):SongNoteData
{
var newData = note.data;
var newTime = note.time;
if (mirrorX)
{
if (newData < strumlineSize) newData = strumlineSize - 1 - newData;
else
newData = strumlineSize + strumlineSize * 2 - 1 - newData;
}
if (mirrorY)
{
if (newTime < timeDiff) newTime += (timeDiff - newTime) * 2;
else if (newTime > timeDiff) newTime -= (newTime - timeDiff) * 2;
}
return new SongNoteData(newTime, newData, note.length, note.kind);
});
}
/**
* Prepare an array of notes to be used as the clipboard data.
*
@@ -175,7 +239,8 @@ class SongDataUtils
public static function sortNotes(notes:Array<SongNoteData>, desc:Bool = false):Array<SongNoteData>
{
// TODO: Modifies the array in place. Is this okay?
notes.sort(function(a:SongNoteData, b:SongNoteData):Int {
notes.sort(function(a:SongNoteData, b:SongNoteData):Int
{
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
});
return notes;
@@ -187,7 +252,8 @@ class SongDataUtils
public static function sortEvents(events:Array<SongEventData>, desc:Bool = false):Array<SongEventData>
{
// TODO: Modifies the array in place. Is this okay?
events.sort(function(a:SongEventData, b:SongEventData):Int {
events.sort(function(a:SongEventData, b:SongEventData):Int
{
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
});
return events;
@@ -199,7 +265,8 @@ class SongDataUtils
public static function sortTimeChanges(timeChanges:Array<SongTimeChange>, desc:Bool = false):Array<SongTimeChange>
{
// TODO: Modifies the array in place. Is this okay?
timeChanges.sort(function(a:SongTimeChange, b:SongTimeChange):Int {
timeChanges.sort(function(a:SongTimeChange, b:SongTimeChange):Int
{
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.timeStamp, b.timeStamp);
});
return timeChanges;
@@ -212,7 +279,7 @@ class SongDataUtils
{
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<SongClipboardItems>(ignoreNullOptionals);
var dataString:String = writer.write(data, ' ');
var dataString:String = writer.write(data, ' ');
ClipboardUtil.setClipboard(dataString);
@@ -256,7 +323,8 @@ class SongDataUtils
*/
public static function getNotesInTimeRange(notes:Array<SongNoteData>, start:Float, end:Float):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool {
return notes.filter(function(note:SongNoteData):Bool
{
return note.time >= start && note.time <= end;
});
}
@@ -266,7 +334,8 @@ class SongDataUtils
*/
public static function getEventsInTimeRange(events:Array<SongEventData>, start:Float, end:Float):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool {
return events.filter(function(event:SongEventData):Bool
{
return event.time >= start && event.time <= end;
});
}
@@ -276,7 +345,8 @@ class SongDataUtils
*/
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool {
return notes.filter(function(note:SongNoteData):Bool
{
return note.data >= start && note.data <= end;
});
}
@@ -286,7 +356,8 @@ class SongDataUtils
*/
public static function getNotesWithData(notes:Array<SongNoteData>, data:Array<Int>):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool {
return notes.filter(function(note:SongNoteData):Bool
{
return data.indexOf(note.data) != -1;
});
}
@@ -296,7 +367,8 @@ class SongDataUtils
*/
public static function getEventsWithKind(events:Array<SongEventData>, kinds:Array<String>):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool {
return events.filter(function(event:SongEventData):Bool
{
return kinds.indexOf(event.eventKind) != -1;
});
}
+39 -11
View File
@@ -56,7 +56,7 @@ using funkin.data.song.migrator.SongDataMigrator;
// SCRIPTED ENTRIES
//
var scriptedEntryClassNames:Array<String> = getScriptedClassNames();
log('Parsing ${scriptedEntryClassNames.length} scripted entries...');
log(' INFO '.info() + 'Parsing ${scriptedEntryClassNames.length} scripted entries...');
for (entryCls in scriptedEntryClassNames)
{
@@ -85,10 +85,12 @@ using funkin.data.song.migrator.SongDataMigrator;
//
// UNSCRIPTED ENTRIES
//
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('songs/', '-metadata.json').map(function(songDataPath:String):String {
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('songs/', '-metadata.json').map(function(songDataPath:String):String
{
return songDataPath.split('/')[0];
});
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool
{
return !entries.exists(entryId);
});
log('Parsing ${unscriptedEntryIds.length} unscripted entries...');
@@ -99,15 +101,15 @@ using funkin.data.song.migrator.SongDataMigrator;
var entry:Null<Song> = createEntry(entryId);
if (entry != null)
{
trace(' Loaded entry data: ${entry}');
log('Loaded entry data: ${entry}');
entries.set(entry.id, entry);
}
}
catch (e:Dynamic)
{
// Print the error.
trace(' Failed to load entry data: ${entryId}');
trace(e);
log(' ERROR '.error() + 'Failed to load entry data: ${entryId}');
log(' ERROR '.error() + e);
continue;
}
}
@@ -129,6 +131,29 @@ using funkin.data.song.migrator.SongDataMigrator;
return parseEntryMetadataRaw(contents);
}
public override function isScriptedEntry(id:String, ?params:Null<SongEntryParams>)
{
var variation:String = params?.variation ?? Constants.DEFAULT_VARIATION;
if (variation != Constants.DEFAULT_VARIATION)
{
return scriptedSongVariations.exists('${id}:${variation}');
}
return super.isScriptedEntry(id, params);
}
public override function getScriptedEntryClassName(id:String, ?params:Null<SongEntryParams>):Null<String>
{
var variation:String = params?.variation ?? Constants.DEFAULT_VARIATION;
if (variation != Constants.DEFAULT_VARIATION)
{
final variationSongId:ScriptedSong = cast scriptedSongVariations.get('${id}:${variation}');
@:privateAccess
var path:String = variationSongId._asc._c.name;
return path;
}
return super.getScriptedEntryClassName(id, params);
}
/**
* We override `fetchEntry` to handle song variations!
*/
@@ -426,12 +451,13 @@ using funkin.data.song.migrator.SongDataMigrator;
}
}
public function parseEntryChartDataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongChartData>
public function parseEntryChartDataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version,
?variation:String):Null<SongChartData>
{
// If a version rule is not specified, do not check against it.
if (SONG_CHART_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_CHART_DATA_VERSION_RULE))
{
return parseEntryChartDataRaw(contents, fileName);
return parseEntryChartDataRaw(contents, fileName, variation);
}
else
{
@@ -445,7 +471,7 @@ using funkin.data.song.migrator.SongDataMigrator;
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
if (!openfl.Assets.exists(entryFilePath))
{
trace(' [WARN] Could not locate file $entryFilePath');
trace(' WARNING '.bold().bg_yellow() + ' Could not locate file $entryFilePath');
return null;
}
var rawJson:Null<String> = openfl.Assets.getText(entryFilePath);
@@ -523,7 +549,7 @@ using funkin.data.song.migrator.SongDataMigrator;
if (character == null)
{
trace(' [WARN] Could not locate character $characterId');
trace(' WARNING '.bold().bg_yellow() + ' Could not locate character $characterId');
return allDifficulties;
}
@@ -539,9 +565,11 @@ using funkin.data.song.migrator.SongDataMigrator;
}
}
allDifficulties.sort(funkin.util.SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
if (allDifficulties.length == 0)
{
trace(' [WARN] No difficulties found. Returning default difficulty list.');
trace(' WARNING '.bold().bg_yellow() + ' No difficulties found. Returning default difficulty list.');
allDifficulties = Constants.DEFAULT_DIFFICULTY_LIST.copy();
}
@@ -12,7 +12,6 @@ class ChartManifestData
public static final invalidIdRegex:EReg = ~/[\/\\:*?"<>|]/g;
@:default(funkin.data.song.importer.ChartManifestData.CHART_MANIFEST_DATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:thx.semver.Version;
@@ -22,6 +21,7 @@ class ChartManifestData
* The metadata and chart data file names are derived from this.
*/
public var songId(default, set):String;
public function set_songId(value:String):String
{
return songId = invalidIdRegex.replace(value.trim(), '');
@@ -71,7 +71,7 @@ class ChartManifestData
updateVersionToLatest();
var writer = new json2object.JsonWriter<ChartManifestData>();
return writer.write(this, pretty ? ' ' : null);
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
@@ -27,7 +27,9 @@ class LegacySongData
public var notes:Either<Array<LegacyNoteSection>, LegacyNoteData>;
public var song:String; // Song name
public function new() {}
public function new()
{
}
public function toString():String
{
@@ -0,0 +1,82 @@
package funkin.data.song.importer;
/**
* Structure of a parsed Osu!Mania .osu file
* Stuctured like a INI file format by CSV for HitObjects and more
*/
typedef OsuManiaData =
{
var General:
{
var PreviewTime:Int;
};
var Editor:
{
var DistanceSpacing:Float;
var BeatDivisor:Int;
var GridSize:Int;
};
var Metadata:
{
var Title:String;
var TitleUnicode:String;
var Artist:String;
var ArtistUnicode:String;
var Creator:String;
var Version:String;
};
var Difficulty:
{
var OverallDifficulty:Int;
var SliderMultiplier:Float;
var SliderTickRate:Float;
var CircleSize:Int;
};
var HitObjects:Array<ManiaHitObject>;
var TimingPoints:Array<TimingPoint>;
var Events:Array<Any>;
}
class TimingPoint
{
public var time:Float;
public var beatLength:Float;
public var meter:Int;
public var sampleSet:Int;
public var sampleIndex:Int;
public var volume:Int;
public var uninherited:Int;
public var effects:Int;
public var bpm:Null<Float>;
public var sv:Null<Float>;
public function new(time:Float, beatLength:Float, meter:Int, sampleSet:Int, sampleIndex:Int, volume:Int, uninherited:Int, effects:Int)
{
this.time = time;
this.beatLength = beatLength;
this.meter = meter;
this.sampleSet = sampleSet;
this.sampleIndex = sampleIndex;
this.volume = volume;
this.uninherited = uninherited;
this.effects = effects;
// Derived values
this.bpm = (uninherited == 1) ? (Math.round((60000 / beatLength) * 10) / 10) : null;
this.sv = (uninherited == 0) ? (beatLength / 100) : null; // Just incase someone wants to add Scroll Velocity Support
}
}
class ManiaHitObject
{
public var time:Int;
public var column:Int;
public var holdDuration:Int;
public function new(time:Int, column:Int, holdDuration:Int)
{
this.time = time;
this.column = column;
this.holdDuration = holdDuration;
}
}
@@ -0,0 +1,203 @@
package funkin.data.song.importer;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongCharacterData;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.importer.OsuManiaData;
import funkin.data.song.importer.OsuManiaData.TimingPoint;
import funkin.data.song.importer.OsuManiaData.ManiaHitObject;
class OsuManiaImporter
{
public static function parseOsuFile(osuContent:String):OsuManiaData
{
var lines:Array<String> = osuContent.split("\n");
var result:Dynamic = {};
var currentSection:String = null;
var nonCSVLikeSections = ["General", "Editor", "Metadata", "Difficulty"];
for (line in lines)
{
line = StringTools.trim(line);
if (line == "" || StringTools.startsWith(line, "//")) continue;
// Section header like [General]
var sectionRegex = ~/^\[(.+)\]$/;
if (sectionRegex.match(line))
{
currentSection = sectionRegex.matched(1);
if (nonCSVLikeSections.contains(currentSection))
{
Reflect.setField(result, currentSection, {
});
}
else
{
Reflect.setField(result, currentSection, []);
}
continue;
}
// Key-value pairs (INI style)
if (currentSection != null && nonCSVLikeSections.contains(currentSection))
{
var parts:Array<String> = line.split(":");
var key:String = StringTools.trim(parts.shift());
var value:String = StringTools.trim(parts.join(":"));
if (Reflect.field(result, currentSection) == null) Reflect.setField(result, currentSection, {
});
Reflect.setField(Reflect.field(result, currentSection), key, value);
}
// For CSV-like sections
else if (currentSection != null)
{
var theArray:Array<String> = cast Reflect.field(result, currentSection);
theArray.push(line);
Reflect.setField(result, currentSection, theArray);
}
}
return result;
}
/**
* @param songData The raw parsed JSON data to migrate, as a Dynamic.
* @param difficulty The difficulty name to assign to the migrated chart.
* @return SongMetadata
*/
public static function migrateMetadata(songData:OsuManiaData, difficulty:String = 'normal'):SongMetadata
{
trace('Migrating song metadata from Osu!Mania.');
var songMetadata:SongMetadata = new SongMetadata('Import', songData.Metadata.ArtistUnicode ?? songData.Metadata.Artist ?? Constants.DEFAULT_ARTIST,
songData.Metadata.Creator ?? Constants.DEFAULT_CHARTER, Constants.DEFAULT_VARIATION);
// Set generatedBy string for debugging.
songMetadata.generatedBy = 'Chart Editor Import (Osu!Mania)';
songMetadata.playData.stage = 'mainStage';
songMetadata.songName = songData.Metadata.TitleUnicode ?? songData.Metadata.Title ?? 'Import';
songMetadata.playData.difficulties = [difficulty];
songMetadata.playData.songVariations = [];
songMetadata.timeChanges = rebuildTimeChanges(songData);
songMetadata.playData.characters = new SongCharacterData('bf', 'gf', 'dad');
songMetadata.playData.ratings.set(difficulty, songData.Difficulty.OverallDifficulty ?? 0);
return songMetadata;
}
static function rebuildTimeChanges(songData:OsuManiaData):Array<SongTimeChange>
{
var timings:Array<TimingPoint> = parseTimingPoints(cast songData.TimingPoints);
var bpmPoints:Array<TimingPoint> = timings.filter((tp) -> tp.uninherited == 1);
var result:Array<SongTimeChange> = [];
if (bpmPoints.length >= 1)
{
result.push(new SongTimeChange(0, bpmPoints[0].bpm ?? Constants.DEFAULT_BPM));
for (i in 1...bpmPoints.length)
{
var bpmPoint:TimingPoint = bpmPoints[i];
result.push(new SongTimeChange(bpmPoint.time, bpmPoint.bpm ?? Constants.DEFAULT_BPM));
}
}
if (result.length == 0)
{
result.push(new SongTimeChange(0, Constants.DEFAULT_BPM));
trace("[WARN] No BPM points found, resulting to default BPM...");
}
return result;
}
/**
* @param songData The raw parsed JSON data to migrate, as a Dynamic.
* @param difficulty The difficulty name to assign to the migrated chart.
* @return SongChartData
*/
public static function migrateChartData(songData:OsuManiaData, difficulty:String = 'normal'):SongChartData
{
trace('Migrating song chart data from Osu!Mania.');
// Osu!Mania doesn't have a scroll speed variable as its controlled by the player
var songChartData:SongChartData = new SongChartData([difficulty => Constants.DEFAULT_SCROLLSPEED], [], [difficulty => []]);
// songData.HitObjects is a Array<String> here so im casting it so haxe stops yelling at me
var osuNotes:Array<ManiaHitObject> = parseManiaHitObjects(cast songData.HitObjects, songData.Difficulty.CircleSize);
songChartData.notes.set(difficulty, convertNotes(osuNotes, songData.Difficulty.CircleSize));
songChartData.events = [];
return songChartData;
}
static final STRUMLINE_SIZE = 4;
static function convertNotes(hitObjects:Array<ManiaHitObject>, keyCount:Int):Array<SongNoteData>
{
var result:Array<SongNoteData> = [];
for (hitObject in hitObjects)
{
var wrappedColumn:Int = hitObject.column % (keyCount * 2); // wrap overflow for 9K+
if (keyCount <= 4) // if its 4K or less, flip to BF side
{
wrappedColumn -= 4;
var noteOffset:Int = Std.int(Math.abs(keyCount - STRUMLINE_SIZE));
var flippedNoteData:Int = wrappedColumn + keyCount + noteOffset;
result.push(new SongNoteData(hitObject.time, flippedNoteData, hitObject.holdDuration ?? 0, ''));
}
else
result.push(new SongNoteData(hitObject.time, wrappedColumn, hitObject.holdDuration ?? 0, ''));
}
return result;
}
static function parseTimingPoints(timingLines:Array<String>):Array<TimingPoint>
{
return timingLines.map(function(line:String):TimingPoint
{
var parts = line.split(",");
var time = Std.parseFloat(parts[0]);
var beatLength = Std.parseFloat(parts[1]);
var meter = Std.parseInt(parts[2]);
var sampleSet = Std.parseInt(parts[3]);
var sampleIndex = Std.parseInt(parts[4]);
var volume = Std.parseInt(parts[5]);
var uninherited = Std.parseInt(parts[6]);
var effects = Std.parseInt(parts[7]);
return new TimingPoint(time, beatLength, meter, sampleSet, sampleIndex, volume, uninherited, effects);
});
}
static function parseManiaHitObjects(hitObjectsLines:Array<String>, ?columns:Int = 4):Array<ManiaHitObject>
{
return hitObjectsLines.map(function(line:String):ManiaHitObject
{
var parts = line.split(",");
var x:Int = Std.parseInt(parts[0]);
var time:Int = Std.parseInt(parts[2]);
var type:Int = Std.parseInt(parts[3]);
var hasHold:Bool = (type & 128) == 128;
var noteD:Int = Std.int(x / (512 / columns));
var holdEndTime:Null<Int> = hasHold ? Std.parseInt(parts[5].split(":")[0]) : null;
var holdDuration:Int = (holdEndTime != null) ? (holdEndTime - time) : 0;
return new ManiaHitObject(time, noteD, holdDuration);
});
}
}
@@ -0,0 +1,126 @@
package funkin.data.song.importer;
typedef StepManiaData =
{
var Metadata:
{
var Title:String;
var Artist:String;
var Genre:String;
var Credit:String;
var Banner:String;
var Background:String;
var Offset:Float;
var SampleStart:Float;
};
var TimingPoints:Array<StepTimingPoint>;
var Stops:Array<StepStop>;
var Difficulties:Array<StepDifficulty>;
}
enum StepManiaChartType
{
DanceSingle;
DanceDouble;
Unknown;
}
enum StepManiaNoteType
{
Tap;
Head;
Tail;
Roll;
Mine;
Fake;
}
class StepNote
{
public var beat:Float;
public var column:Int;
public var type:StepManiaNoteType;
public function new(t:String, beat:Float, column:Int)
{
this.beat = beat;
this.column = column;
switch (t)
{
case "2":
this.type = StepManiaNoteType.Head;
case "3":
this.type = StepManiaNoteType.Tail;
case "4":
this.type = StepManiaNoteType.Roll;
case "M":
this.type = StepManiaNoteType.Mine;
case "F":
this.type = StepManiaNoteType.Fake;
default:
this.type = StepManiaNoteType.Tap;
}
}
}
class StepDifficulty
{
public var name:String;
public var charter:String;
public var difficultyRating:Int;
public var type:StepManiaChartType;
public var notes:Array<StepNote>;
public function parseChartType(chartTypeStr:String):StepManiaChartType
{
switch (chartTypeStr)
{
case "dance-single":
return StepManiaChartType.DanceSingle;
case "dance-double":
return StepManiaChartType.DanceDouble;
default:
return StepManiaChartType.Unknown;
}
}
public function new(name:String, charter:String, difficultyRating:Int, type:String)
{
this.name = name;
this.charter = charter;
this.difficultyRating = difficultyRating;
this.type = parseChartType(type);
}
}
class StepTimingPoint
{
public var bpm:Float;
public var startBeat:Float = Math.NEGATIVE_INFINITY;
public var endBeat:Float = Math.POSITIVE_INFINITY;
public var startTimestamp:Float = 0;
public var endTimestamp:Float = 0;
public function new(bpm:Float, startBeat:Float)
{
this.bpm = bpm;
this.startBeat = startBeat;
}
}
// Not implemented, but if any chart uses them then the chart will break.
// IE this messes with the timing of the notes.
class StepStop
{
public var startBeat:Float = Math.NEGATIVE_INFINITY;
public var duration:Float = 0;
public var startTimestamp:Float = Math.NEGATIVE_INFINITY;
public function new(startBeat:Float, duration:Float)
{
this.startBeat = startBeat;
this.duration = duration;
}
}
@@ -0,0 +1,610 @@
package funkin.data.song.importer;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongCharacterData;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.importer.StepManiaData.StepTimingPoint;
import funkin.data.song.importer.StepManiaData.StepDifficulty;
import funkin.data.song.importer.StepManiaData.StepManiaChartType;
import funkin.data.song.importer.StepManiaData.StepNote;
import funkin.data.song.importer.StepManiaData.StepStop;
import funkin.data.song.importer.StepManiaData.StepManiaNoteType;
enum StepStateEnum
{
Metadata;
TimingPoints;
Stops;
Notes;
}
class StepManiaImporter
{
private static var readDiffMetadata:Map<String, String> = new Map<String, String>();
static function parseMetadataLine(line:String, result:StepManiaData):StepManiaData
{
var parts:Array<String> = line.split(":");
if (parts.length != 2) return result;
var key:String = StringTools.trim(parts[0]);
var value:String = StringTools.trim(parts[1]);
// remove trailing ; on value
if (StringTools.endsWith(value, ";")) value = value.substr(0, value.length - 1);
switch (key)
{
case "TITLE":
result.Metadata.Title = value;
case "ARTIST":
result.Metadata.Artist = value;
case "GENRE":
result.Metadata.Genre = value;
case "CREDIT":
if (result.Metadata.Credit == "") result.Metadata.Credit = value;
else // .ssc
readDiffMetadata.set("CREDIT", value);
case "BANNER":
result.Metadata.Banner = value;
case "BACKGROUND":
result.Metadata.Background = value;
case "OFFSET":
result.Metadata.Offset = Std.parseFloat(value);
case "SAMPLESTART":
result.Metadata.SampleStart = Std.parseFloat(value);
// .ssc
case "STEPSTYPE":
readDiffMetadata.set("STEPSTYPE", value);
case "DESCRIPTION":
readDiffMetadata.set("DESCRIPTION", value);
case "DIFFICULTY":
readDiffMetadata.set("DIFFICULTY", value);
}
return result;
}
static function parseTimingPointLine(line:String):Array<StepTimingPoint>
{
// Remove #BPMS: prefix if present
if (StringTools.startsWith(line, "#BPMS:")) line = line.substr(6);
var parts:Array<String> = line.split(",");
var stepTimingPoints:Array<StepTimingPoint> = [];
for (i in 0...parts.length)
{
// Split the =
var split = parts[i].split("=");
if (split.length != 2) continue;
var beat:Float = Std.parseFloat(StringTools.trim(split[0]));
// Check if split[i + 1] has ; and remove it
var bpmSplit = split[1];
if (StringTools.endsWith(bpmSplit, ";")) bpmSplit = bpmSplit.substr(0, bpmSplit.length - 1);
var tp:StepTimingPoint = new StepTimingPoint(Std.parseFloat(bpmSplit), beat);
stepTimingPoints.push(tp);
}
return stepTimingPoints;
}
static function parseStopsLine(line:String):Array<StepStop>
{
// Remove #STOPS: prefix if present
if (StringTools.startsWith(line, "#STOPS:")) line = line.substr(7);
// Same as timing points
var parts:Array<String> = line.split(",");
var stepStopPoints:Array<StepStop> = [];
for (i in 0...parts.length)
{
// Split the =
var split = parts[i].split("=");
if (split.length != 2) continue;
var beat:Float = Std.parseFloat(StringTools.trim(split[0]));
// Check if split[i + 1] has ; and remove it
var durSplit = split[1];
if (StringTools.endsWith(durSplit, ";")) durSplit = durSplit.substr(0, durSplit.length - 1);
var tp:StepStop = new StepStop(beat, Std.parseFloat(durSplit));
stepStopPoints.push(tp);
}
return stepStopPoints;
}
static function parseMeasure(lines:Array<String>, measureIndex:Int):Array<StepNote>
{
var lengthInRows:Float = 192.0 / lines.length;
var rowIndex:Int = 0;
var beat:Float = 0;
var stepNotes:Array<StepNote> = [];
for (i in 0...lines.length)
{
var stepNoteRow:Float = measureIndex * 192 + (lengthInRows * rowIndex);
beat = stepNoteRow / 48.0; // 48 rows per beat
for (j in 0...lines[i].length)
{
var char:String = lines[i].charAt(j);
if (char != "0")
{
var stepNote:StepNote = new StepNote(char, beat, j);
stepNotes.push(stepNote);
}
}
rowIndex++;
}
return stepNotes;
}
static function synchronizeStepTimingPoints(stepStopPoints:Array<StepStop>, stepTimingPoints:Array<StepTimingPoint>):Array<StepTimingPoint>
{
// Initialize startTimestamp/endTimestamp and endBeat for timing points
for (tpIndex in 0...stepTimingPoints.length)
{
var tp = stepTimingPoints[tpIndex];
// ensure fields exist
if (tp.startTimestamp == Math.NEGATIVE_INFINITY) tp.startTimestamp = 0.0;
}
var tpIndex:Int = 1;
var spIndex:Int = 0;
// if we have more than one timing point or any StepStops, synchronize
if (stepTimingPoints.length > 1 || stepStopPoints.length > 0)
{
if (stepStopPoints.length > 1)
{
while (tpIndex < stepTimingPoints.length || spIndex < stepStopPoints.length)
{
var prevTp:StepTimingPoint = stepTimingPoints[tpIndex - 1];
if (tpIndex == stepTimingPoints.length)
{
var cts:Float = 0;
while (spIndex < stepStopPoints.length)
{
var sp = stepStopPoints[spIndex];
sp.startTimestamp = (prevTp.startTimestamp + (sp.startBeat - prevTp.startBeat) / (prevTp.bpm / 60)) + cts;
cts += sp.duration;
spIndex++;
}
continue;
}
if (spIndex == stepStopPoints.length)
{
while (tpIndex < stepTimingPoints.length)
{
var tp = stepTimingPoints[tpIndex];
prevTp = stepTimingPoints[tpIndex - 1];
prevTp.endBeat = tp.startBeat;
prevTp.endTimestamp += (prevTp.endBeat - prevTp.startBeat) / (prevTp.bpm / 60);
tp.startTimestamp = prevTp.endTimestamp;
tp.endTimestamp = tp.startTimestamp;
tpIndex++;
if (tpIndex == stepTimingPoints.length)
{
tp.endTimestamp = Math.POSITIVE_INFINITY;
tp.endBeat = Math.POSITIVE_INFINITY;
}
}
continue;
}
var tp:StepTimingPoint = stepTimingPoints[tpIndex];
var sp:StepStop = stepStopPoints[spIndex];
prevTp.endBeat = tp.startBeat;
if (sp.startBeat < prevTp.endBeat)
{
sp.startTimestamp = prevTp.endTimestamp + (sp.startBeat - prevTp.startBeat) / (prevTp.bpm / 60);
prevTp.endTimestamp += sp.duration;
spIndex++;
}
else
{
prevTp.endTimestamp += (prevTp.endBeat - prevTp.startBeat) / (prevTp.bpm / 60);
tp.startTimestamp = prevTp.endTimestamp;
tp.endTimestamp = tp.startTimestamp;
tpIndex++;
}
}
}
else
{
while (tpIndex < stepTimingPoints.length)
{
var prevTp:StepTimingPoint = stepTimingPoints[tpIndex - 1];
var tp:StepTimingPoint = stepTimingPoints[tpIndex];
prevTp.endBeat = tp.startBeat;
prevTp.endTimestamp = prevTp.startTimestamp + (prevTp.endBeat - prevTp.startBeat) / (prevTp.bpm / 60);
tp.startTimestamp = prevTp.endTimestamp;
tpIndex++;
if (tpIndex == stepTimingPoints.length)
{
tp.endTimestamp = Math.POSITIVE_INFINITY;
tp.endBeat = Math.POSITIVE_INFINITY;
}
}
}
}
return stepTimingPoints;
}
static function pushWorking(workingDiff:StepDifficulty, result:StepManiaData):StepManiaData
{
if (workingDiff != null && workingDiff.notes.length > 0)
{
// Apply .ssc metadata if any
for (metaKey in readDiffMetadata.keys())
{
var metaValue = readDiffMetadata.get(metaKey);
switch (metaKey)
{
case "STEPSTYPE":
workingDiff.type = workingDiff.parseChartType(metaValue);
case "DIFFICULTY":
workingDiff.name = metaValue;
case "CREDIT":
workingDiff.charter = metaValue;
// DESCRIPTION is ignored for now
}
}
result.Difficulties.push(workingDiff);
}
readDiffMetadata.clear();
return result;
}
static function parseBPMS(line:String, result:StepManiaData):StepManiaData
{
var tps = parseTimingPointLine(line);
if (tps != null)
{
for (tp in tps)
result.TimingPoints.push(tp);
}
return result;
}
static function parseStops(line:String, result:StepManiaData):StepManiaData
{
var sps = parseStopsLine(line);
if (sps != null)
{
for (sp in sps)
result.Stops.push(sp);
}
return result;
}
/**
* Parses a StepMania file content into StepManiaData structure.
* @param stepContent The content of the StepMania file as a string.
* @return StepManiaData The parsed StepMania data.
*/
public static function parseStepManiaFile(stepContent:String):StepManiaData
{
readDiffMetadata = new Map<String, String>();
var lines:Array<String> = stepContent.split("\n");
var currentMeasure:Int = 0;
var measure:Array<String> = [];
var workingDiff:StepDifficulty = null;
var state:StepStateEnum = StepStateEnum.Metadata;
var headerLines:Int = 0;
var result:StepManiaData = {
Metadata: {
Title: "",
Artist: "",
Genre: "",
Credit: "",
Banner: "",
Background: "",
Offset: 0,
SampleStart: 0
},
TimingPoints: [],
Stops: [],
Difficulties: []
};
// Parsing metadata
for (line in lines)
{
line = StringTools.trim(line);
if (line == "") continue;
if (StringTools.startsWith(line, "//")) continue; // Comment line
switch (state)
{
case StepStateEnum.Metadata:
if (StringTools.startsWith(line, "#BPMS:"))
{
state = StepStateEnum.TimingPoints;
result = parseBPMS(line, result);
if (StringTools.endsWith(line, ";")) state = StepStateEnum.Metadata;
}
else if (StringTools.startsWith(line, "#STOPS:"))
{
state = StepStateEnum.Stops;
result = parseStops(line, result);
if (StringTools.endsWith(line, ";")) state = StepStateEnum.Metadata;
}
else if (StringTools.startsWith(line, "#NOTES:"))
{
if (workingDiff != null)
{
// Save previous difficulty
result = pushWorking(workingDiff, result);
}
// Start new difficulty
workingDiff = new StepDifficulty("", "", 0, "");
workingDiff.notes = [];
headerLines = 0;
currentMeasure = 0;
measure = [];
state = StepStateEnum.Notes;
}
else if (StringTools.startsWith(line, "#")) result = parseMetadataLine(line.substr(1), result);
case StepStateEnum.TimingPoints:
if (line == ";") state = StepStateEnum.Metadata;
else
{
result = parseBPMS(line, result);
if (StringTools.endsWith(line, ";")) state = StepStateEnum.Metadata;
}
case StepStateEnum.Stops:
if (line == ";") state = StepStateEnum.Metadata;
else
{
result = parseStops(line, result);
if (StringTools.endsWith(line, ";")) state = StepStateEnum.Metadata;
}
case StepStateEnum.Notes:
if (line == "#NOTES:") continue;
// remove comments in line (if any) ie, "0000 // some comment"
var commentIndex = line.indexOf("//");
if (commentIndex != -1) line = StringTools.trim(line.substr(0, commentIndex));
if (StringTools.contains(line, ":")) // header
{
switch (headerLines)
{
case 0:
// First line is chart type
var chartTypeStr = StringTools.trim(line).replace(":", "");
workingDiff.type = workingDiff.parseChartType(chartTypeStr);
case 1:
// Second line is charter
workingDiff.charter = StringTools.trim(line).replace(":", "");
case 2:
// Third line is difficulty name
workingDiff.name = StringTools.trim(line).replace(":", "");
case 3:
// Fourth line is difficulty rating
workingDiff.difficultyRating = Std.parseInt(StringTools.trim(line).replace(":", ""));
// we dont care about the rest lol
}
headerLines++;
continue;
}
// we're reading StepNote data now
// start gathering measures until we hit a ,
if (line == "," || line == ";")
{
// end of measure
var stepNotesInMeasure = parseMeasure(measure, currentMeasure);
for (stepNote in stepNotesInMeasure)
workingDiff.notes.push(stepNote);
measure = [];
currentMeasure++;
// end of StepNotes section
if (line == ";") state = StepStateEnum.Metadata;
}
else
{
measure.push(line);
}
}
}
if (workingDiff != null && workingDiff.notes.length > 0)
{
// Save last difficulty
result = pushWorking(workingDiff, result);
}
// Syncronize timing points with StepStops
result.TimingPoints = synchronizeStepTimingPoints(result.Stops, result.TimingPoints);
return result;
}
static function getStepStopAtBeat(beat:Float, stepStops:Array<StepStop>):StepStop
{
var stepStop:StepStop = null;
for (s in stepStops)
{
if (s.startBeat < beat) stepStop = s;
}
return stepStop;
}
static function beatToTime(beat:Float, offset:Float, stepTimingPoints:Array<StepTimingPoint>, stepStops:Array<StepStop>):Float
{
var time:Float = 0;
for (tp in stepTimingPoints)
{
if (tp.startBeat <= beat && tp.endBeat > beat)
{
var stepStop:StepStop = getStepStopAtBeat(beat, stepStops);
var b:Float = tp.startBeat;
var startTime:Float = tp.startTimestamp;
if (stepStop != null)
{
if (stepStop.startBeat > tp.startBeat)
{
b = stepStop.startBeat;
startTime = stepStop.startTimestamp + stepStop.duration;
}
}
var nb:Float = (beat - b) / (tp.bpm / 60);
time = startTime + nb;
}
}
return (time * 1000) - (offset * 1000); // convert to ms
}
static function convertStepNotes(offset:Float, type:StepManiaChartType, stepNotes:Array<StepNote>, stepTimingPoints:Array<StepTimingPoint>,
stepStops:Array<StepStop>):Array<SongNoteData>
{
var result:Array<SongNoteData> = [];
var holdArray:Array<Float> = [];
if (type == StepManiaChartType.DanceSingle)
{
holdArray = [-1, -1, -1, -1];
}
else if (type == StepManiaChartType.DanceDouble)
{
holdArray = [-1, -1, -1, -1, -1, -1, -1, -1];
}
else
{
trace("[WARN] Unknown StepMania chart type when converting notes.");
return result;
}
for (stepNote in stepNotes)
{
var time = beatToTime(stepNote.beat, offset, stepTimingPoints, stepStops);
if (stepNote.type == StepManiaNoteType.Head || stepNote.type == StepManiaNoteType.Roll)
{
holdArray[stepNote.column] = time;
continue;
}
var snd:SongNoteData = new SongNoteData(time, stepNote.column, 0);
if (stepNote.type == StepManiaNoteType.Mine) snd.kind = "mine";
else if (stepNote.type == StepManiaNoteType.Fake) snd.kind = "fake";
if (stepNote.type == StepManiaNoteType.Tail)
{
var length:Float = 0;
if (holdArray[stepNote.column] != -1) length = time - holdArray[stepNote.column];
snd.time = holdArray[stepNote.column];
snd.length = length;
holdArray[stepNote.column] = -1;
}
if (type != StepManiaChartType.DanceSingle)
{
if (stepNote.column < 4) snd.data = stepNote.column + 4;
else
snd.data = stepNote.column - 4;
}
else
{
if (snd.data >= 4) snd.data -= 4;
}
result.push(snd);
}
return result;
}
/**
* Migrates StepManiaData to SongMetadata.
* @param songData The StepManiaData to migrate.
* @return SongMetadata The migrated SongMetadata.
*/
public static function migrateChartMetadata(songData:StepManiaData):SongMetadata
{
var metadata:SongMetadata = new SongMetadata(songData.Metadata.Title, songData.Metadata.Artist);
metadata.playData.stage = 'mainStage';
metadata.playData.characters = new SongCharacterData('bf', 'gf', 'dad');
metadata.generatedBy = 'Chart Editor Import (StepMania)';
metadata.playData.songVariations = [];
// Difficulties
var difficulties:Array<String> = [];
for (diff in songData.Difficulties)
{
if (diff.type == StepManiaChartType.Unknown)
{
trace("[WARN] Skipping unknown StepMania chart type. Name: " + diff.name);
continue; // skip unknown chart types
}
difficulties.push(diff.name);
metadata.playData.ratings.set(diff.name, diff.difficultyRating);
}
metadata.playData.difficulties = difficulties;
metadata.charter = songData.Metadata.Credit != "" ? songData.Metadata.Credit : null;
// TimeChanges
metadata.timeChanges = [];
for (tp in songData.TimingPoints)
{
var timeChange = new SongTimeChange(tp.startTimestamp * 1000, tp.bpm);
timeChange.beatTime = tp.startBeat;
metadata.timeChanges.push(timeChange);
}
return metadata;
}
/**
* Migrates StepManiaData to SongChartData.
* @param songData The StepManiaData to migrate.
* @return SongChartData The migrated SongChartData.
*/
public static function migrateChartData(songData:StepManiaData):SongChartData
{
var scrollsMap:Map<String, Float> = new Map<String, Float>();
var stepNoteMap:Map<String, Array<SongNoteData>> = new Map<String, Array<SongNoteData>>();
for (diff in songData.Difficulties)
{
if (diff.type == StepManiaChartType.Unknown)
{
trace("[WARN] Skipping unknown StepMania chart type. Name: " + diff.name);
continue; // skip unknown chart types
}
scrollsMap.set(diff.name, Constants.DEFAULT_SCROLLSPEED);
stepNoteMap.set(diff.name, convertStepNotes(songData.Metadata.Offset, diff.type, diff.notes, songData.TimingPoints, songData.Stops));
}
var songChartData:SongChartData = new SongChartData(scrollsMap, [], stepNoteMap);
return songChartData;
}
}
@@ -49,7 +49,9 @@ class SongMetadata_v2_0_0
public var timeChanges:Array<SongData.SongTimeChange>;
public function new() {}
public function new()
{
}
/**
* Produces a string representation suitable for debugging.
@@ -88,7 +90,9 @@ class SongPlayData_v2_0_0
public var stage:String;
public function new() {}
public function new()
{
}
/**
* Produces a string representation suitable for debugging.
@@ -100,7 +100,9 @@ class SongPlayData_v2_1_0
public var characters:SongData.SongCharacterData;
public var stage:String;
public function new() {}
public function new()
{
}
/**
* Produces a string representation suitable for debugging.

Some files were not shown because too many files have changed in this diff Show More