Compare commits

...
168 Commits
Author SHA1 Message Date
EricandGitHub df0f915be5 Merge pull request #1014 from FunkinCrew/kade-github/pico-mix-bugfix
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
FUNK-1944 Bugfix for Pico (Pico Mix's) that would cause the doppelganger cutscene bad shit
2025-04-25 22:29:54 -04:00
EliteMasterEric 104d3cffcb Merge branch 'rewrite/master' into kade-github/pico-mix-bugfix 2025-04-25 22:29:32 -04:00
EliteMasterEric 584b1ece07 Update assets submodule 2025-04-25 22:27:57 -04:00
EliteMasterEricandKade adefa902e7 Fix several issues preventing builds on HTML5 2025-04-25 12:14:28 -07:00
kade-github 63f216ea71 assets submodule update 2025-04-25 12:06:48 -07:00
EliteMasterEric 03d46084b4 Update game version to v0.6.3. 2025-04-25 02:32:28 -04:00
kade-githubandCameron Taylor 19eb138666 vwooshin for retry 2025-04-25 00:32:07 -04:00
EliteMasterEricandCameron Taylor e21b01aa66 Fix a broken usage. 2025-04-25 00:32:00 -04:00
EliteMasterEricandCameron Taylor c41c800998 Fix a bug where render distance ignores scroll speed (test on 0.5x scroll speed), then do some code cleanup and documentation. 2025-04-25 00:32:00 -04:00
Cameron Taylor b7675aa440 assets submod (Cheer overrides lightning scare from kade) 2025-04-25 00:20:46 -04:00
Cameron TaylorandKade 745f49ca6d tiny little cleans in char select nametag 2025-04-24 19:26:35 -07:00
Cameron TaylorandKade 78646c4635 fix for difficulty stars not appearing when returning from character select 2025-04-24 19:14:11 -07:00
Cameron Taylor f1cc092820 quickie haxe checkstyle thing that annoy me all the time grr.. 2025-04-24 21:29:18 -04:00
Cameron Taylor 7e200241ef assets submod (satin panties fixins) 2025-04-24 20:54:57 -04:00
EricandGitHub 1c4b8751b7 Pico Results Screen Easter Egg (#1007) 2025-04-23 16:52:29 -07:00
EliteMasterEricandCameron Taylor e290e0c702 Make merch referrals error-proof (they don't work if the API is disabled whoops!) 2025-04-22 19:14:15 -04:00
nebulazoruaandCameron Taylor 53dec057be vuln patch part 1
so now you cant open files as easily i think

oops
2025-04-22 19:14:15 -04:00
Cameron Taylor b3f1798320 assets submod (tankman deathlines for bf) 2025-04-22 19:02:58 -04:00
Hyper_andCameron Taylor f20b938f3f fix: Pad number with 1 decimal place with zero
Merge pull request #1000 from FunkinCrew/hyper/chart-editor-display-beat

[PUBLIC PR] Display current beat and step in the Chart Editor
2025-04-22 19:02:56 -04:00
Hyper_andCameron Taylor 85915eae8c Implement suggestion: Fractional beat display 2025-04-22 19:02:09 -04:00
Hyper_andCameron Taylor 83bb3bb507 feat: Show current beat and step next to song position 2025-04-22 19:02:09 -04:00
KadeandCameron Taylor 642b2142ca Merge pull request #999 from FunkinCrew/kolo/chart-event-crash-fix
[PUBLIC PR] Fix a crash when messing with events in the chart editor
2025-04-22 19:00:24 -04:00
KadeandCameron Taylor 0c6b730647 Merge pull request #998 from FunkinCrew/kolo/cap-score-display
[PUBLIC PR] Cap Score Displays to their biggest possible values
2025-04-22 19:00:24 -04:00
KoloandCameron Taylor 83c367e23b another bugfix yeah
i hate dropdowns i hate dropdowns i hate dropdowns
2025-04-22 19:00:24 -04:00
KadeandCameron Taylor fe7bf3dd55 Merge pull request #994 from FunkinCrew/cyn0x8/fileutil-sandbox
[PUBLIC PR] Sandboxing for FileUtil (prevent deleting important game files etc)
2025-04-22 19:00:24 -04:00
KadeandCameron Taylor 273d950899 Merge branch 'rewrite/master' into kolo/cap-score-display 2025-04-22 19:00:24 -04:00
KoloandCameron Taylor 9e61481cfe Merge branch 'develop' of https://github.com/FunkinCrew/Funkin into chart-events-fix 2025-04-22 19:00:24 -04:00
SuperandCameron Taylor e572c7c2fb Remove FlxG.fixedTimeStep from PlayState
It's in TitleState now
2025-04-22 19:00:24 -04:00
cyn0x8andCameron Taylor 95ade2a08b FileUtil overhaul + sandboxing 2025-04-22 19:00:24 -04:00
KoloandCameron Taylor 51324e9c28 mmyes 2025-04-22 19:00:24 -04:00
AbnormalandCameron Taylor e6e3a30c0c docs: Add missing documentation to the remaining functions in Module 2025-04-22 19:00:24 -04:00
SuperandCameron Taylor 2b7254fbd2 Add FlxG.fixedTimestep change to initState 2025-04-22 19:00:24 -04:00
HundrecandCameron Taylor d54df57b95 Two more Chart Editor entries in Changelog 2025-04-22 19:00:24 -04:00
Cameron Taylor b295e31266 Merge branch 'rewrite/master' into public-changes 2025-04-22 19:00:24 -04:00
HundrecandCameron Taylor 695c708cb0 Add a few fixes and changes from 0.6.0 2025-04-22 19:00:24 -04:00
EliteMasterEricandCameron Taylor 4a7f56a7ad Merge remote-tracking branch 'public/develop' into develop 2025-04-22 19:00:24 -04:00
HundrecandCameron Taylor 030abed207 Oops, one more lil space 2025-04-22 19:00:24 -04:00
HundrecandCameron Taylor 0d8cfaf104 Remove 0.6.0 Changelog entries that weren't implemented + polish 2025-04-22 19:00:24 -04:00
EliteMasterEricandCameron Taylor caf708fe4a Merge remote-tracking branch 'public/main' into develop 2025-04-22 18:41:41 -04:00
HundrecandCameron Taylor e7cf5fbbdc Grammatical fix in style-guide.md 2025-04-22 18:41:41 -04:00
HundrecandCameron Taylor ccaa3d1070 Consistent hyphens in CODESTYLE.md 2025-04-22 18:41:41 -04:00
4b883d4339 Restore Contributing Guide to latest version
Merge conflicts yay

Co-Authored-By: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
2025-04-22 18:41:41 -04:00
sector-aandEric b40b4b0369 Fix Animation Editor Crash
Fix poor code formatting

This is unnecessary now
2025-04-22 15:53:53 -04:00
Cameron TaylorandEric aa37745bbd assets submod (week 6 shader fixin) 2025-04-22 15:51:45 -04:00
EliteMasterEricandCameron Taylor df6ffca01f Update assets submodule (charting revert stuff) 2025-04-19 19:41:27 -04:00
EliteMasterEricandCameron Taylor 9dd8a257df Update assets submodule to include twenty-seven chart tweak commits 2025-04-19 19:38:18 -04:00
mint 2984ce4852 hotfix(ci): use our own runners again :) 2025-04-19 03:23:27 +02:00
mint 8fb35ac822 hotfix(ci): for now, disable fail-fast so we can get some windows builds 2025-04-19 03:17:47 +02:00
mint e8c42c281f hotfix: mac lime ndlls again? 2025-04-19 02:58:52 +02:00
mint 68892b92ab hotfix(ci): bump neko version to 2.4.1 2025-04-19 02:32:22 +02:00
Cameron Taylor 12ee60f776 Mac ndlls lime hmm bump 2025-04-18 20:18:39 -04:00
Cameron Taylor 945314d271 lime bump for windows fix 2025-04-18 19:41:53 -04:00
EliteMasterEricandCameron Taylor 93d4f30cad Make ScreenshotPlugin null-safe. 2025-04-18 19:40:39 -04:00
AbnormalandCameron Taylor dda91cf770 change requested by eric 2025-04-18 19:35:24 -04:00
AbnormalandCameron Taylor 6a1160feab chore: Enable null safety for 11 classes 2025-04-18 19:35:24 -04:00
kade-githubandEric b48b5ae1aa remember diff for generate song list 2025-04-18 17:33:31 -04:00
fabsandCameron Taylor 5bf8154e0b otis muzzle flashing (assets submod) 2025-04-17 19:59:59 -04:00
EliteMasterEricandCameron Taylor 6de51848fd Update assets submodule 2025-04-17 19:59:59 -04:00
Cameron Taylor ee906ae8c0 fix: make the sound tray volume be less loud (#990) 2025-04-17 19:59:59 -04:00
7eebce432d [PUBLIC PR] Prevent softlock caused by removing all keybinds for a UI control (#988)
* prevent removing last keybind for UI controls

* add popup to alert you cannot remove keybind

* ensure controls list is unique

* Use public submodule URLs (would be nice if this was automatic but oh well)

---------

Co-authored-by: VioletSnowLeopard <ngzeretzke@gmail.com>
Co-authored-by: EliteMasterEric <ericmyllyoja@gmail.com>
2025-04-17 19:59:59 -04:00
EliteMasterEric 750779a3fb Disable the pop-in animation on the Random capsule for consistency. 2025-04-17 18:25:44 -04:00
kade-githubandEric 095bc9006d animation on all diffs 2025-04-17 18:25:44 -04:00
kade-githubandEric 09fdd0c91e going to erect with the erect takes you to the closest erect 2025-04-17 18:25:44 -04:00
kade-githubandEric be5d6227a7 erect nearest neighbor select 2025-04-17 18:25:44 -04:00
KoloandCameron Taylor 855deb5828 set enabled when prompt gone 2025-04-16 16:06:39 -04:00
EliteMasterEricandCameron Taylor 963e2ecd63 Edit RegistryMacro to automatically populate baseGameEntryIds list. 2025-04-15 20:25:18 -04:00
lemz1andCameron Taylor 14975212a9 macro
registry macro

Merge branch 'lemz1/registry-entry-data' into lemz1/registry-macro-stufff
2025-04-15 20:25:18 -04:00
Hyper_andCameron Taylor 3ad14bac32 fix: Double check if events are the same when doing indexOf
This is basically copy-pasted from SongNoteDataArrayTools lol
2025-04-15 19:47:39 -04:00
Cameron Taylor e672c0978f hmm lime bump 2025-04-14 23:35:43 -04:00
Cameron Taylor 0d5b7d4ed4 also conditional for WindowUtils.setVSyncMode() for mac 2025-04-14 23:27:34 -04:00
kade-githubandCameron Taylor 7e90423495 Pre-Processor to disable VSync on MacOS 2025-04-14 23:27:34 -04:00
kade-githubandCameron Taylor c7c2620217 Set vsync on launch (might need to be set elsewhere too) 2025-04-14 21:42:24 -04:00
kade-githubandCameron Taylor 3944816021 Error handling from wiki 2025-04-14 21:42:24 -04:00
kade-githubandCameron Taylor db00feff93 Vsync does work now 2025-04-14 21:42:24 -04:00
kade-githubandCameron Taylor 6e2a077d6b Fixes from merge 2025-04-14 21:42:22 -04:00
EliteMasterEricandCameron Taylor 1ee8246b48 Added a preference to enable VSync. 2025-04-14 21:40:58 -04:00
EricandCameron Taylor 20c3228d8c Don't discount the score so much on miss, but still keep it meaningful. 2025-04-14 19:32:55 -04:00
EliteMasterEricandCameron Taylor 01bf3bee11 Dropping hold notes should no longer cost health by default. 2025-04-14 19:32:55 -04:00
EliteMasterEricandCameron Taylor 4d9638f0ac Fix the clamping. 2025-04-14 19:32:55 -04:00
EliteMasterEricandCameron Taylor b00589367c Make hold drop penalty threshold match the hit window. 2025-04-14 19:32:55 -04:00
EliteMasterEricandCameron Taylor 577421a782 Scoring rework: Penalize the player (deducting health and score) for dropping a hold note before it's finished. 2025-04-14 19:32:54 -04:00
EliteMasterEricandCameron Taylor 72e9fb641e Scoring rework: Reduce health given by hold notes by 20%. 2025-04-14 19:32:16 -04:00
EliteMasterEricandCameron Taylor 429f6f9bc0 Remove the unused input splitter. 2025-04-14 19:32:16 -04:00
EliteMasterEricandCameron Taylor 550bf95c76 Split miss penalty and ghost miss penalty into two variables (but make them equal for now). 2025-04-14 19:32:16 -04:00
EliteMasterEricandCameron Taylor da686ed0c4 Scoring rebalance: Misses now reduce your final grade, which impacts your rank 2025-04-14 19:32:16 -04:00
EliteMasterEricandCameron Taylor 9f3711bf43 Scoring rebalance: Misses are now worth -250 points (up from -10). 2025-04-14 19:32:16 -04:00
EliteMasterEricandCameron Taylor 6390725ef0 Redo data format for sticker packs to make it simpler and allow for song-specific selections. 2025-04-14 19:25:42 -04:00
AbnormalandCameron Taylor cdc468ba15 feat: Sticker Registry 2025-04-14 19:25:29 -04:00
EliteMasterEricandCameron Taylor deb4e1921d Fix an issue where Stress would crash on the second playthrough 2025-04-14 15:45:29 -04:00
KoloandCameron Taylor 068c9fb43d no thoughts save empty
also more code clarity ig
2025-04-14 15:28:14 -04:00
AbnormalandCameron Taylor 5be4f3bbb9 chore: Remove ComboMilestone 2025-04-14 15:28:14 -04:00
AbnormalandCameron Taylor b83ae65a65 docs: Add missing documentation to the remaining functions in Module 2025-04-14 15:28:14 -04:00
HundrecandCameron Taylor dc876be57c Two more Chart Editor entries in Changelog 2025-04-14 15:28:14 -04:00
HundrecandCameron Taylor c08847f48d Add a few fixes and changes from 0.6.0 2025-04-14 15:28:14 -04:00
HundrecandCameron Taylor 19912b5f89 Oops, one more lil space 2025-04-14 15:28:14 -04:00
HundrecandCameron Taylor 019473dec2 Remove 0.6.0 Changelog entries that weren't implemented + polish 2025-04-14 15:28:14 -04:00
EliteMasterEricandCameron Taylor f001562270 Update HXVLC to 2.1.2 2025-04-13 21:45:39 -04:00
EliteMasterEricandCameron Taylor b28ba8f465 Update assets submodule 2025-04-11 16:07:53 -04:00
EliteMasterEricandCameron Taylor dcc8085691 Update assets submodule 2025-04-11 16:00:16 -04:00
EricandCameron Taylor c186666402 Update mod version number (this requires mods to check they are working, then change their metadata) 2025-04-11 15:58:51 -04:00
EliteMasterEricandCameron Taylor 6f93960cb5 Update HaxeUI to their latest versions. 2025-04-11 15:58:34 -04:00
Cameron TaylorandEric a756ceac22 properly initialize soundtray on game start if muted 2025-04-10 18:43:54 -04:00
Cameron TaylorandEric c394520019 small cleanups / tidying of FunkinSoundTray.hx 2025-04-10 18:43:54 -04:00
Cameron TaylorandEric 2d93baf968 properly save mute preferences by doing the save on show() in the soundtray 2025-04-10 18:43:54 -04:00
EliteMasterEricandCameron Taylor 4a98e6999f Update assets submodule 2025-04-09 18:13:30 -04:00
EliteMasterEricandCameron Taylor 9ff14aed42 Fix an issue where Pico's retry animation is broken on Ugh/Guns Pico Mix 2025-04-09 15:58:44 -04:00
EliteMasterEricandCameron Taylor 0a7bd3111f Fix a crash with the chart editor Note window (via Lasercar) 2025-04-09 15:52:47 -04:00
VioletSnowLeopardandCameron Taylor 1f88a3b6e1 swap restart and skip in dialogue pause menu 2025-04-09 15:33:54 -04:00
EricandGitHub 278bac7b6d Merge pull request #4554 from FunkinCrew/release/v0.6.2
Release the source code for v0.6.2
2025-04-04 15:03:23 -04:00
EliteMasterEric 29cddfb528 Merge remote-tracking branch 'public/main' into release/v0.6.2 2025-04-04 15:01:59 -04:00
EliteMasterEricandCameron Taylor 02dc353032 Update assets submodule 2025-04-04 14:41:22 -04:00
EliteMasterEric c7d4406a34 Fix results screen music continuing to play when using the debugger 2025-04-03 22:01:32 -04:00
EliteMasterEricandCameron Taylor 68e9daffe8 Zero out the ABot visualizer before the song starts. 2025-04-03 16:04:19 -04:00
EliteMasterEricandCameron Taylor c157338783 Fix certain characters pixel icons not displaying properly in the chart editor. 2025-04-03 16:04:07 -04:00
EliteMasterEricandCameron Taylor eb9c2c2c99 Fix Pixo's freeplay AFK animation not playing (properly this time). 2025-04-03 16:02:10 -04:00
EliteMasterEricandCameron Taylor afb4a3dd68 Remove some unused/redundant debug hotkeys. 2025-04-03 16:02:10 -04:00
Cameron Taylor 9b5032ee84 quick patch for cory 2025-04-02 21:54:27 -04:00
Cameron Taylor 62621881c0 data -> data.score 2025-04-02 16:18:58 -04:00
Cameron Taylor 52be941b45 Auto Pause -> Pause on Unfocus (was changed before but prob bad merge somewhere reverted it?) 2025-04-02 13:55:54 -04:00
Cameron Taylor c94b3a3b65 2024 -> 2025 0.6.x changelog date fixins (closes FUNK-1740) 2025-04-02 13:41:54 -04:00
EliteMasterEricandCameron Taylor 8291fe0407 Make the back of ABot's stereo display slightly wider. 2025-04-02 13:37:50 -04:00
EliteMasterEricandCameron Taylor f935526baa Update assets submodule 2025-04-02 13:36:22 -04:00
EliteMasterEricandCameron Taylor 102ac4bfc8 Fix a bug where restarting the level would add more Tankmen to the background. 2025-04-02 13:34:23 -04:00
EliteMasterEricandCameron Taylor f8b212a318 Improve null safety of medal and leaderboard classes. 2025-04-02 13:33:32 -04:00
EliteMasterEricandCameron Taylor 3bd79b822e Fix a deprecation warning. 2025-04-02 13:33:32 -04:00
EliteMasterEricandCameron Taylor 17a4be66b9 Fix highscores not submitting to the website unless they are a personal best. 2025-04-02 13:33:06 -04:00
EliteMasterEricandCameron Taylor 54cd52db76 Fix an issue where story mode medals would not be awarded without a personal best. 2025-04-02 13:33:06 -04:00
Cameron Taylor 4fa0334e7b easier script for creating macos universal binaries 2025-04-01 16:41:46 -04:00
EricandCameron Taylor b7761d45b7 Disable FEATURE_RESULTS_DEBUG on release builds by default.
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-latest, 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-03-31 22:39:08 -04:00
HundrecandEric 3c6466bec7 Bump repo versions to 0.6.2 2025-03-31 21:20:18 -04:00
Cameron Taylor 1d5d0ddd52 IGJHSpritin credits fix (assets submod) 2025-03-31 19:55:42 -04:00
Cameron Taylor 0818bd9c93 disable results screen debugger 2025-03-31 19:51:22 -04:00
Cameron Taylor 43f6cf6267 changelog update 0.6.2 2025-03-31 19:48:06 -04:00
Cameron Taylor cfd380b243 creds update (assets submod) 2025-03-31 19:45:09 -04:00
HundrecandEric 3ccd1b04da Bump repo versions to 0.6.1 2025-03-31 18:42:17 -04:00
HundrecandEric 439be3cb26 Update templates and docs to 0.6.0 2025-03-31 13:21:55 -04:00
871709bb7d Some additions to the Contributor's Guide
Co-authored-by: Hundrec <hundrecard@gmail.com>
Co-authored-by: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
2025-03-25 15:18:43 -04:00
HundrecandEric b74df06f48 Reword some debug hotkeys 2025-03-14 23:37:25 -04:00
LasercarandEric 055c4d9b75 Add remaining debug keys 2025-03-14 23:37:25 -04:00
HundrecandEric 4383fcf32c Correctly list all new contributors since Weekend 1
Gives those contributors their credit!
Lists all first-time GitHub contributors, even if their change isn't listed as an entry in the Changelog.
Quadruple-checked for good measure.
2025-03-14 02:17:46 -04:00
HundrecandEric cef0cbf9d4 Remove docs entries in Changelog + add punctuation 2025-03-14 02:17:46 -04:00
HundrecandEric 7d80e6657b A few more rewordings 2025-03-11 19:26:36 -04:00
HundrecandEric bb8d3ae3dc Combine those lines
Thanks Hyper!
2025-03-11 19:26:36 -04:00
HundrecandEric 56033b3288 Changelog overhaul for 0.4.1, 0.4.0, 0.3.3, 0.3.2, 0.3.1, 0.3.0 2025-03-11 19:26:36 -04:00
HundrecandEric 6e44484a1a Changelog overhaul for 0.5.3, 0.5.2, 0.5.1
Plus added line breaks to match Keep A Changelog's format
2025-03-11 19:26:36 -04:00
Hyper_andEric 9708e20dd4 Minor adjustments
Some reformatting and shortening
2025-03-10 12:56:42 -04:00
Hyper_andEric df353a1ee8 docs: Some Linux troubleshooting 2025-03-10 12:56:42 -04:00
HundrecandEric 6c9c3e418a Add more rejected features to Contributing Guide 2025-03-10 12:54:48 -04:00
HundrecandEric bd3acc319c Remove issue reopening action 2025-02-22 23:28:23 -05:00
HundrecandEric 894d0743c9 Table of Contents for CONTRIBUTING.md 2025-02-16 21:36:59 -05:00
MidyGamyandEric 33f943a672 More dump save info + force crash now accurate 2025-02-14 18:10:01 -05:00
MidyGamyandEric f5efc16611 Rewording missing Funkin' Debug Hotkeys.md (thx Hundrec) 2025-02-14 18:10:01 -05:00
MidyGamyandEric 7e820f9497 Added missing debug hotkeys 2025-02-14 18:10:01 -05:00
HundrecandEric 07b6d8f86f adjust link in PR Template 2025-02-13 21:23:18 -05:00
HundrecandEric 8126cca29b PR Template 2025-02-13 21:23:18 -05:00
HundrecandEric ba78ded457 Enhancement Template 2025-02-13 21:23:18 -05:00
HundrecandEric bda5d90bbc Crash Template 2025-02-13 21:23:18 -05:00
HundrecandEric a9d0c1cb88 Compiling Help Template 2025-02-13 21:23:18 -05:00
HundrecandEric f8f533ad37 Charting Template 2025-02-13 21:23:18 -05:00
HundrecandEric 0bc0ab4eec Revert spacing 2025-02-13 21:23:18 -05:00
HundrecandEric d11027fb08 A lil spacing 2025-02-13 21:23:18 -05:00
HundrecandEric 7b9e375fea Fix Contributing link 2025-02-13 21:23:18 -05:00
HundrecandEric dd215687d4 quote 2025-02-13 21:23:18 -05:00
HundrecandEric e3eddc892d Contributing Guide in Minor Bug Template 2025-02-13 21:23:18 -05:00
HundrecandEric 36fd05b1f9 Link CONTRIBUTING.md in README 2025-02-13 21:23:18 -05:00
HundrecandEric c6ef9b17a2 Add more console outputs to Troubleshooting Guide
Includes a line about warnings and deprecated functions, as well as a line about the hxcpp prompt.
2025-02-13 19:04:07 -05:00
943c38767b Contributor's guide
Co-authored-by: Hundrec <hundrecard@gmail.com>
Co-authored-by: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
2025-02-13 19:01:55 -05:00
Kolo 71ed154b81 fix that one annoying ass bug for events 2024-12-05 22:24:49 +01:00
129 changed files with 3477 additions and 2189 deletions
+1 -2
View File
@@ -6,7 +6,6 @@ body:
- type: markdown
attributes:
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
- type: checkboxes
attributes:
label: Issue Checklist
@@ -46,7 +45,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.5.3
placeholder: ex. 0.6.2
validations:
required: true
+1 -1
View File
@@ -46,7 +46,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.5.3
placeholder: ex. 0.6.2
validations:
required: true
+1 -1
View File
@@ -34,7 +34,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.5.3
placeholder: ex. 0.6.2
validations:
required: true
+1 -1
View File
@@ -46,7 +46,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.5.3
placeholder: ex. 0.6.2
validations:
required: true
+1 -1
View File
@@ -30,7 +30,7 @@ runs:
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
- name: Install Haxe
uses: funkincrew/ci-haxe@v3.2.6
uses: funkincrew/ci-haxe@v3.2.7
with:
haxe-version: ${{ inputs.haxe }}
+2 -1
View File
@@ -118,10 +118,11 @@ jobs:
build-game-on-host:
strategy:
fail-fast: false
matrix:
include:
- target: windows
runs-on: windows-latest
runs-on: windows
- target: macos
runs-on: macos
runs-on:
+50 -21
View File
@@ -4,14 +4,34 @@ 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.6.1] - 2024-03-31
## [0.6.2] - 2025-03-31
### Added
- Updated the 0.6 credits list
### Fixed
- Additional shader fix for Stress (Pico Mix) crashing at the end (was the same issue as Senpai Pico shader error, just in a different shaderfile)
## [0.6.1] - 2025-03-31
### Fixed
- Hopefully Senpai Pico/Erect mix shader isn't brokey
- NG API encryption key was added proper, so medals + leaderboards posting should work
## [0.6.0] - 2024-03-31
## [0.6.0] - 2025-03-31
The Pit Stop 2 update!
### Added
- Added six (!) new playable songs! Check them out in the Freeplay menu for their respective characters.
- Cocoa (Pico Mix)
- Senpai (Pico Mix)
@@ -32,56 +52,55 @@ The Pit Stop 2 update!
- The new sticker system isn't fully available to mods yet, but we're working on it!
- New option in the Preferences menu: Strumline Backgrounds!
- Options in the Preferences menu now display an on-screen description when selected.
- Added a little easter egg to Pico's Good Results Screen animation.
- New song event type in the Chart Editor: Set Health Icon!
- This event is now used in Stress (Pico Mix).
- The HOME and END keys now jump to the top and bottom of the Freeplay song list, respectively. ([bb974c2](https://github.com/FunkinCrew/Funkin/commit/bb974c264270d10ff503784063e5d77bb352b3f7)) - by @AbnormalPoof in [#4103](https://github.com/FunkinCrew/Funkin/pull/4103)
- Added an option to launch the game in fullscreen. ([ee53ccd](https://github.com/FunkinCrew/Funkin/commit/ee53ccd32721e0790adfe82c60d4aca419db0a7f)) - by @AbnormalPoof in [#3738](https://github.com/FunkinCrew/Funkin/pull/3738)
- Added descriptions for each item in the Preferences menu. ([a17b0e8](https://github.com/FunkinCrew/Funkin/commit/a17b0e8b3cc1d56fcdc0b51eaca9fd57cdb5bce0)) - by @anysad in [#3872](https://github.com/FunkinCrew/Funkin/pull/3872)
- Added on-screen descriptions for each item in the Preferences menu. ([a17b0e8](https://github.com/FunkinCrew/Funkin/commit/a17b0e8b3cc1d56fcdc0b51eaca9fd57cdb5bce0)) - by @anysad in [#3872](https://github.com/FunkinCrew/Funkin/pull/3872)
- Added precise scrolling in the Chart Editor using Ctrl-Mouse Wheel. ([0d8e4a5](https://github.com/FunkinCrew/Funkin/commit/0d8e4a53305d6d069454812766300122f3581e31)) - by @ninjamuffin99 in [#3806](https://github.com/FunkinCrew/Funkin/pull/3806)
- Added a “None” option to the character selector in the Chart Editor. ([d9637d3](https://github.com/FunkinCrew/Funkin/commit/d9637d3a19466b9fc68a102676c46a74ef504909)) - by @Lasercar in [#4279](https://github.com/FunkinCrew/Funkin/pull/4279)
- Added the ability to flip the character in the Animation Editor. ([de02137](https://github.com/FunkinCrew/Funkin/commit/de02137d7c7d1779e85aeda34743f506a5b9cc27)) - by @AbnormalPoof in [#3028](https://github.com/FunkinCrew/Funkin/pull/3028)
- Added offsets support for album titles. ([69d8570](https://github.com/FunkinCrew/Funkin/commit/69d8570a9eb06011ed6dd95fcbef83d90f7f8684)) - by @AbnormalPoof in [#3618](https://github.com/FunkinCrew/Funkin/pull/3618)
- Added three new properties to stage data: `angle`, `scroll`, and `alpha`. ([ff56b19](https://github.com/FunkinCrew/Funkin/commit/ff56b1948aef42bbb6bb4ede4f9b2012d49ab044)) - by @AbnormalPoof in [#3720](https://github.com/FunkinCrew/Funkin/pull/3720)
- Added script events for losing/gaining focus. ([4b127b6](https://github.com/FunkinCrew/Funkin/commit/4b127b64130f6f753d0574ec66a1672322e4bd13)) - by @AbnormalPoof in [#3721](https://github.com/FunkinCrew/Funkin/pull/3721)
- Added 10 new functions to `ReflectUtil`.([6216655](https://github.com/FunkinCrew/Funkin/commit/62166554e7a176245d1a63bd15122033044c4e40)) - by @AbnormalPoof in [#3622](https://github.com/FunkinCrew/Funkin/pull/3622), [#3809](https://github.com/FunkinCrew/Funkin/pull/3809), and [#4019](https://github.com/FunkinCrew/Funkin/pull/4019)
- Added 10 new functions to `ReflectUtil`. ([6216655](https://github.com/FunkinCrew/Funkin/commit/62166554e7a176245d1a63bd15122033044c4e40)) - by @AbnormalPoof in [#3622](https://github.com/FunkinCrew/Funkin/pull/3622), [#3809](https://github.com/FunkinCrew/Funkin/pull/3809), and [#4019](https://github.com/FunkinCrew/Funkin/pull/4019)
- Added `DEBUG_BUILD` value to `Constants` to indicate whether a build has debug functions enabled. ([ad45b72](https://github.com/FunkinCrew/Funkin/commit/ad45b72b1ae8eb73a12dc51bcb59f66cc55e7bbd)) - by @AbnormalPoof in [#3853](https://github.com/FunkinCrew/Funkin/pull/3853)
### Changed
- Switched from hxCodec to hxvlc for video playback. This may break a mod or two.
- Check the [Funkin Modding Docs](https://funkincrew.github.io/funkin-modding-docs/09-migration/09-02-0.5.0-to-0.6.0.html) for more info on how to update your mods.
- Polymod should now ignore `.git` files when loading mods.
- Lots of improvements to issue and pull request organization. - by @Hundrec and @AbnormalPoof
- The pause menu can now be opened and closed rapidly.
- Adjusted difficulty ratings and scroll speeds for many songs.
- Chart Editor event fields now allow for values to be as specific as desired.
- For example, the Zoom Camera event can now be set to 0.9857.
- Lots of improvements to GitHub issue and pull request organization. - by @Hundrec and @AbnormalPoof
- Overhauled the Changelog to improve readability and properly credit contributors. ([4383fcf](https://github.com/FunkinCrew/Funkin/commit/4383fcf32c280a1c0ee7b9c80d255611d497cabc)) - by @Hundrec in [#4296](https://github.com/FunkinCrew/Funkin/pull/4296) and [#4298](https://github.com/FunkinCrew/Funkin/pull/4298)
- Made various improvements to the screenshot function. ([9ce7bbc](https://github.com/FunkinCrew/Funkin/commit/9ce7bbcfbb8f30ae120c876194f89bc4c787f585)) - by @Lasercar in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082)
- Accept keybinds (Z and Space by default) can now be used to exit the Results screen. ([edb270d](https://github.com/FunkinCrew/Funkin/commit/edb270d15e41784dccbf75639ac731840e80fe23)) - by @JVNpixels in [#3799](https://github.com/FunkinCrew/Funkin/pull/3799)
- Reordered UI keybinds in the controls menu for consistency. ([a01bcc3](https://github.com/FunkinCrew/Funkin/commit/a01bcc3da836ec52851ca9de13ef459daf61269a)) - by @lemz1 in [#3027](https://github.com/FunkinCrew/Funkin/pull/3027)
- New save files now have default Freeplay controls for gamepads. ([2b7f62e](https://github.com/FunkinCrew/Funkin/commit/2b7f62edd33de5527e259d9e5643f926d35da734)) - by @MrMadera in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
- Renamed “Auto Pause” preference to “Pause on Unfocus” for clarity. ([49a21c1](https://github.com/FunkinCrew/Funkin/commit/49a21c198236fbecbe5902bb106f78939cc6442a)) - by @JackXson-Real in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
- Made scrolling smoother in the Chart Editor. ([20d9016](https://github.com/FunkinCrew/Funkin/commit/20d90169845f1e50f849e39f4c5f818359756c78)) - by @ninjamuffin99 in [#3768](https://github.com/FunkinCrew/Funkin/pull/3768)
- The Chart Editor now clears the undo/redo history after loading a new song. ([c7bdc1a](https://github.com/FunkinCrew/Funkin/commit/c7bdc1abe265678468920ca3cc3d7eface5d6925)) - by @Lasercar in [#4308](https://github.com/FunkinCrew/Funkin/pull/4308)
- Mods with missing dependencies are now skipped instead of preventing all mods from loading. ([1c2fb43](https://github.com/FunkinCrew/Funkin/commit/1c2fb43ae16cf40be5ef94c40b047e8e772b1211)) - by @AbnormalPoof in [#3993](https://github.com/FunkinCrew/Funkin/pull/3993)
- Slightly improved flexibility for modding note hit animations. ([3aad825](https://github.com/FunkinCrew/Funkin/commit/3aad825f865c4ed87016983d44121e2c1610d332)) - by @TechnikTil in [#3936](https://github.com/FunkinCrew/Funkin/pull/3936)
- Introduced several QoL modding changes. ([785c4be](https://github.com/FunkinCrew/Funkin/commit/785c4be88b52dc1b5899013822fc004ba7d9894d)) - by @Kade-github in [#4009](https://github.com/FunkinCrew/Funkin/pull/4009)
- Lots of smaller bug fixes.
- Lots of smaller changes.
## New Contributors for 0.6.0
### Fixed
* @PatoFlamejanteTV made their first contribution in [#3843](https://github.com/FunkinCrew/Funkin/pull/3843)
* @sphis-Sinco made their first contribution in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
* @MrMadera made their first contribution in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
* @Lasercar made their first contribution in [#4100](https://github.com/FunkinCrew/Funkin/pull/4065)
* @MidyGamy made their first contribution in [#4068](https://github.com/FunkinCrew/Funkin/pull/4068)
* @MrScottyPieey made their first contribution in [#4085](https://github.com/FunkinCrew/Funkin/pull/4085)
* @JackXson-Real made their first contribution in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
# Fixed
- Shaders no longer create thin seams within atlas sprites.
- Completing a song in Practice Mode no longer plays a new rank animation in the Freeplay menu.
- Fixed lots of charting issues across many songs.
- The Chart Editor grid now properly adjusts to the new BPM after switching variations.
- Fixed a few crashes in the Stage Editor.
- Fixed a bug where the song would restart from the beginning instead of moving to the Results screen. ([3667c51](https://github.com/FunkinCrew/Funkin/commit/3667c51c1efe14cfe7c810e2f35991f08f50781a)) - by @KoloInDaCrib in [#4309](https://github.com/FunkinCrew/Funkin/pull/4309) and @Lasercar in [#4330](https://github.com/FunkinCrew/Funkin/pull/4330)
- Reduced stuttering when resyncing instrumental and voices tracks. ([22d41d2](https://github.com/FunkinCrew/Funkin/commit/22d41d21b88acb7422a0afcda8414682710bd2ed)) - by @TechnikTil in [#3955](https://github.com/FunkinCrew/Funkin/pull/3955)
- Songs with only instrumental tracks no longer stutter. ([dfe02ec](https://github.com/FunkinCrew/Funkin/commit/dfe02ec668b61d6308f459c978d12a7487f9dc28)) - by @KoloInDaCrib in [#3861](https://github.com/FunkinCrew/Funkin/pull/3861)
- The mouse cursor is no longer visible outside of debug editors. ([1c12b84](https://github.com/FunkinCrew/Funkin/commit/1c12b8467eca350eb28138473360d5358fa620e2)) - by @sphis-Sinco in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
- The debug mouse cursor no longer flickers before the Title Screen loads. ([1c12b84](https://github.com/FunkinCrew/Funkin/commit/1c12b8467eca350eb28138473360d5358fa620e2)) - by @sphis-Sinco in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
- Unbound keys now display as [N/A] instead of crashing the game. ([94e5d3c](https://github.com/FunkinCrew/Funkin/commit/94e5d3ca258cbaa5d9ab6f0cb26feda83b3433a2)) - by @NotHyper-474 in [#4355](https://github.com/FunkinCrew/Funkin/pull/4355)
- Songs can no longer be spam-selected after selecting an instrumental in Freeplay. ([0e0c4ae](https://github.com/FunkinCrew/Funkin/commit/0e0c4aeb7745cfb9479685ccbb635cf3743cddbb)) - by @AbnormalPoof in [#3866](https://github.com/FunkinCrew/Funkin/pull/3866)
- The Random capsule can now switch to Erect/Nightmare difficulties in Freeplay. ([6f32747](https://github.com/FunkinCrew/Funkin/commit/6f327474f30374f50774f432ea3ccb6d02579445)) - by @KoloInDaCrib in [#3838](https://github.com/FunkinCrew/Funkin/pull/3838)
- The millions place digit of the Freeplay score now updates properly. ([3719ca6](https://github.com/FunkinCrew/Funkin/commit/3719ca6ce7a170c3c890cdb2aeea3e4534b91736)) - by @Lasercar in [#4065](https://github.com/FunkinCrew/Funkin/pull/4065)
- Fixed a rare bug where a song would not register as beaten. ([a3e2373](https://github.com/FunkinCrew/Funkin/commit/a3e23733db104b1ef00cfcff17db3a5d032a4d67)) - by @AbnormalPoof in [#3820](https://github.com/FunkinCrew/Funkin/pull/3820)
- The difficulty graphic on the Results screen no longer cuts off incorrectly. ([b13bf05](https://github.com/FunkinCrew/Funkin/commit/b13bf05d16ff2977309e0c7ba3f049c0134e8902)) - by @AbnormalPoof in [#4161](https://github.com/FunkinCrew/Funkin/pull/4161)
- Four-digit long Total Notes values in the Results screen no longer overflow to the right. ([2be4c0c](https://github.com/FunkinCrew/Funkin/commit/2be4c0c7196e018a43f9697f015179f3efc3fcdb)) - by @Hundrec in [#4356](https://github.com/FunkinCrew/Funkin/pull/4356)
@@ -93,6 +112,16 @@ The Pit Stop 2 update!
- Removed an unused class from Polymod blacklist. ([06c12e3](https://github.com/FunkinCrew/Funkin/commit/06c12e36c6bd6df4e2be32a3bec540172e79e162)) - by @AbnormalPoof in [#3729](https://github.com/FunkinCrew/Funkin/pull/3729)
- Many additional small bug fixes.
## New Contributors for 0.6.0
* @PatoFlamejanteTV made their first contribution in [#3843](https://github.com/FunkinCrew/Funkin/pull/3843)
* @sphis-Sinco made their first contribution in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
* @MrMadera made their first contribution in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
* @MidyGamy made their first contribution in [#4068](https://github.com/FunkinCrew/Funkin/pull/4068)
* @Lasercar made their first contribution in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082)
* @MrScottyPieey made their first contribution in [#4085](https://github.com/FunkinCrew/Funkin/pull/4085)
## [0.5.3] - 2024-10-18
This patch resolves a critical issue that could cause user's save data to become corrupted. It is recommended that users switch to this version immediately and avoid using version 0.5.2.
+3 -3
View File
@@ -14,8 +14,8 @@ Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which include
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
- Reconfigure `MethodLength`
- Reconfigure `CyclomaticComplexity`
- Re-configure `MethodLength`
- Re-configure `CyclomaticComplexity`
- Re-enable `MagicNumber`
- Re-configure `NestedControlFlow`
- Re-configure `NestedIfDepth`
@@ -28,5 +28,5 @@ Prettier automatically handles formatting of JSON files, and can be configured i
### Prettier Notes
* Prettier will automatically attempt to place expressions on a single line if they fit, but will keep them multi-line if they are manually made multi-line.
* This means that long singleline objects are automatically expanded, and short multiline objects aren't automatically collapsed.
* This means that long single-line objects are automatically expanded, and short multi-line objects aren't automatically collapsed.
* You may want to use regex replacement to manually remove the first newline in short multi-line objects to convince Prettier to collapse them.
+1 -1
Submodule art updated: 4efa0536a3...78dc310219
+1 -1
Submodule assets updated: c445377389...ac932c19f9
+1 -1
View File
@@ -102,7 +102,7 @@ RUN <<EOF
# | jq '.[] | select(.name == "'"$neko_version"'")' \
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
# | jq -r '.browser_download_url')
neko_url="https://geo.thei.rs/funkin/neko-2.4.0-linux64.tar.gz"
neko_url="https://geo.thei.rs/funkin/neko-2.4.1-linux64.tar.gz"
curl -fL "$neko_url" | tar -xz -C /usr/local
EOF
+1 -1
View File
@@ -347,7 +347,7 @@
{
"props": {
"allowSingleLineStatement": true,
"tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"]
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
},
"type": "NeedBraces"
},
+10
View File
@@ -0,0 +1,10 @@
# Mac Compiling Guide + Considerations
There's a few extra considerations when compiling FNF for Mac that *we* have to handle when creating a wider release.
- [Creating a Universal Binary](#creating-a-universal-binary)
- Code-signing
- Notarizing
## Creating a Universal Binary
Run the `art/macos-universal.sh` script, which automatically compiles release versions of both arm64 and x86 of Funkin. You can also see there for reference of how it's done.
+176 -64
View File
@@ -1,6 +1,6 @@
# Contributing
Welcome to the Contributing Guide!
You can contribute to the Funkin' repository by opening issues or pull requests.
You can contribute to the Funkin' repository by opening issues or pull requests.
This guide will cover best practices for each type of contribution.
@@ -11,7 +11,7 @@ This guide will cover best practices for each type of contribution.
[Part 1: Etiquette](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-1-etiquette)
<details>
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
* [Requirements](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#requirements)
@@ -24,8 +24,8 @@ This guide will cover best practices for each type of contribution.
</details>
<details>
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
* [Choosing a base branch](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#choosing-a-base-branch)
@@ -39,6 +39,8 @@ This guide will cover best practices for each type of contribution.
* [funkin.assets PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#funkinassets-prs)
* [Charting PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#charting-prs)
</details>
[Closing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#closing)
@@ -54,11 +56,11 @@ This guide will cover best practices for each type of contribution.
# Part 2: Issues
Issues serve many purposes, from reporting bugs to suggesting new features.
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
## Requirements
Make sure you're playing:
- the latest version of the game (currently v0.5.3)
- the latest version of the game (currently v0.6.3)
- without any mods
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
@@ -84,7 +86,7 @@ Here's a list of commonly suggested features and the reasons why they won't be a
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
## Issue Types
@@ -122,6 +124,9 @@ Also only report one issue or enhancement at a time! If you have multiple bug re
Once you're sure your issue is unique and specific, feel free to submit it.
> [!IMPORTANT]
> **DO NOT CLOSE YOUR ISSUE FOR ANY REASON!** Your issue will be taken care of by a moderator!
**Thank you for opening issues!**
# Part 3: Pull Requests
@@ -133,6 +138,7 @@ When creating a branch in your fork, base your branch on either the `main` or `d
> [!CAUTION]
> Avoid using your fork's default branch (`main` in this case) for your PR. This is considered an [anti-pattern](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) by GitHub themselves!
> Instead, make a separate branch for your additions (ex. `docs/fix-typo` or `minor-bugfix`).
Choose the `main` branch if you modify:
- Documentation (`.md` files)
@@ -165,85 +171,173 @@ This process reapplies your changes on top of the updated branch and cleanly res
> This guide does not cover compiling. If you have trouble compiling the game, refer to the [Compilation Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md).
Code-based PRs make changes such as **fixing bugs** or **implementing new features** in the game.
This involves modifying one or several of the repository’s `.hx` files, found within the `source/` folder.
### Codestyle
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md).
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md). This keeps your code consistent with the rest of the codebase!
### Code comments
Code comments help others understand your changes, so the way you write them is important!
Here are some guidelines for writing comments in your code:
- Leave comments only when you believe a piece of code warrants explanation.
- Leave comments only when you believe a piece of code warrants explanation. If a piece of code is self-explanatory, it does not need a comment.
- Ensure that your comments provide meaningful insight into the function or purpose of the code.
- Write your comments in a clear and concise manner.
- Only sign your comments with your name when your changes are complex and may require further explanation.
### Example Comments
### Example code comments
#### DO NOT:
Below are examples of what you SHOULD NOT do when writing code comments.
```haxe
/**
* jumps around the song
* works with bpm changes but skipped notes still hurt
* @param sections how many sections to jump, negative = backwards
*/
function changeSection(sections:Int):Void
/**
* jumps around the song
* works with bpm changes but skipped notes still hurt
* @param sections how many sections to jump, negative = backwards
*/
function changeSection(sections:Int):Void
{
// Pause the music, as you probably guessed
// FlxG.sound.music.pause();
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
// Don't go back in time to before the song started, that would probably break a lot of things and cause a bunch of problems!
targetTimeMs = Math.max(0, targetTimeMs);
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
{
// Pause the music, as you probably guessed
// FlxG.sound.music.pause();
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
// Don't go back in time to before the song started, that would probably break a lot of things and cause a whole bunch of problems!
targetTimeMs = Math.max(0, targetTimeMs);
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
{
FlxG.sound.music.time = targetTimeMs;
}
// Handle skipped notes and events and all that jazz
handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
// regenNoteData(FlxG.sound.music.time);
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
// I hate this function - [GitHub username]
resyncVocals();
FlxG.sound.music.time = targetTimeMs;
}
// Handle skipped notes and events and all that jazz
handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
// regenNoteData(FlxG.sound.music.time);
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
// I hate this function - [GitHub username]
resyncVocals();
}
```
```haxe
// End the song when the music is complete.
FlxG.sound.music.onComplete = function() {
endSong(skipEndingTransition);
};
// A negative instrumental offset means the song skips the first few milliseconds of the track.
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
FlxG.sound.music.pitch = playbackRate;
// Prevent the volume from being wrong.
FlxG.sound.music.volume = 1.0;
// IF fadetween is not null we cancel it
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
// Play the vocals
trace('Playing vocals...');
// Add the vocals
add(vocals);
// Play the vocals for real this time lol
vocals.play();
// Set the vocals volume
vocals.volume = 1.0;
// Set the vocals pitch
vocals.pitch = playbackRate;
// Set the vocals time to the music time
vocals.time = FlxG.sound.music.time;
// trace('${FlxG.sound.music.time}');
// trace('${vocals.time}');
// functionThatWasntHereBeforeThisPRorSomethingIdKLOL();
/* testsprite = new FlxSprite(0, 0);
testsprite.loadGraphic(Paths.image('test'));
testsprite.screenCenter();
add(testsprite); */
// Me too [GitHub username] I hate this function it gave me pain and suffering - Girlfriend
resyncVocals();
```
```haxe
#if FEATURE_DEBUG_FUNCTIONS
// PAGEUP: who knows what this does
// SHIFT+PAGEUP: There will be dire consequences.
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
// PAGEUP: who knows what this does
// SHIFT+PAGEUP: There will be dire consequences.
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
#end
```
#### DO:
Below are examples on what you SHOULD do when writing code comments.
```haxe
/**
* Jumps forward or backward a number of sections in the song.
* Accounts for BPM changes, does not prevent death from skipped notes.
* @param sections The number of sections to jump, negative to go backwards.
*/
function changeSection(sections:Int):Void
/**
* Jumps forward or backward a number of sections in the song.
* Accounts for BPM changes, does not prevent death from skipped notes.
* @param sections The number of sections to jump, negative to go backwards.
*/
function changeSection(sections:Int):Void
{
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
// Don't go back in time to before the song started.
targetTimeMs = Math.max(0, targetTimeMs);
if (FlxG.sound.music != null)
{
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
// Don't go back in time to before the song started.
targetTimeMs = Math.max(0, targetTimeMs);
if (FlxG.sound.music != null)
{
FlxG.sound.music.time = targetTimeMs;
}
handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
resyncVocals();
FlxG.sound.music.time = targetTimeMs;
}
handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
resyncVocals();
}
```
```haxe
FlxG.sound.music.onComplete = function() {
endSong(skipEndingTransition);
};
// A negative instrumental offset means the song skips the first few milliseconds of the track.
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
FlxG.sound.music.pitch = playbackRate;
// Prevent the volume from being wrong.
FlxG.sound.music.volume = 1.0;
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
trace('Playing vocals...');
add(vocals);
vocals.play();
vocals.volume = 1.0;
vocals.pitch = playbackRate;
vocals.time = FlxG.sound.music.time;
resyncVocals();
```
```haxe
#if FEATURE_DEBUG_FUNCTIONS
// PAGEUP: Skip forward two sections.
// SHIFT+PAGEUP: Skip forward twenty sections.
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
// PAGEDOWN: Skip backward two section. Doesn't replace notes.
// SHIFT+PAGEDOWN: Skip backward twenty sections.
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
#end
```
## Documentation PRs
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
This involves modifying one or several of the repository’s `.md` files, found throughout the repository.
@@ -297,6 +391,24 @@ If you simultaneously modify files from both repositories, then open two separat
Be sure to choose `main` as the base branch for `funkin.assets` PRs, as no `develop` branch exists for that repository.
### Charting PRs
Charting PRs make changes such as **adding/removing notes** or **adjusting the placement of song events**.
This involves modifying one or several of the `funkin.assets` repository's `.json` chart files, found in the `preload/data/songs/` directory.
These PRs should only be opened in the `funkin.assets` repository.
> [!CAUTION]
> **No Major Recharts!** Any PR that makes major chart modifications will be rejected.
> Keep your PRs to small tweaks and fixes.
Here are some guidelines for opening a Charting PR:
- **Explain the issue.** Which song, variation, difficulty, and section/timestamp is the problem in? Help us understand with screenshots and videos.
- **Show your changes.** How does the chart look with your changes? Provide screenshots and videos here as well.
- **Minimize the diff.** If your changes are very small (e.g. a few notes), do not re-export the chart using the Chart Editor. Instead, manually edit the `.json` chart files to help GitHub display your changes cleanly.
If your PR is accepted, you will be credited as a GitHub contributor (but not as a charter in the Pause Menu).
# Closing
Thank you for reading the Contributing Guide.
+18 -5
View File
@@ -3,6 +3,7 @@
Most of this functionality is only available on debug builds of the game!
## Any State
- `INSERT`: ***MEMORY GARBAGE COLLECTION***: Immediately perform memory garbage collection.
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
@@ -11,6 +12,9 @@ Most of this functionality is only available on debug builds of the game!
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
## Loading State
- `SPACE`: Trace callbacks.
## **Play State**
- `H`: ***HIDE UI***: Makes the user interface invisible. Works in Pause Menu, great for screenshots.
- `1`: ***END SONG***: Immediately ends the song and moves to Results Screen on Freeplay, or next song on Story Mode.
@@ -19,20 +23,29 @@ Most of this functionality is only available on debug builds of the game!
- `9`: NEATO!
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
- `CHART Hotkey`: ***OPEN CHART EDITOR***: Open the Chart Editor with the current song loaded.
- `STAGE Hotkey`: ***OPEN STAGE EDITOR***: Open the old Stage Editor overlay.
## **Freeplay State**
- `F` (Freeplay Menu) - Move to Favorites
- `Q` (Freeplay Menu) - Back one category
- `E` (Freeplay Menu) - Forward one category
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
- `T`: ***TEST RANK ANIMATION***: Play the Gold Perfect rank animation on the currently selected song.
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
- `CTRL-LEFT`/`RIGHT`/`UP`/`DOWN`: ***OFFSET BF DJ IN CARTOON STATE***: While BF DJ's TV animation is playing, offset the DJ by 1 pixel in the opposite direction. `SHIFT` to move 10 pixels, `ALT` to move 0.1 pixels.
- `CTRL-C`: ***TRIGGER DJ CARTOON***: Play the Freeplay DJ's cartoon animation. Press again to return to its regular animation.
## **Title State**
- `Y` - WOAH
- `ESCAPE`: ***QUIT***: Quit the game.
- `D`: COOL
- `UI LEFT`/`UI RIGHT Hotkey`: 2COOL
- `Y`: WOAH
## **Main Menu**
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
- `SHIFT-ACCEPT`: ***OPEN FREEPLAY AS PICO***: When selecting Freeplay, open the Freeplay menu with the playable character set to Pico.
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
- `CTRL-ALT-SHIFT-M`: ***NO MORE ACCESS***: Re-locks all songs in Freeplay except those unlocked by default. Only available on debug builds.
- `CTRL-ALT-SHIFT-R`: ***GREAT SCORE?***: Give the user a hypothetical overridden score, and see if we can maintain that golden P rank. Only available on debug builds.
- `CTRL-ALT-SHIFT-P`: ***CHARACTER UNLOCK SCREEN***: Forces the Character Select screen to play Pico's unlocking animation. Only available on debug builds.
- `CTRL-ALT-SHIFT-N`: ***CHARACTER NOT SEEN***: Marks all characters as not seen and enables BF's new character unlocked animation in Freeplay. Only available on debug builds.
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
+1 -1
View File
@@ -4,7 +4,7 @@ This short document is designed to give a run-down on how code should be formatt
## Notes on IDEs and Extensions
The Visual Studio Code IDE is highly recommended, as this repo contains various configuration that works with VSCode extensions to automatically style certain things for you.
The Visual Studio Code IDE is highly recommended, as this repo contains various configurations that work with VSCode extensions to automatically style certain things for you.
VSCode is also the only IDE that has any good tools for Haxe so yeah.
+6 -6
View File
@@ -32,8 +32,8 @@
"name": "flxanimate",
"type": "git",
"dir": null,
"ref": "713d3de0e566d6cd54cde13711ab3e4f60f59f4b",
"url": "https://github.com/Dot-Stuff/flxanimate"
"ref": "f8842cea9883d6112a2c854cf93fa72856171428",
"url": "https://github.com/FunkinCrew/flxanimate"
},
{
"name": "format",
@@ -63,14 +63,14 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
"ref": "74ba53387eab0c4c2d3825e103fe70df8e46d9b1",
"ref": "07fc7aa8098deaea633b0726d01f83eb4ef8a832",
"url": "https://github.com/haxeui/haxeui-core"
},
{
"name": "haxeui-flixel",
"type": "git",
"dir": null,
"ref": "34fe148e45292439a9c3caf00890c93bcc6f9453",
"ref": "b899a4c7d7318c5ff2b1bb645fbc73728fad1ac9",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{
@@ -116,7 +116,7 @@
{
"name": "hxvlc",
"type": "haxelib",
"version": "2.0.1"
"version": "2.1.2"
},
{
"name": "json2object",
@@ -143,7 +143,7 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "d1322e60f97b5c6e977f9e3e8a04f22b5190e7d9",
"ref": "be81ad7e4e1a92c3482bcc009648a4ac892cfa35",
"url": "https://github.com/FunkinCrew/lime"
},
{
+8 -1
View File
@@ -25,7 +25,7 @@ class Project extends HXProject {
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
* You only have to change it here, the rest of the game will query this value.
*/
static final VERSION:String = "0.6.1";
static final VERSION:String = "0.6.3";
/**
* The game's name. Used as the default window title.
@@ -259,6 +259,12 @@ class Project extends HXProject {
*/
static final FEATURE_STAGE_EDITOR:FeatureFlag = "FEATURE_STAGE_EDITOR";
/**
* `-DFEATURE_RESULTS_DEBUG
* If this flag is enabled, a debug menu for Results screen will be accessible from the debug menu.
*/
static final FEATURE_RESULTS_DEBUG:FeatureFlag = "FEATURE_RESULTS_DEBUG";
/**
* `-DFEATURE_POLYMOD_MODS`
* If this flag is enabled, the game will enable the Polymod library's support for atomic mod loading from the `./mods` folder.
@@ -502,6 +508,7 @@ class Project extends HXProject {
// Should be true on debug builds or if GITHUB_BUILD is enabled.
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
FEATURE_RESULTS_DEBUG.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
// Got a lot of complains about this being turned off by default on some builds.
// TODO: Look into ways to optimize logging (maybe by using a thread pool?)
+3
View File
@@ -13,6 +13,7 @@ import openfl.events.Event;
import openfl.Lib;
import openfl.media.Video;
import openfl.net.NetStream;
import funkin.util.WindowUtil;
/**
* The main class which initializes HaxeFlixel and starts the game in its initial state.
@@ -67,6 +68,8 @@ class Main extends Sprite
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
#end
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
if (hasEventListener(Event.ADDED_TO_STAGE))
{
removeEventListener(Event.ADDED_TO_STAGE, init);
+1
View File
@@ -6,6 +6,7 @@ import openfl.utils.Future;
* 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
{
/**
+12 -1
View File
@@ -18,6 +18,7 @@ import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongRegistry;
import funkin.data.stickers.StickerRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.story.level.LevelRegistry;
@@ -97,9 +98,18 @@ class InitState extends FlxState
FlxG.sound.volumeDownKeys = [];
FlxG.sound.muteKeys = [];
// A small jumpstart to the soundtray, it usually sets itself to inactive (somewhere...)
// but that makes our soundtray not show up on init if we have the game muted.
// We set it to active so it at least calls it's update function once (see FlxGame.onEnterFrame(), it's called there)
// and also see FunkinSoundTray.update() to see what we do and how we check if we are muted or not
FlxG.game.soundTray.active = true;
// Set the game to a lower frame rate while it is in the background.
FlxG.game.focusLostFramerate = 30;
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
FlxG.fixedTimestep = false;
setupFlixelDebug();
//
@@ -184,6 +194,7 @@ class InitState extends FlxState
FreeplayStyleRegistry.instance.loadEntries();
AlbumRegistry.instance.loadEntries();
StageRegistry.instance.loadEntries();
StickerRegistry.instance.loadEntries();
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
// Move it to use a BaseRegistry.
@@ -241,7 +252,7 @@ class InitState extends FlxState
title: "Cum Song Erect by Kawai Sprite",
songId: "cum",
characterId: "pico",
difficultyId: "nightmare",
difficultyId: "hard",
isNewHighscore: true,
scoreData:
{
+23
View File
@@ -1,6 +1,7 @@
package funkin;
import funkin.save.Save;
import funkin.util.WindowUtil;
/**
* A core class which provides a store of user-configurable, globally relevant values.
@@ -176,6 +177,27 @@ class Preferences
return value;
}
/**
* If enabled, the game will utilize VSync (or adaptive VSync) on startup.
* @default `OFF`
*/
public static var vsyncMode(get, set):lime.ui.WindowVSyncMode;
static function get_vsyncMode():lime.ui.WindowVSyncMode
{
return Save?.instance?.options?.vsyncMode ?? lime.ui.WindowVSyncMode.OFF;
}
static function set_vsyncMode(value:lime.ui.WindowVSyncMode):lime.ui.WindowVSyncMode
{
WindowUtil.setVSyncMode(value);
var save:Save = Save.instance;
save.options.vsyncMode = value;
save.flush();
return value;
}
public static var unlockedFramerate(get, set):Bool;
static function get_unlockedFramerate():Bool
@@ -340,6 +362,7 @@ 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);
#if web
+1
View File
@@ -8,6 +8,7 @@ import io.newgrounds.objects.events.Result;
/**
* Use Newgrounds to perform basic telemetry. Ignore if not logged in to Newgrounds.
*/
@:nullSafety
class Events
{
// Only allow letters, numbers, spaces, dashes, and underscores.
+19 -12
View File
@@ -4,29 +4,33 @@ package funkin.api.newgrounds;
import io.newgrounds.Call.CallError;
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
import io.newgrounds.objects.events.Outcome;
import io.newgrounds.utils.ScoreBoardList;
class Leaderboards
{
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
{
if (NewgroundsClient.instance.leaderboards == null)
var leaderboardList:Null<ScoreBoardList> = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null)
{
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
return [];
}
var result:Map<Leaderboard, LeaderboardData> = [];
for (leaderboardId in NewgroundsClient.instance.leaderboards.keys())
else
{
var leaderboardData = NewgroundsClient.instance.leaderboards.get(leaderboardId);
if (leaderboardData == null) continue;
var result:Map<Leaderboard, LeaderboardData> = [];
// A little hacky, but it works.
result.set(cast leaderboardId, leaderboardData);
for (leaderboardId in leaderboardList.keys())
{
var leaderboardData = leaderboardList.get(leaderboardId);
if (leaderboardData == null) continue;
// A little hacky, but it works.
result.set(cast leaderboardId, leaderboardData);
}
return result;
}
return result;
}
/**
@@ -42,7 +46,10 @@ class Leaderboards
if (NewgroundsClient.instance.isLoggedIn())
{
var leaderboardData = NewgroundsClient.instance.leaderboards.get(leaderboard.getId());
var leaderboardList = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null) return;
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
if (leaderboardData != null)
{
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
+33 -30
View File
@@ -5,54 +5,55 @@ 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;
class Medals
{
public static var medalJSON:Array<MedalJSON>;
public static var medalJSON:Array<MedalJSON> = [];
public static function listMedalData():Map<Medal, MedalData>
{
if (NewgroundsClient.instance.medals == null)
var medalList = NewgroundsClient.instance.medals;
if (medalList == null)
{
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
return [];
}
var result:Map<Medal, MedalData> = [];
for (medalId in NewgroundsClient.instance.medals.keys())
else
{
var medalData = NewgroundsClient.instance.medals.get(medalId);
if (medalData == null) continue;
// TODO: Why do I have to do this, @:nullSafety is fucked up
var result:Map<Medal, MedalData> = [];
// A little hacky, but it works.
result.set(cast medalId, medalData);
for (medalId in medalList.keys())
{
var medalData = medalList.get(medalId);
if (medalData == null) continue;
// A little hacky, but it works.
result.set(cast medalId, medalData);
}
return result;
}
return result;
}
static function isValid(medalData:MedalData):Bool
{
// IDK why medalData can exist but _data can be null...
// TODO: Move checks to the NG library.
@:privateAccess
return (medalData != null && medalData._data != null);
}
public static function award(medal:Medal):Void
{
if (NewgroundsClient.instance.isLoggedIn())
{
var medalData = NewgroundsClient.instance.medals.get(medal.getId());
if (!isValid(medalData))
var medalList = NewgroundsClient.instance.medals;
if (medalList == null) return;
var medalData:Null<MedalData> = medalList.get(medal.getId());
@:privateAccess
if (medalData == null || medalData._data == null)
{
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
return;
}
if (!medalData.unlocked)
else if (!medalData.unlocked)
{
trace('[NEWGROUNDS] Awarding medal (${medal}).');
medalData.sendUnlock();
@@ -70,8 +71,6 @@ 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 g:FlxGraphic = null;
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
return medal == jsonMedal.idTest;
@@ -86,10 +85,14 @@ class Medals
// Lime/OpenFL parses it without the included prefix stuff, so we remove it.
str = str.replace("data:image/png;base64,", "").trim();
var bitmapData = BitmapData.fromBase64(str, "image/png");
if (str != null) g = FlxGraphic.fromBitmapData(bitmapData);
g.persist = true;
var medalGraphic:Null<FlxGraphic> = null;
if (str != null)
{
medalGraphic = FlxGraphic.fromBitmapData(bitmapData);
medalGraphic.persist = true;
}
NewgroundsMedalPlugin.play(medalData.value, medalData.name, g);
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
#end
}
else
@@ -119,7 +122,7 @@ class Medals
trace('[NEWGROUNDS] Failed to parse local medal data!');
for (error in parser.errors)
funkin.data.DataError.printError(error);
medalJSON = null;
medalJSON = [];
}
else
{
@@ -149,6 +149,9 @@ class NewgroundsClient
Save.instance.ngSessionId = null;
}
/**
* @return `true` if the user is logged in to Newgrounds.
*/
public function isLoggedIn():Bool
{
#if FEATURE_NEWGROUNDS
+55
View File
@@ -0,0 +1,55 @@
package funkin.api.newgrounds;
#if FEATURE_NEWGROUNDS
import io.newgrounds.NG;
#end
import funkin.api.newgrounds.NewgroundsClient;
import funkin.util.WindowUtil;
/**
* Handles referrals to links retrieved from the Newgrounds API.
*/
class Referral
{
/**
* Opens the referral page with the given name.
* @param referralName The name of the referral.
* @param fallbackUrl The URL to open if the referral fails or is not available.
*/
static function doReferral(referralName:String, fallbackUrl:String):Void
{
#if FEATURE_NEWGROUNDS
if (NewgroundsClient.instance.isLoggedIn())
{
NG.core?.calls.loader.loadReferral(false)
.addComponentParameter("referral_name", referralName)
.addResponseHandler(response -> {
trace(response);
if (response.success)
{
WindowUtil.openURL(response.result.data.url);
}
else
{
WindowUtil.openURL(fallbackUrl);
}
})
.send();
}
else
{
WindowUtil.openURL(fallbackUrl);
}
#else
WindowUtil.openURL(fallbackUrl);
#end
}
/**
* Opens the merch referral page.
*/
public static function doMerchReferral():Void
{
doReferral("merch_link", Constants.URL_MERCH_FALLBACK);
}
}
+32 -92
View File
@@ -9,14 +9,17 @@ import funkin.vis.dsp.SpectralAnalyzer;
using Lambda;
@:nullSafety
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
{
// public var vis:VisShit;
var analyzer:SpectralAnalyzer;
var analyzer:Null<SpectralAnalyzer> = null;
var volumes:Array<Float> = [];
public var snd:FlxSound;
public var snd:Null<FlxSound> = null;
static final BAR_COUNT:Int = 7;
// TODO: Make the sprites easier to soft code.
public function new(snd:FlxSound, pixel:Bool)
@@ -25,7 +28,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
this.snd = snd;
var visCount = pixel ? (7 + 1) : (7 + 1);
var visCount = pixel ? (BAR_COUNT + 1) : (BAR_COUNT + 1);
var visScale = pixel ? 6 : 1;
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas(pixel ? 'characters/abotPixel/aBotVizPixel' : 'characters/abot/aBotViz');
@@ -58,8 +61,10 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
public function initAnalyzer():Void
{
if (snd == null) return;
@:privateAccess
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, BAR_COUNT, 0.1, 40);
// A-Bot tuning...
analyzer.minDb = -65;
analyzer.maxDb = -25;
@@ -77,16 +82,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
// analyzer.fftN = 2048;
}
public function dumpSound():Void
{
snd = null;
analyzer = null;
}
var visTimer:Float = -1;
var visTimeMax:Float = 1 / 30;
override function update(elapsed:Float)
{
// updateViz();
// updateFFT(elapsed);
//
super.update(elapsed);
}
@@ -97,8 +103,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
override function draw()
{
if (analyzer != null) drawFFT();
super.draw();
drawFFT();
}
/**
@@ -106,7 +112,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
*/
function drawFFT():Void
{
var levels = analyzer.getLevels();
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
for (i in 0...min(group.members.length, levels.length))
{
@@ -118,12 +124,6 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
// decrement our animFrame, so we can get a value from 0-5 for animation frames
animFrame -= 1;
#if desktop
// Web version scales with the Flixel volume level.
// This line brings platform parity but looks worse.
// animFrame = Math.round(animFrame * FlxG.sound.volume);
#end
animFrame = Math.floor(Math.min(5, animFrame));
animFrame = Math.floor(Math.max(0, animFrame));
@@ -133,79 +133,19 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
}
}
// function updateFFT(elapsed:Float)
// {
// if (vis.snd != null)
// {
// vis.checkAndSetBuffer();
// if (vis.setBuffer)
// {
// var remappedShit:Int = 0;
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
// else
// remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
// var fftSamples:Array<Float> = [];
// var swagBucks = remappedShit;
// for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
// {
// var left = vis.audioData[swagBucks] / 32767;
// var right = vis.audioData[swagBucks + 1] / 32767;
// var balanced = (left + right) / 2;
// swagBucks += 2;
// fftSamples.push(balanced);
// }
// var freqShit = vis.funnyFFT(fftSamples);
// for (i in 0...group.members.length)
// {
// var getSliceShit = function(s:Int) {
// var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
// return Math.round(Math.pow(10, powShit));
// };
// // var powShit:Float = getSliceShit(i);
// var hzSliced:Int = getSliceShit(i);
// var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
// var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
// var avgVel:Float = 0;
// for (slice in volSlice)
// {
// avgVel += slice;
// }
// avgVel /= volSlice.length;
// avgVel *= 10000000;
// volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
// var animFrame:Int = Std.int(volumes[i]);
// animFrame = Math.floor(Math.min(5, animFrame));
// animFrame = Math.floor(Math.max(0, animFrame));
// animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
// group.members[i].animation.curAnim.curFrame = animFrame;
// if (FlxG.keys.justPressed.U)
// {
// trace(avgVel);
// trace(group.members[i].animation.curAnim.curFrame);
// }
// }
// // group.members[0].animation.curAnim.curFrame =
// }
// }
// }
// public function updateViz()
// {
// if (vis.snd != null)
// {
// var remappedShit:Int = 0;
// vis.checkAndSetBuffer();
// if (vis.setBuffer)
// {
// // var startingSample:Int = Std.int(FlxMath.remapToRange)
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
// for (i in 0...group.members.length)
// {
// var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
// var left = vis.audioData[sampleApprox] / 32767;
// var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
// group.members[i].animation.curAnim.curFrame = animFrame;
// }
// }
// }
// }
/**
* Explicitly define the default levels to draw when the analyzer is not available.
* @return Array<Bar>
*/
static function getDefaultLevels():Array<Bar>
{
var result:Array<Bar> = [];
for (i in 0...BAR_COUNT)
{
result.push({value: 0, peak: 0.0});
}
return result;
}
}
+14
View File
@@ -16,6 +16,7 @@ typedef EntryConstructorFunction = String->Void;
* @param J The type of the JSON data used when constructing.
*/
@:generic
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
{
/**
@@ -189,6 +190,19 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
return entries.get(id);
}
/**
* A list of all entries included in the base game.
* The actual function exists and is auto-generated on each registry at build time.
* @return Array<String>
*/
// public function listBaseGameEntryIds():Array<String> {}
/**
* A list of all entries that are not included in the base game.
* @return Array<String>
*/
// public function listModdedEntryIds():Array<String> {}
public function toString():String
{
return 'Registry(' + registryId + ', ${countEntries()} entries)';
@@ -0,0 +1,9 @@
package funkin.data;
/**
* An interface which automatically implements essential fields for a class extending `BaseRegistry`.
*
* @see `funkin.data.BaseRegistry`
*/
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
interface DefaultRegistryImpl {}
+1
View File
@@ -5,6 +5,7 @@ package funkin.data;
* A `String->Void` constructor is also mandatory, but enforced elsewhere.
* @param T The JSON data type of the registry entry.
*/
@:autoBuild(funkin.util.macro.RegistryMacro.buildEntry())
interface IRegistryEntry<T>
{
public final id:String;
@@ -2,8 +2,10 @@ package funkin.data.dialogue.conversation;
import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.ScriptedConversation;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the dialogue box data format.
@@ -14,74 +16,8 @@ class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
public static var instance(get, never):ConversationRegistry;
static var _instance:Null<ConversationRegistry> = null;
static function get_instance():ConversationRegistry
{
if (_instance == null) _instance = new ConversationRegistry();
return _instance;
}
public function new()
{
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<ConversationData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<ConversationData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<ConversationData>
{
var parser = new json2object.JsonParser<ConversationData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):Conversation
{
return ScriptedConversation.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedConversation.listScriptClasses();
}
}
@@ -3,8 +3,10 @@ package funkin.data.dialogue.dialoguebox;
import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the dialogue box data format.
@@ -15,74 +17,8 @@ class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
public static var instance(get, never):DialogueBoxRegistry;
static var _instance:Null<DialogueBoxRegistry> = null;
static function get_instance():DialogueBoxRegistry
{
if (_instance == null) _instance = new DialogueBoxRegistry();
return _instance;
}
public function new()
{
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<DialogueBoxData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<DialogueBoxData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<DialogueBoxData>
{
var parser = new json2object.JsonParser<DialogueBoxData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):DialogueBox
{
return ScriptedDialogueBox.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedDialogueBox.listScriptClasses();
}
}
@@ -2,8 +2,10 @@ package funkin.data.dialogue.speaker;
import funkin.play.cutscene.dialogue.Speaker;
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the speaker data format.
@@ -14,74 +16,8 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
public static var instance(get, never):SpeakerRegistry;
static var _instance:Null<SpeakerRegistry> = null;
static function get_instance():SpeakerRegistry
{
if (_instance == null) _instance = new SpeakerRegistry();
return _instance;
}
public function new()
{
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<SpeakerData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<SpeakerData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<SpeakerData>
{
var parser = new json2object.JsonParser<SpeakerData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):Speaker
{
return ScriptedSpeaker.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedSpeaker.listScriptClasses();
}
}
@@ -3,8 +3,10 @@ package funkin.data.freeplay.album;
import funkin.ui.freeplay.Album;
import funkin.data.freeplay.album.AlbumData;
import funkin.ui.freeplay.ScriptedAlbum;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class AlbumRegistry extends BaseRegistry<Album, AlbumData>
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the album data format.
@@ -15,70 +17,8 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData>
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
public static final instance:AlbumRegistry = new AlbumRegistry();
public function new()
{
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
* @param id The ID of the entry to load.
* @return The parsed data object.
*/
public function parseEntryData(id:String):Null<AlbumData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
* @return The parsed data object.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<AlbumData>
{
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):Album
{
return ScriptedAlbum.init(clsName, 'unknown');
}
function getScriptedClassNames():Array<String>
{
return ScriptedAlbum.listScriptClasses();
}
}
@@ -5,5 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.1]
### Added
- Added `stickerPack` to specify which sticker pack to use when transitioning into and out of song featuring this character.
- If the value isn't present, it will use a default sticker pack instead.
## [1.0.0]
Initial release.
@@ -31,6 +31,15 @@ class PlayerData
@:default(false)
public var showUnownedChars:Bool = false;
/**
* The default sticker pack to use for songs featuring this playable character.
* Can be overridden by specific songs.
* @default `default`
*/
@:optional
@:default(funkin.util.Constants.DEFAULT_STICKER_PACK)
public var stickerPack:String = Constants.DEFAULT_STICKER_PACK;
/**
* Which freeplay style to use for this character.
*/
@@ -325,7 +334,11 @@ typedef PlayerResultsAnimationData =
*/
var renderType:String;
var assetPath:String;
@:optional
var assetPath:Null<String>;
@:optional
var scriptClass:Null<String>;
@:optional
@:default([0, 0])
@@ -4,8 +4,10 @@ import funkin.data.freeplay.player.PlayerData;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
import funkin.save.Save;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the stage data format.
@@ -16,15 +18,6 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
public static var instance(get, never):PlayerRegistry;
static var _instance:Null<PlayerRegistry> = null;
static function get_instance():PlayerRegistry
{
if (_instance == null) _instance = new PlayerRegistry();
return _instance;
}
/**
* A mapping between stage character IDs and Freeplay playable character IDs.
*/
@@ -130,79 +123,4 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
{
return ownedCharacterIds.exists(characterId);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<PlayerData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<PlayerData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<PlayerData>
{
var parser = new json2object.JsonParser<PlayerData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):PlayableCharacter
{
return ScriptedPlayableCharacter.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedPlayableCharacter.listScriptClasses();
}
/**
* A list of all the playable characters from the base game, in order.
*/
public function listBaseGamePlayerIds():Array<String>
{
return ["bf", "pico"];
}
/**
* A list of all installed playable characters that are not from the base game.
*/
public function listModdedPlayerIds():Array<String>
{
return listEntryIds().filter(function(id:String):Bool {
return listBaseGamePlayerIds().indexOf(id) == -1;
});
}
}
@@ -3,8 +3,10 @@ package funkin.data.freeplay.style;
import funkin.ui.freeplay.FreeplayStyle;
import funkin.data.freeplay.style.FreeplayStyleData;
import funkin.ui.freeplay.ScriptedFreeplayStyle;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData>
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the style data format.
@@ -15,70 +17,8 @@ class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleDat
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
public static final instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
public function new()
{
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
* @param id The ID of the entry to load.
* @return The parsed data object.
*/
public function parseEntryData(id:String):Null<FreeplayStyleData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
* @return The parsed data object.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<FreeplayStyleData>
{
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):FreeplayStyle
{
return ScriptedFreeplayStyle.init(clsName, 'unknown');
}
function getScriptedClassNames():Array<String>
{
return ScriptedFreeplayStyle.listScriptClasses();
}
}
@@ -3,8 +3,10 @@ package funkin.data.notestyle;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.notestyle.ScriptedNoteStyle;
import funkin.data.notestyle.NoteStyleData;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the note style data format.
@@ -15,15 +17,6 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
public static var instance(get, never):NoteStyleRegistry;
static var _instance:Null<NoteStyleRegistry> = null;
static function get_instance():NoteStyleRegistry
{
if (_instance == null) _instance = new NoteStyleRegistry();
return _instance;
}
public function new()
{
super('NOTESTYLE', 'notestyles', NOTE_STYLE_DATA_VERSION_RULE);
@@ -33,61 +26,4 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
{
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<NoteStyleData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<NoteStyleData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<NoteStyleData>
{
var parser = new json2object.JsonParser<NoteStyleData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):NoteStyle
{
return ScriptedNoteStyle.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedNoteStyle.listScriptClasses();
}
}
+5
View File
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.5]
### Added
- Added `playData.stickerPack` to specify which sticker pack to use when transitioning into and out of a song.
- If the value isn't present, it will use the character's sticker pack instead.
## [2.2.4]
### Added
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
+7
View File
@@ -471,6 +471,13 @@ class SongPlayData implements ICloneable<SongPlayData>
@:optional
public var album:Null<String>;
/**
* The sticker pack for the song to use during transitions.
* If `null`, display the character's sticker pack.
*/
@:optional
public var stickerPack:Null<String>;
/**
* The start time for the audio preview in Freeplay.
* Defaults to 0 seconds in.
+3 -70
View File
@@ -10,11 +10,13 @@ import funkin.play.song.ScriptedSong;
import funkin.play.song.Song;
import funkin.util.assets.DataAssets;
import funkin.util.VersionUtil;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
using funkin.data.song.migrator.SongDataMigrator;
@:nullSafety
class SongRegistry extends BaseRegistry<Song, SongMetadata>
class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the stage data format.
@@ -40,19 +42,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
return '${Constants.TITLE} - ${Constants.VERSION}';
}
/**
* TODO: What if there was a Singleton macro which automatically created the property for us?
*/
public static var instance(get, never):SongRegistry;
static var _instance:Null<SongRegistry> = null;
static function get_instance():SongRegistry
{
if (_instance == null) _instance = new SongRegistry();
return _instance;
}
public function new()
{
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
@@ -419,16 +408,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
}
}
function createScriptedEntry(clsName:String):Song
{
return ScriptedSong.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedSong.listScriptClasses();
}
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
{
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
@@ -503,52 +482,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
return chartData;
}
/**
* A list of all the story weeks from the base game, in order.
* TODO: Should this be hardcoded?
*/
public function listBaseGameSongIds():Array<String>
{
return [
"tutorial",
"bopeebo",
"fresh",
"dadbattle",
"spookeez",
"south",
"monster",
"pico",
"philly-nice",
"blammed",
"satin-panties",
"high",
"milf",
"cocoa",
"eggnog",
"winter-horrorland",
"senpai",
"roses",
"thorns",
"ugh",
"guns",
"stress",
"darnell",
"lit-up",
"2hot",
"blazin"
];
}
/**
* A list of all installed story weeks that are not from the base game.
*/
public function listModdedSongIds():Array<String>
{
return listEntryIds().filter(function(id:String):Bool {
return listBaseGameSongIds().indexOf(id) == -1;
});
}
/**
* A list of all difficulties for a specific character.
*/
+3 -103
View File
@@ -2,8 +2,10 @@ package funkin.data.stage;
import funkin.play.stage.Stage;
import funkin.play.stage.ScriptedStage;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class StageRegistry extends BaseRegistry<Stage, StageData>
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the stage data format.
@@ -14,110 +16,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
public static var instance(get, never):StageRegistry;
static var _instance:Null<StageRegistry> = null;
static function get_instance():StageRegistry
{
if (_instance == null) _instance = new StageRegistry();
return _instance;
}
public function new()
{
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<StageData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<StageData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<StageData>
{
var parser = new json2object.JsonParser<StageData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):Stage
{
return ScriptedStage.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedStage.listScriptClasses();
}
/**
* A list of all the stages from the base game, in order.
* TODO: Should this be hardcoded?
*/
public function listBaseGameStageIds():Array<String>
{
return [
"mainStage",
"mainStageErect",
"spookyMansion",
"phillyTrain",
"phillyTrainErect",
"limoRide",
"limoRideErect",
"mallXmas",
"mallXmasErect",
"mallEvil",
"school",
"schoolEvil",
"tankmanBattlefield",
"phillyStreets",
"phillyStreetsErect",
"phillyBlazin",
];
}
/**
* A list of all installed story weeks that are not from the base game.
*/
public function listModdedStageIds():Array<String>
{
return listEntryIds().filter(function(id:String):Bool {
return listBaseGameStageIds().indexOf(id) == -1;
});
}
}
+9
View File
@@ -0,0 +1,9 @@
# Sticker Pack Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release. Sticker packs have been restructured compared to previous versions to be less complicated.
@@ -0,0 +1,30 @@
package funkin.data.stickers;
/**
* A type definition for a sticker pack.
* It includes things like its name, the artist, and what sticker graphics to use.
* @see https://lib.haxe.org/p/json2object/
*/
typedef StickerData =
{
/**
* Semantic version of the sticker set data.
*/
public var version:String;
/**
* Readable name of the sticker set.
*/
public var name:String;
/**
* The artist of the sticker set.
*/
public var artist:String;
/**
* The stickers in the set.
* This is simply a list of asset files to use.
*/
public var stickers:Array<String>;
}
@@ -0,0 +1,89 @@
package funkin.data.stickers;
import funkin.data.stickers.StickerData;
import funkin.ui.transition.stickers.StickerPack;
import funkin.ui.transition.stickers.ScriptedStickerPack;
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
{
/**
* The current version string for the sticker pack data format.
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateStickerData()` function.
*/
public static final STICKER_DATA_VERSION:thx.semver.Version = '1.0.0';
public static final STICKER_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
public static final instance:StickerRegistry = new StickerRegistry();
public function new()
{
super('STICKER', 'stickerpacks', STICKER_DATA_VERSION_RULE);
}
public function fetchDefault():StickerPack
{
return fetchEntry(Constants.DEFAULT_STICKER_PACK);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
* @param id The ID of the entry to load.
* @return The parsed data object.
*/
public function parseEntryData(id:String):Null<StickerData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser:json2object.JsonParser<StickerData> = new json2object.JsonParser<StickerData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
* @return The parsed data object.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<StickerData>
{
var parser:json2object.JsonParser<StickerData> = new json2object.JsonParser<StickerData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):StickerPack
{
return ScriptedStickerPack.init(clsName, 'unknown');
}
function getScriptedClassNames():Array<String>
{
return ScriptedStickerPack.listScriptClasses();
}
}
+23 -90
View File
@@ -3,8 +3,10 @@ package funkin.data.story.level;
import funkin.util.SortUtil;
import funkin.ui.story.Level;
import funkin.ui.story.ScriptedLevel;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
class LevelRegistry extends BaseRegistry<Level, LevelData>
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the level data format.
@@ -15,110 +17,41 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
public static var instance(get, never):LevelRegistry;
static var _instance:Null<LevelRegistry> = null;
static function get_instance():LevelRegistry
{
if (_instance == null) _instance = new LevelRegistry();
return _instance;
}
public function new()
{
super('LEVEL', 'levels', LEVEL_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
*/
public function parseEntryData(id:String):Null<LevelData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser = new json2object.JsonParser<LevelData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<LevelData>
{
var parser = new json2object.JsonParser<LevelData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):Level
{
return ScriptedLevel.init(clsName, "unknown");
}
function getScriptedClassNames():Array<String>
{
return ScriptedLevel.listScriptClasses();
}
/**
* A list of all the story weeks from the base game, in order.
* TODO: Should this be hardcoded?
* @return Array<String>
*/
public function listBaseGameLevelIds():Array<String>
public function listBaseGameEntryIds():Array<String>
{
// This MUST be hard-coded (overriding the auto-generated method)
// because the auto-generated method spits out values in alphabetical order.
return [
"tutorial",
"week1",
"week2",
"week3",
"week4",
"week5",
"week6",
"week7",
"weekend1"
'tutorial',
'week1',
'week2',
'week3',
'week4',
'week5',
'week6',
'week7',
'weekend1'
];
}
/**
* A list of all the story weeks in the game, in order.
* Modded levels are in alphabetical order at the end of the list.
* @return Array<String>
*/
public function listSortedLevelIds():Array<String>
{
var result = listEntryIds();
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameLevelIds()));
var result:Array<String> = listEntryIds();
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameEntryIds()));
return result;
}
/**
* A list of all installed story weeks that are not from the base game.
*/
public function listModdedLevelIds():Array<String>
{
return listEntryIds().filter(function(id:String):Bool {
return listBaseGameLevelIds().indexOf(id) == -1;
});
}
}
+1
View File
@@ -10,6 +10,7 @@ import flixel.math.FlxPoint;
* An offshoot of FlxTrail, but accomodates the way Funkin
* does offsets for characters. example, fixes Spirits trail
*/
@:nullSafety
class FunkTrail extends FlxTrail
{
/**
+1
View File
@@ -4,6 +4,7 @@ import flixel.util.FlxTimer;
import flixel.FlxCamera;
import openfl.filters.ColorMatrixFilter;
@:nullSafety
class RetroCameraFade
{
// im lazy, but we only use this for week 6
@@ -35,6 +35,11 @@ class FlxAtlasSprite extends FlxAnimate
*/
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
/**
* Signal dispatched when a looping animation finishes playing.
*/
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
var currentAnimation:String;
var canPlayOtherAnims:Bool = true;
@@ -307,7 +312,7 @@ class FlxAtlasSprite extends FlxAnimate
{
anim.curFrame = (fr != null) ? fr.index : 0;
anim.resume();
// _onAnimationComplete not called since this is a loop.
_onAnimationLoop();
}
else if (fr != null && anim.curFrame != anim.length - 1)
{
@@ -331,6 +336,18 @@ class FlxAtlasSprite extends FlxAnimate
}
}
function _onAnimationLoop():Void
{
if (currentAnimation != null)
{
onAnimationLoop.dispatch(currentAnimation);
}
else
{
onAnimationLoop.dispatch('');
}
}
var prevFrames:Map<Int, FlxFrame> = [];
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
+5
View File
@@ -74,6 +74,11 @@ interface INoteScriptedClass extends IScriptedClass
* Called when EITHER player (usually the player) misses a note.
*/
public function onNoteMiss(event:NoteScriptEvent):Void;
/**
* Called when EITHER player (usually the player) drops a hold note.
*/
public function onNoteHoldDrop(event:HoldNoteScriptEvent):Void;
}
/**
+6 -1
View File
@@ -11,6 +11,7 @@ import funkin.play.notes.notekind.NoteKindManager;
import funkin.data.song.SongRegistry;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.stickers.StickerRegistry;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterData.CharacterDataParser;
@@ -38,7 +39,7 @@ class PolymodHandler
* Using more complex rules allows mods from older compatible versions to stay functioning,
* while preventing mods made for future versions from being installed.
*/
static final API_VERSION_RULE:String = ">=0.5.0 <0.6.0";
static final API_VERSION_RULE:String = ">=0.6.3 <0.7.0";
/**
* Where relative to the executable that mods are located.
@@ -250,6 +251,9 @@ class PolymodHandler
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
// `funkin.util.FileUtil` has unrestricted access to the file system.
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
// Add blacklisting for prohibited classes and packages.
// `Sys`
@@ -480,6 +484,7 @@ class PolymodHandler
SpeakerRegistry.instance.loadEntries();
AlbumRegistry.instance.loadEntries();
StageRegistry.instance.loadEntries();
StickerRegistry.instance.loadEntries();
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
NoteKindManager.loadScripts();
@@ -5,6 +5,7 @@ import funkin.data.song.SongData.SongEventData;
import flixel.FlxState;
import flixel.FlxSubState;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.SustainTrail;
import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.Countdown.CountdownStep;
import funkin.play.notes.NoteDirection;
@@ -226,6 +227,48 @@ class GhostMissNoteScriptEvent extends ScriptEvent
}
}
class HoldNoteScriptEvent extends NoteScriptEvent
{
/**
* The hold note that was hit (or dropped).
*/
public var holdNote:SustainTrail;
/**
* The score the player received for hitting the note.
*/
public var score:Int;
/**
* If the hit causes a combo break.
*/
public var isComboBreak:Bool = false;
/**
* The time difference when the player hit the note
*/
public var hitDiff:Float = 0;
/**
* Whether this note hit causes a note splash to display.
* Defaults to true only on "sick" notes.
*/
public var doesNotesplash:Bool = false;
public function new(type:ScriptEventType, holdNote:SustainTrail, healthChange:Float, score:Int, isComboBreak:Bool, cancelable:Bool = false):Void
{
super(type, null, healthChange, comboCount, true);
this.holdNote = holdNote;
this.score = score;
this.isComboBreak = isComboBreak;
}
public override function toString():String
{
return 'HoldNoteScriptEvent(type=$type, holdNote=$holdNote, healthChange=$healthChange, score=$score, isComboBreak=$isComboBreak, cancelable=$cancelable)';
}
}
/**
* An event that is fired when the song reaches an event.
*/
@@ -90,6 +90,9 @@ class ScriptEventDispatcher
case NOTE_MISS:
t.onNoteMiss(cast event);
return;
case NOTE_HOLD_DROP:
t.onNoteHoldDrop(cast event);
return;
default: // Continue;
}
}
@@ -88,6 +88,14 @@ enum abstract ScriptEventType(String) from String to String
*/
var NOTE_MISS = 'NOTE_MISS';
/**
* Called when a character lets go of a hold note.
* Important information such as note data, player/opponent, etc. are all provided.
*
* This event is not cancelable.
*/
var NOTE_HOLD_DROP = 'NOTE_HOLD_DROP';
/**
* Called when a character presses a note when there was none there, causing them to lose health.
* Important information such as direction pressed, etc. are all provided.
+88
View File
@@ -57,6 +57,9 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
// TODO: Half of these aren't actually being called!!!!!!!
/**
* Called when ANY script event is dispatched.
*/
public function onScriptEvent(event:ScriptEvent) {}
/**
@@ -71,55 +74,140 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
*/
public function onDestroy(event:ScriptEvent) {}
/**
* Called every frame.
*/
public function onUpdate(event:UpdateScriptEvent) {}
/**
* Called when the game is paused.
*/
public function onPause(event:PauseScriptEvent) {}
/**
* Called when the game is resumed.
*/
public function onResume(event:ScriptEvent) {}
/**
* Called when the song begins.
*/
public function onSongStart(event:ScriptEvent) {}
/**
* Called when the song ends.
*/
public function onSongEnd(event:ScriptEvent) {}
/**
* Called when the player dies.
*/
public function onGameOver(event:ScriptEvent) {}
/**
* Called when a note on the strumline has been rendered and is now onscreen.
* This gets dispatched for both the player and opponent strumlines.
*/
public function onNoteIncoming(event:NoteScriptEvent) {}
/**
* Called when a note has been hit.
* This gets dispatched for both the player and opponent strumlines.
*/
public function onNoteHit(event:HitNoteScriptEvent) {}
/**
* Called when a note has been missed.
* This gets dispatched for both the player and opponent strumlines.
*/
public function onNoteMiss(event:NoteScriptEvent) {}
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
/**
* Called when the player presses a key without any notes present.
*/
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
/**
* Called when a step is hit in the song.
*/
public function onStepHit(event:SongTimeScriptEvent) {}
/**
* Called when a beat is hit in the song.
*/
public function onBeatHit(event:SongTimeScriptEvent) {}
/**
* Called when a song event is triggered.
*/
public function onSongEvent(event:SongEventScriptEvent) {}
/**
* Called when the countdown begins.
*/
public function onCountdownStart(event:CountdownScriptEvent) {}
/**
* Called for every step in the countdown.
*/
public function onCountdownStep(event:CountdownScriptEvent) {}
/**
* Called when the countdown ends, but BEFORE the song starts.
*/
public function onCountdownEnd(event:CountdownScriptEvent) {}
/**
* Called when the song's chart has been parsed and loaded.
*/
public function onSongLoaded(event:SongLoadScriptEvent) {}
/**
* Called when the game is about to switch to a new state.
*/
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
/**
* Called after the game has switched to a new state.
*/
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
/**
* Called when the game regains focus.
* This does not get called if "Auto Pause" is disabled.
*/
public function onFocusGained(event:FocusScriptEvent) {}
/**
* Called when the game loses focus.
* This does not get called if "Auto Pause" is disabled.
*/
public function onFocusLost(event:FocusScriptEvent) {}
/**
* Called when the game is about to open a substate.
*/
public function onSubStateOpenBegin(event:SubStateScriptEvent) {}
/**
* Called when a substate has been opened.
*/
public function onSubStateOpenEnd(event:SubStateScriptEvent) {}
/**
* Called when the game is about to close a substate.
*/
public function onSubStateCloseBegin(event:SubStateScriptEvent) {}
/**
* Called when a substate has been closed.
*/
public function onSubStateCloseEnd(event:SubStateScriptEvent) {}
/**
* Called when the song has been restarted.
*/
public function onSongRetry(event:SongRetryEvent) {}
}
+14 -10
View File
@@ -291,7 +291,7 @@ class GameOverSubState extends MusicBeatSubState
}
else
{
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
var targetState:funkin.ui.transition.stickers.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
if (PlayStatePlaylist.isStoryMode)
@@ -299,16 +299,20 @@ class GameOverSubState extends MusicBeatSubState
PlayStatePlaylist.reset();
}
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
var stickerPack = switch (PlayState.instance.currentChart.song.id)
{
case "tutorial": "tutorial";
case "darnell" | "lit-up" | "2hot": "weekend";
default: "all";
};
var stickerPackId:Null<String> = PlayState.instance.currentChart.stickerPack;
openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
if (stickerPackId == null)
{
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
var playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER);
if (playerCharacter != null)
{
stickerPackId = playerCharacter.getStickerPackID();
}
}
openSubState(new funkin.ui.transition.stickers.StickerSubState({targetState: targetState, stickerPack: stickerPackId}));
}
}
+16 -12
View File
@@ -82,8 +82,8 @@ class PauseSubState extends MusicBeatSubState
*/
static final PAUSE_MENU_ENTRIES_CONVERSATION:Array<PauseMenuEntry> = [
{text: 'Resume', callback: resume},
{text: 'Restart Dialogue', callback: restartConversation},
{text: 'Skip Dialogue', callback: skipConversation},
{text: 'Restart Dialogue', callback: restartConversation},
{text: 'Exit to Menu', callback: quitToMenu},
];
@@ -737,8 +737,8 @@ class PauseSubState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) -> new StoryMenuState(sticker) : (sticker) ->
FreeplayState.build(sticker);
var targetState:funkin.ui.transition.stickers.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
// Do this AFTER because this resets the value of isStoryMode!
if (PlayStatePlaylist.isStoryMode)
@@ -746,16 +746,20 @@ class PauseSubState extends MusicBeatSubState
PlayStatePlaylist.reset();
}
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
var stickerPack = switch (PlayState.instance.currentChart.song.id)
{
case "tutorial": "tutorial";
case "darnell" | "lit-up" | "2hot": "weekend";
default: "all";
};
var stickerPackId:Null<String> = PlayState.instance.currentChart.stickerPack;
state.openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
if (stickerPackId == null)
{
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
var playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER);
if (playerCharacter != null)
{
stickerPackId = playerCharacter.getStickerPackID();
}
}
state.openSubState(new funkin.ui.transition.stickers.StickerSubState({targetState: targetState, stickerPack: stickerPackId}));
}
/**
+74 -118
View File
@@ -32,7 +32,6 @@ import funkin.api.newgrounds.Events;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.components.ComboMilestone;
import funkin.play.components.HealthIcon;
import funkin.play.components.PopUpStuff;
import funkin.play.cutscene.dialogue.Conversation;
@@ -657,9 +656,6 @@ class PlayState extends MusicBeatSubState
cameraFollowPoint = new FlxObject(0, 0);
}
// Reduce physics accuracy (who cares!!!) to improve animation quality.
FlxG.fixedTimestep = false;
// This state receives update() even when a substate is active.
this.persistentUpdate = true;
// This state receives draw calls even when a substate is active.
@@ -838,8 +834,6 @@ class PlayState extends MusicBeatSubState
startingSong = true;
isPlayerDying = false;
inputSpitter = [];
// Reset music properly.
if (FlxG.sound.music != null)
{
@@ -881,6 +875,10 @@ class PlayState extends MusicBeatSubState
// Delete all notes and reset the arrays.
regenNoteData();
// so the song doesn't start too early :D
Conductor.instance.update(-5000, false);
// Reset camera zooming
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
@@ -889,11 +887,27 @@ class PlayState extends MusicBeatSubState
health = Constants.HEALTH_STARTING;
songScore = 0;
Highscore.tallies.combo = 0;
Countdown.performCountdown();
// timer for vwoosh
var vwooshTimer = new FlxTimer();
vwooshTimer.start(0.5, function(t:FlxTimer) {
Conductor.instance.update(startTimestamp - Conductor.instance.combinedOffset, false);
if (playerStrumline.notes.length == 0) playerStrumline.updateNotes();
if (opponentStrumline.notes.length == 0) opponentStrumline.updateNotes();
playerStrumline.vwooshInNotes();
opponentStrumline.vwooshInNotes();
Countdown.performCountdown();
});
// Reset the health icons.
if (currentStage.getBoyfriend() != null)
{
currentStage.getBoyfriend().initHealthIcon(false);
}
if (currentStage.getDad() != null)
{
currentStage.getDad().initHealthIcon(true);
}
needsReset = false;
}
@@ -1510,35 +1524,6 @@ class PlayState extends MusicBeatSubState
// Break up into individual lines to aid debugging.
var shouldShowComboText:Bool = false;
// TODO: Re-enable combo text (how to do this without sections?).
// if (currentSong != null)
// {
// shouldShowComboText = (Conductor.instance.currentBeat % 8 == 7);
// var daSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16)];
// shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection);
// shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5);
//
// var daNextSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16) + 1];
// var isEndOfSong = .getSong().length < Std.int(Conductor.instance.currentBeat / 16);
// shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection));
// }
if (shouldShowComboText)
{
var animShit:ComboMilestone = new ComboMilestone(-100, 300, Highscore.tallies.combo);
animShit.scrollFactor.set(0.6, 0.6);
animShit.zIndex = 1100;
animShit.cameras = [camHUD];
add(animShit);
var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation
new FlxTimer().start(((Conductor.instance.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
animShit.forceFinish();
});
}
if (playerStrumline != null) playerStrumline.onBeatHit();
if (opponentStrumline != null) opponentStrumline.onBeatHit();
@@ -2369,7 +2354,7 @@ class PlayState extends MusicBeatSubState
{
// Call an event to allow canceling the note miss.
// NOTE: This is what handles the character animations!
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, -Constants.HEALTH_MISS_PENALTY, 0, true);
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Constants.HEALTH_MISS_PENALTY, 0, true);
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
@@ -2416,18 +2401,45 @@ class PlayState extends MusicBeatSubState
// The player dropped a hold note.
holdNote.handledMiss = true;
// Mute vocals and play miss animation, but don't penalize.
// Mute vocals and play miss animation.
// vocals.playerVolume = 0;
// if (currentStage != null && currentStage.getBoyfriend() != null) currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true);
if (!isBotPlayMode)
{
if (holdNote.sustainLength > Constants.HOLD_DROP_PENALTY_THRESHOLD_MS)
{
// Penalize the player for letting go of a hold note too early.
trace('Player dropped a hold note, penalizing... (has hit: ${holdNote.hitNote})');
// Different penalty based on whether the note itself was missed,
// or the note was hit and then the hold was dropped.
var remainingLengthSec = holdNote.sustainLength / Constants.MS_PER_SEC;
var healthChangeUncapped = remainingLengthSec * Constants.HEALTH_HOLD_DROP_PENALTY_PER_SECOND;
// If the base note of the hold was missed, don't penalize them more on top of that.
var healthChangeMax = Constants.HEALTH_HOLD_DROP_PENALTY_MAX - (holdNote.hitNote ? -Constants.HEALTH_MISS_PENALTY : 0);
var healthChange = healthChangeUncapped.clamp(healthChangeMax, 0);
var scoreChange = Std.int(Constants.SCORE_HOLD_DROP_PENALTY_PER_SECOND * remainingLengthSec);
var event:HoldNoteScriptEvent = new HoldNoteScriptEvent(NOTE_HOLD_DROP, holdNote, healthChange, scoreChange, true);
dispatchEvent(event);
trace('Penalizing score by ${event.score} and health by ${event.healthChange} for dropping hold note (is combo break: ${event.isComboBreak})!');
applyScore(event.score, 'miss', event.healthChange, event.isComboBreak);
// Play the miss sound.
vocals.playerVolume = 0;
FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6));
}
else
{
trace('Hold note too short, not penalizing...');
}
}
}
}
}
/**
* Spitting out the input for ravy 🙇‍♂️!!
*/
var inputSpitter:Array<ScoreInput> = [];
function handleSkippedNotes():Void
{
for (note in playerStrumline.notes.members)
@@ -2612,31 +2624,10 @@ class PlayState extends MusicBeatSubState
{
if (pressArray[i]) indices.push(i);
}
if (indices.length > 0)
{
for (i in 0...indices.length)
{
inputSpitter.push(
{
t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
}
}
else
{
inputSpitter.push(
{
t: Std.int(Conductor.instance.songPosition),
d: -1,
l: 20
});
}
}
vocals.playerVolume = 0;
applyScore(-10, 'miss', healthChange, true);
applyScore(Scoring.getMissScore(), 'miss', healthChange, true);
if (playSound)
{
@@ -2657,7 +2648,7 @@ class PlayState extends MusicBeatSubState
{
var event:GhostMissNoteScriptEvent = new GhostMissNoteScriptEvent(direction, // Direction missed in.
hasPossibleNotes, // Whether there was a note you could have hit.
- 1 * Constants.HEALTH_MISS_PENALTY, // How much health to add (negative).
Constants.HEALTH_GHOST_MISS_PENALTY, // How much health to add (negative).
- 10 // Amount of score to add (negative).
);
dispatchEvent(event);
@@ -2682,15 +2673,6 @@ class PlayState extends MusicBeatSubState
{
if (pressArray[i]) indices.push(i);
}
for (i in 0...indices.length)
{
inputSpitter.push(
{
t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
}
}
if (event.playSound)
@@ -2764,8 +2746,6 @@ class PlayState extends MusicBeatSubState
// SHIFT+PAGEDOWN: Skip backward twenty sections.
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
#end
if (FlxG.keys.justPressed.B) trace(inputSpitter.join('\n'));
}
/**
@@ -2831,27 +2811,6 @@ class PlayState extends MusicBeatSubState
{
if (pressArray[i]) indices.push(i);
}
if (indices.length > 0)
{
for (i in 0...indices.length)
{
inputSpitter.push(
{
t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
}
}
else
{
inputSpitter.push(
{
t: Std.int(Conductor.instance.songPosition),
d: -1,
l: 20
});
}
}
comboPopUps.displayRating(daRating);
if (combo >= 10) comboPopUps.displayCombo(combo);
@@ -2935,13 +2894,6 @@ class PlayState extends MusicBeatSubState
dispatchEvent(event);
if (event.eventCanceled) return;
#if sys
// spitter for ravy, teehee!!
var writer = new json2object.JsonWriter<Array<ScoreInput>>();
var output = writer.write(inputSpitter, ' ');
sys.io.File.saveContent("./scores.json", output);
#end
deathCounter = 0;
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
@@ -2975,6 +2927,10 @@ class PlayState extends MusicBeatSubState
// adds current song data into the tallies for the level (story levels)
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
#if FEATURE_NEWGROUNDS
Leaderboards.submitSongScore(currentSong.id, suffixedDifficulty, songScore);
#end
if (!isPracticeMode && !isBotPlayMode)
{
#if FEATURE_NEWGROUNDS
@@ -3017,7 +2973,7 @@ class PlayState extends MusicBeatSubState
}
});
// Award various medals based on variation, difficulty, and scoring rank.
// Award various medals based on variation, difficulty, song ID, and scoring rank.
if (scoreRank == ScoringRank.SHIT) Medals.award(LossRating);
if (scoreRank >= ScoringRank.PERFECT && currentDifficulty == 'hard') Medals.award(PerfectRatingHard);
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'hard') Medals.award(GoldPerfectRatingHard);
@@ -3062,19 +3018,18 @@ class PlayState extends MusicBeatSubState
},
};
if (currentSong.validScore
&& Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
#if FEATURE_NEWGROUNDS
// Award a medal for beating a Story level.
Medals.awardStoryLevel(PlayStatePlaylist.campaignId);
// Submit the score for the Story level to Newgrounds.
Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore);
Events.logCompleteLevel(PlayStatePlaylist.campaignId);
#end
if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
{
#if FEATURE_NEWGROUNDS
// Award a medal for beating a Story level.
Medals.awardStoryLevel(PlayStatePlaylist.campaignId);
// Submit the score for the Story level to Newgrounds.
Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore);
Events.logCompleteLevel(PlayStatePlaylist.campaignId);
#end
Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
isNewHighscore = true;
}
@@ -3331,6 +3286,7 @@ class PlayState extends MusicBeatSubState
storyMode: PlayStatePlaylist.isStoryMode,
songId: currentChart.song.id,
difficultyId: currentDifficulty,
variationId: currentVariation,
characterId: currentChart.characters.player,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
prevScoreData: prevScoreData,
+2
View File
@@ -19,6 +19,8 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
var dumbNumb = Std.parseInt(Std.string(val));
var prevNum:ScoreNum;
dumbNumb = Std.int(Math.min(dumbNumb, Math.pow(10, group.members.length) - 1));
while (dumbNumb > 0)
{
scoreStart += 1;
+75 -68
View File
@@ -1,34 +1,37 @@
package funkin.play;
import funkin.ui.story.StoryMenuState;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.addons.display.FlxBackdrop;
import flixel.effects.FlxFlicker;
import flixel.FlxSprite;
import flixel.FlxSubState;
import funkin.graphics.FunkinSprite;
import flixel.effects.FlxFlicker;
import flixel.graphics.frames.FlxBitmapFont;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxPoint;
import funkin.ui.MusicBeatSubState;
import flixel.math.FlxRect;
import flixel.text.FlxBitmapText;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import flixel.util.FlxColor;
import flixel.tweens.FlxEase;
import funkin.graphics.FunkinCamera;
import funkin.ui.freeplay.FreeplayState;
import flixel.tweens.FlxTween;
import flixel.addons.display.FlxBackdrop;
import funkin.audio.FunkinSound;
import flixel.util.FlxColor;
import flixel.util.FlxGradient;
import flixel.util.FlxTimer;
import funkin.play.scoring.Scoring;
import funkin.save.Save.SaveScoreData;
import funkin.audio.FunkinSound;
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.LeftMaskShader;
import funkin.play.components.TallyCounter;
import funkin.play.components.ClearPercentCounter;
import funkin.play.components.TallyCounter;
import funkin.play.scoring.Scoring;
import funkin.play.song.Song;
import funkin.data.song.SongRegistry;
import funkin.save.Save.SaveScoreData;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.freeplay.FreeplayState;
import funkin.ui.MusicBeatSubState;
import funkin.ui.story.StoryMenuState;
import funkin.modding.base.ScriptedFlxAtlasSprite;
#if FEATURE_NEWGROUNDS
import funkin.api.newgrounds.Medals;
#end
@@ -72,9 +75,10 @@ class ResultState extends MusicBeatSubState
delay:Float
}> = [];
var playerCharacterId:Null<String>;
var playerCharacterId:Null<String> = null;
var playerCharacter:Null<PlayableCharacter> = null;
var introMusicAudio:Null<FunkinSound>;
var introMusicAudio:Null<FunkinSound> = null;
var rankBg:FunkinSprite;
final cameraBG:FunkinCamera;
@@ -171,7 +175,7 @@ class ResultState extends MusicBeatSubState
// Fetch playable character data. Default to BF on the results screen if we can't find it.
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
trace('Got playable character: ${playerCharacter?.getName()}');
// Query JSON data based on the rank, then use that to build the animation(s) the player sees.
@@ -186,13 +190,27 @@ class ResultState extends MusicBeatSubState
if (Preferences.naughtyness && animData.filter != "naughty" || !Preferences.naughtyness && animData.filter != "safe") continue;
}
var animPath:String = Paths.stripLibrary(animData.assetPath);
var animLibrary:String = Paths.getLibrary(animData.assetPath);
var animPath:String = "";
var animLibrary:String = "";
if (animData.assetPath != null)
{
animPath = Paths.stripLibrary(animData.assetPath);
animLibrary = Paths.getLibrary(animData.assetPath);
}
var offsets = animData.offsets ?? [0, 0];
switch (animData.renderType)
{
case 'animateatlas':
var animation:FlxAtlasSprite = new FlxAtlasSprite(offsets[0], offsets[1], Paths.animateAtlas(animPath, animLibrary));
@:nullSafety(Off)
var animation:FlxAtlasSprite = null;
if (animData.scriptClass != null) animation = ScriptedFlxAtlasSprite.init(animData.scriptClass, offsets[0], offsets[1]);
else
animation = new FlxAtlasSprite(offsets[0], offsets[1], Paths.animateAtlas(animPath, animLibrary));
if (animation == null) continue;
animation.zIndex = animData.zIndex ?? 500;
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
@@ -221,7 +239,6 @@ class ResultState extends MusicBeatSubState
animation.onAnimationComplete.add((_name:String) -> {
if (animation != null)
{
trace("AHAHAH");
animation.anim.curFrame = animData.loopFrame ?? 0;
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
@@ -267,7 +284,7 @@ class ResultState extends MusicBeatSubState
}
}
var diffSpr:String = 'diff_${params?.difficultyId ?? 'Normal'}';
var diffSpr:String = 'diff_${params?.difficultyId ?? Constants.DEFAULT_DIFFICULTY}';
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
add(difficulty);
@@ -489,7 +506,7 @@ class ResultState extends MusicBeatSubState
// Only play the tick sound if the number increased.
if (clearPercentLerp != clearPercentCounter.curNumber)
{
trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
// trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
clearPercentLerp = clearPercentCounter.curNumber;
FunkinSound.playOnce(Paths.sound('scrollMenu'));
}
@@ -502,8 +519,9 @@ class ResultState extends MusicBeatSubState
clearPercentCounter.curNumber = clearPercentTarget;
#if FEATURE_NEWGROUNDS
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
// This is the easiest spot to do the medal calculation lol.
if (clearPercentTarget == 69) Medals.award(Nice);
if (isScoreValid && clearPercentTarget == 69) Medals.award(Nice);
#end
clearPercentCounter.flash(true);
@@ -697,34 +715,6 @@ class ResultState extends MusicBeatSubState
override function update(elapsed:Float):Void
{
// if(FlxG.keys.justPressed.R){
// FlxG.switchState(() -> new funkin.play.ResultState(
// {
// storyMode: false,
// title: "Cum Song Erect by Kawai Sprite",
// songId: "cum",
// difficultyId: "nightmare",
// isNewHighscore: true,
// scoreData:
// {
// score: 1_234_567,
// tallies:
// {
// sick: 200,
// good: 0,
// bad: 0,
// shit: 0,
// missed: 0,
// combo: 0,
// maxCombo: 69,
// totalNotesHit: 200,
// totalNotes: 200 // 0,
// }
// },
// }));
// }
// maskShaderSongName.swagSprX = songName.x;
maskShaderDifficulty.swagSprX = difficulty.x;
if (movingSongStuff)
@@ -745,8 +735,16 @@ class ResultState extends MusicBeatSubState
if (controls.PAUSE || controls.ACCEPT)
{
if (_parentState is funkin.ui.debug.results.ResultsDebugSubState)
{
if (introMusicAudio != null)
{
introMusicAudio.stop();
introMusicAudio.destroy();
introMusicAudio = null;
}
close(); // IF we are a substate, we will close ourselves. This is used from ResultsDebugSubState
if (introMusicAudio != null)
}
else if (introMusicAudio != null)
{
@:nullSafety(Off)
introMusicAudio.onComplete = null;
@@ -792,13 +790,18 @@ class ResultState extends MusicBeatSubState
var shouldTween = false;
var shouldUseSubstate = false;
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
var stickerPack = switch (PlayState.instance?.currentChart?.song?.id)
var stickerPackId:Null<String> = null;
var song:Null<Song> = params.songId == null ? null : SongRegistry.instance.fetchEntry(params.songId);
if (song != null)
{
case "tutorial": "tutorial";
case "darnell" | "lit-up" | "2hot": "weekend";
default: "all";
};
stickerPackId = song.getStickerPackId(params?.difficultyId ?? Constants.DEFAULT_DIFFICULTY, params?.variationId ?? Constants.DEFAULT_VARIATION);
}
if (stickerPackId == null && playerCharacter != null)
{
stickerPackId = playerCharacter.getStickerPackID();
}
if (params.storyMode)
{
@@ -821,11 +824,10 @@ class ResultState extends MusicBeatSubState
// No new characters.
shouldTween = false;
shouldUseSubstate = true;
targetState = new funkin.ui.transition.StickerSubState(
targetState = new funkin.ui.transition.stickers.StickerSubState(
{
targetState: (sticker) -> new StoryMenuState(sticker),
stickerSet: stickerSet,
stickerPack: stickerPack
stickerPack: stickerPackId
});
}
}
@@ -859,11 +861,10 @@ class ResultState extends MusicBeatSubState
{
shouldTween = false;
shouldUseSubstate = true;
targetState = new funkin.ui.transition.StickerSubState(
targetState = new funkin.ui.transition.stickers.StickerSubState(
{
targetState: (sticker) -> FreeplayState.build(null, sticker),
stickerSet: stickerSet,
stickerPack: stickerPack
stickerPack: stickerPackId
});
}
}
@@ -943,10 +944,16 @@ typedef ResultsStateParams =
/**
* The difficulty ID of the song/week we just played.
* @default Normal
* @default `Constants.DEFAULT_DIFFICULTY`
*/
var ?difficultyId:String;
/**
* The variation ID of the song/week we just played.
* @default `Constants.DEFAULT_VARIATION`
*/
var ?variationId:String;
/**
* The score, accuracy, and judgements.
*/
+26 -3
View File
@@ -553,17 +553,40 @@ class BaseCharacter extends Bopper
if (event.note.noteData.getMustHitNote() && characterType == BF)
{
// If the note is from the same strumline, play the sing animation.
// If the note is from the same strumline, play the miss animation.
this.playSingAnimation(event.note.noteData.getDirection(), true);
}
else if (!event.note.noteData.getMustHitNote() && characterType == DAD)
{
// If the note is from the same strumline, play the sing animation.
// If the note is from the same strumline, play the miss animation.
this.playSingAnimation(event.note.noteData.getDirection(), true);
}
else if (event.note.noteData.getMustHitNote() && characterType == GF)
{
playComboDropAnimation(Highscore.tallies.combo);
playComboDropAnimation(event.comboCount);
}
}
public override function onNoteHoldDrop(event:HoldNoteScriptEvent)
{
super.onNoteHoldDrop(event);
// If another script cancelled the event, don't do anything.
if (event.eventCanceled) return;
if (event.holdNote.noteData.getMustHitNote() && characterType == BF)
{
// If the note is from the same strumline, play the miss animation.
this.playSingAnimation(event.holdNote.noteData.getDirection(), true);
}
else if (!event.holdNote.noteData.getMustHitNote() && characterType == DAD)
{
// If the note is from the same strumline, play the miss animation.
this.playSingAnimation(event.holdNote.noteData.getDirection(), true);
}
else if (event.holdNote.noteData.getMustHitNote() && event.isComboBreak && characterType == GF)
{
playComboDropAnimation(event.comboCount);
}
}
@@ -296,7 +296,7 @@ class CharacterDataParser
charPath += "monsterpixel";
case "mom" | "mom-car":
charPath += "mommypixel";
case "pico-blazin" | "pico-playable" | "pico-speaker":
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
charPath += "picopixel";
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
charPath += "gfpixel";
@@ -308,7 +308,7 @@ class CharacterDataParser
charPath += "senpaipixel";
case "spooky-dark":
charPath += "spookypixel";
case "tankman-atlas":
case "tankman-atlas" | "tankman-bloody":
charPath += "tankmanpixel";
case "pico-christmas" | "pico-dark":
charPath += "picopixel";
@@ -1,126 +0,0 @@
package funkin.play.components;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.util.FlxTimer;
import funkin.audio.FunkinSound;
class ComboMilestone extends FlxTypedSpriteGroup<FlxSprite>
{
var effectStuff:FlxSprite;
var wasComboSetup:Bool = false;
var daCombo:Int = 0;
var grpNumbers:FlxTypedGroup<ComboMilestoneNumber>;
var onScreenTime:Float = 0;
public function new(x:Float, y:Float, daCombo:Int = 0)
{
super(x, y);
this.daCombo = daCombo;
effectStuff = new FlxSprite(0, 0);
effectStuff.frames = Paths.getSparrowAtlas('comboMilestone');
effectStuff.animation.addByPrefix('funny', 'NOTE COMBO animation', 24, false);
effectStuff.animation.play('funny');
effectStuff.animation.finishCallback = function(nameThing) {
kill();
};
effectStuff.setGraphicSize(Std.int(effectStuff.width * 0.7));
add(effectStuff);
grpNumbers = new FlxTypedGroup<ComboMilestoneNumber>();
// add(grpNumbers);
}
public function forceFinish():Void
{
if (onScreenTime < 0.9)
{
new FlxTimer().start((Conductor.instance.beatLengthMs / 1000) * 0.25, function(tmr) {
forceFinish();
});
}
else
effectStuff.animation.play('funny', true, false, 18);
}
override function update(elapsed:Float)
{
onScreenTime += elapsed;
if (effectStuff.animation.curAnim.curFrame == 17) effectStuff.animation.pause();
if (effectStuff.animation.curAnim.curFrame == 2 && !wasComboSetup)
{
setupCombo(daCombo);
}
if (effectStuff.animation.curAnim.curFrame == 18)
{
grpNumbers.forEach(function(spr:ComboMilestoneNumber) {
spr.animation.reset();
});
}
if (effectStuff.animation.curAnim.curFrame == 20)
{
grpNumbers.forEach(function(spr:ComboMilestoneNumber) {
spr.kill();
});
}
super.update(elapsed);
}
function setupCombo(daCombo:Int)
{
FunkinSound.playOnce(Paths.sound('comboSound'));
wasComboSetup = true;
var loopNum:Int = 0;
while (daCombo > 0)
{
var comboNumber:ComboMilestoneNumber = new ComboMilestoneNumber(450 - (100 * loopNum), 20 + 14 * loopNum, daCombo % 10);
comboNumber.setGraphicSize(Std.int(comboNumber.width * 0.7));
grpNumbers.add(comboNumber);
add(comboNumber);
loopNum += 1;
daCombo = Math.floor(daCombo / 10);
}
}
}
class ComboMilestoneNumber extends FlxSprite
{
public function new(x:Float, y:Float, digit:Int)
{
super(x - 20, y);
var stringNum:String = Std.string(digit);
frames = Paths.getSparrowAtlas('comboMilestoneNumbers');
animation.addByPrefix(stringNum, stringNum, 24, false);
animation.play(stringNum);
updateHitbox();
}
var shiftedX:Bool = false;
override function update(elapsed:Float)
{
if (animation.curAnim.curFrame == 2 && !shiftedX)
{
shiftedX = true;
x += 20;
}
super.update(elapsed);
}
}
@@ -8,6 +8,7 @@ import funkin.util.MathUtil;
/**
* Numerical counters used next to each judgement in the Results screen.
*/
@:nullSafety
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
{
public var curNumber:Float = 0;
@@ -17,13 +18,13 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
public var align:FlxTextAlign = FlxTextAlign.LEFT;
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF, align:FlxTextAlign = FlxTextAlign.LEFT)
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int, align:FlxTextAlign = FlxTextAlign.LEFT)
{
super(x, y);
this.align = align;
this.flavour = flavour;
this.flavour = flavour ?? 0xFFFFFFFF;
this.neededNumber = neededNumber;
@@ -79,6 +80,7 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
}
}
@:nullSafety
class TallyNumber extends FlxSprite
{
public function new(x:Float, y:Float, digit:Int)
@@ -28,22 +28,11 @@ import funkin.util.EaseUtil;
@:nullSafety
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
{
/**
* The ID of the conversation.
*/
public final id:String;
/**
* The current state of the conversation.
*/
var state:ConversationState = ConversationState.Start;
/**
* Conversation data as parsed from the JSON file.
* `null` if the data could not be parsed or loaded.
*/
public final _data:Null<ConversationData>;
/**
* The current entry in the dialogue.
*/
@@ -659,16 +648,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
outroTween = null;
}
}
public override function toString():String
{
return 'Conversation($id)';
}
static function _fetchData(id:String):Null<ConversationData>
{
return ConversationRegistry.instance.parseEntryDataWithMigration(id, ConversationRegistry.instance.fetchEntryVersion(id));
}
}
// Managing things with a single enum is a lot easier than a multitude of flags.
@@ -16,8 +16,6 @@ import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData>
{
public final id:String;
public var dialogueBoxName(get, never):String;
function get_dialogueBoxName():String
@@ -25,8 +23,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
return _data.name ?? 'UNKNOWN';
}
public final _data:DialogueBoxData;
/**
* Offset the speaker's sprite by this much when playing each animation.
*/
@@ -247,8 +243,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
var animNames:Array<String> = this.boxSprite?.animation?.getNameList() ?? [];
trace('[DIALOGUE BOX] Successfully loaded ${animNames.length} animations for ${id}');
boxSprite.animation.callback = this.onAnimationFrame;
boxSprite.animation.finishCallback = this.onAnimationFinished;
boxSprite.animation.onFrameChange.add(this.onAnimationFrame);
boxSprite.animation.onFinish.add(this.onAnimationFinished);
}
/**
@@ -419,14 +415,4 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
}
public function onScriptEvent(event:ScriptEvent):Void {}
public override function toString():String
{
return 'DialogueBox($id)';
}
static function _fetchData(id:String):Null<DialogueBoxData>
{
return DialogueBoxRegistry.instance.parseEntryDataWithMigration(id, DialogueBoxRegistry.instance.fetchEntryVersion(id));
}
}
@@ -16,16 +16,6 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
*/
class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRegistryEntry<SpeakerData>
{
/**
* The internal ID for this speaker.
*/
public final id:String;
/**
* The full data for a speaker.
*/
public final _data:SpeakerData;
/**
* A readable name for this speaker.
*/
@@ -298,14 +288,4 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
}
public function onScriptEvent(event:ScriptEvent):Void {}
public override function toString():String
{
return 'Speaker($id)';
}
static function _fetchData(id:String):Null<SpeakerData>
{
return SpeakerRegistry.instance.parseEntryDataWithMigration(id, SpeakerRegistry.instance.fetchEntryVersion(id));
}
}
+5
View File
@@ -104,6 +104,11 @@ class NoteSprite extends FunkinSprite
return noteData.length > 0;
}
/**
* The Y Offset of the note.
*/
public var yOffset:Float = 0.0;
/**
* Set this flag to true when hitting the note to avoid scoring it multiple times.
*/
+258 -52
View File
@@ -1,5 +1,6 @@
package funkin.play.notes;
import flixel.math.FlxPoint;
import flixel.util.FlxSignal.FlxTypedSignal;
import flixel.FlxG;
import funkin.play.notes.notestyle.NoteStyle;
@@ -18,35 +19,46 @@ import funkin.play.notes.notekind.NoteKindManager;
*/
class Strumline extends FlxSpriteGroup
{
/**
* The directions of the notes on the strumline, in order.
*/
public static final DIRECTIONS:Array<NoteDirection> = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT];
/**
* A magic number for the size of the strumline, in pixels.
*/
public static final STRUMLINE_SIZE:Int = 104;
/**
* The spacing between notes on the strumline, in pixels.
*/
public static final NOTE_SPACING:Int = STRUMLINE_SIZE + 8;
// Positional fixes for new strumline graphics.
static final INITIAL_OFFSET = -0.275 * STRUMLINE_SIZE;
static final INITIAL_OFFSET:Float = -0.275 * STRUMLINE_SIZE;
static final NUDGE:Float = 2.0;
static final KEY_COUNT:Int = 4;
static final NOTE_SPLASH_CAP:Int = 6;
static var RENDER_DISTANCE_MS(get, never):Float;
var renderDistanceMs(get, never):Float;
/**
* The custom render distance for the strumline.
* This should be in miliseconds only! Not pixels.
*/
public static var CUSTOM_RENDER_DISTANCE_MS:Float = 0.0;
public var customRenderDistanceMs:Float = 0.0;
/**
* Whether to use the custom render distance.
* If false, the render distance will be calculated based on the screen height.
*/
public static var USE_CUSTOM_RENDER_DISTANCE:Bool = false;
public var useCustomRenderDistance:Bool = false;
static function get_RENDER_DISTANCE_MS():Float
function get_renderDistanceMs():Float
{
if (USE_CUSTOM_RENDER_DISTANCE) return CUSTOM_RENDER_DISTANCE_MS;
return FlxG.height / Constants.PIXELS_PER_MS;
if (useCustomRenderDistance) return customRenderDistanceMs;
return FlxG.height / Constants.PIXELS_PER_MS / scrollSpeed;
}
/**
@@ -71,6 +83,9 @@ class Strumline extends FlxSpriteGroup
// Used in-game to control the scroll speed within a song
public var scrollSpeed:Float = 1.0;
/**
* Reset the scroll speed to the current chart's scroll speed.
*/
public function resetScrollSpeed():Void
{
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
@@ -101,8 +116,16 @@ class Strumline extends FlxSpriteGroup
*/
public var notes:FlxTypedSpriteGroup<NoteSprite>;
/**
* The hold notes currently being rendered on the strumline.
* This group iterates over this every frame to update hold note positions.
* The PlayState also iterates over this to calculate user inputs.
*/
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
/**
* A signal that is dispatched when a note is spawned and heading towards the strumline.
*/
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
var background:FunkinSprite;
@@ -218,6 +241,9 @@ class Strumline extends FlxSpriteGroup
return value;
}
/**
* Refresh the strumline, sorting its children by z-index.
*/
public function refresh():Void
{
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
@@ -241,7 +267,7 @@ class Strumline extends FlxSpriteGroup
#if FEATURE_GHOST_TAPPING
/**
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
* @return `true` if no notes are in range of the strumline and the player can spam without penalty.
*/
public function mayGhostTap():Bool
{
@@ -286,31 +312,41 @@ class Strumline extends FlxSpriteGroup
});
}
public function getNoteSprite(noteData:SongNoteData):NoteSprite
/**
* Get a note sprite corresponding to the given note data.
* @param target The note data for the note sprite.
* @return The note sprite.
*/
public function getNoteSprite(target:SongNoteData):NoteSprite
{
if (noteData == null) return null;
if (target == null) return null;
for (note in notes.members)
{
if (note == null) continue;
if (note.alive) continue;
if (note.noteData == noteData) return note;
if (note.noteData == target) return note;
}
return null;
}
public function getHoldNoteSprite(noteData:SongNoteData):SustainTrail
/**
* Get a hold note sprite corresponding to the given note data.
* @param target The note data for the hold note.
* @return The hold note sprite.
*/
public function getHoldNoteSprite(target:SongNoteData):SustainTrail
{
if (noteData == null || ((noteData.length ?? 0.0) <= 0.0)) return null;
if (target == null || ((target.length ?? 0.0) <= 0.0)) return null;
for (holdNote in holdNotes.members)
{
if (holdNote == null) continue;
if (holdNote.alive) continue;
if (holdNote.noteData == noteData) return holdNote;
if (holdNote.noteData == target) return holdNote;
}
return null;
@@ -321,6 +357,8 @@ class Strumline extends FlxSpriteGroup
*/
public function vwooshNotes():Void
{
var vwooshTime:Float = 0.5;
for (note in notes.members)
{
if (note == null) continue;
@@ -331,7 +369,7 @@ class Strumline extends FlxSpriteGroup
var targetY:Float = FlxG.height + note.y;
if (Preferences.downscroll) targetY = 0 - note.height;
FlxTween.tween(note, {y: targetY}, 0.5,
FlxTween.tween(note, {y: targetY}, vwooshTime,
{
ease: FlxEase.expoIn,
onComplete: function(twn) {
@@ -352,7 +390,7 @@ class Strumline extends FlxSpriteGroup
var targetY:Float = FlxG.height + holdNote.y;
if (Preferences.downscroll) targetY = 0 - holdNote.height;
FlxTween.tween(holdNote, {y: targetY}, 0.5,
FlxTween.tween(holdNote, {y: targetY}, vwooshTime,
{
ease: FlxEase.expoIn,
onComplete: function(twn) {
@@ -365,23 +403,65 @@ class Strumline extends FlxSpriteGroup
}
/**
* For a note's strumTime, calculate its Y position relative to the strumline.
* NOTE: Assumes Conductor and PlayState are both initialized.
* @param strumTime
* @return Float
* Reverse of vwooshNotes, we bring the notes IN (by their offsets)
*/
public function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float
public function vwooshInNotes():Void
{
// Make the note move faster visually as it moves offscreen.
// var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
// ^^^ commented this out... do NOT make it move faster as it moves offscreen!
var vwoosh:Float = 1.0;
var vwooshTime:Float = 0.5;
return
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
for (note in notes.members)
{
if (note == null) continue;
if (!note.alive) continue;
note.yOffset = 200;
if (Preferences.downscroll)
{
note.yOffset = -200;
}
FlxTween.tween(note, {yOffset: 0}, vwooshTime,
{
ease: FlxEase.expoOut,
onComplete: function(twn) {
note.yOffset = 0;
}
});
}
for (holdNote in holdNotes.members)
{
if (holdNote == null) continue;
if (!holdNote.alive) continue;
holdNote.yOffset = 200;
if (Preferences.downscroll)
{
holdNote.yOffset = -200;
}
FlxTween.tween(holdNote, {yOffset: 0}, vwooshTime,
{
ease: FlxEase.expoOut,
onComplete: function(twn) {
holdNote.yOffset = 0;
}
});
}
}
function updateNotes():Void
/**
* For a note's strumTime, calculate its Y position relative to the strumline.
* NOTE: Assumes Conductor and PlayState are both initialized.
* @param strumTime The strumtime of the note.
* @return The Y position of the note.
*/
public function calculateNoteYPos(strumTime:Float):Float
{
return
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1);
}
public function updateNotes():Void
{
if (noteData.length == 0) return;
@@ -392,7 +472,7 @@ class Strumline extends FlxSpriteGroup
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
var hitWindowStart:Float = conductorInUse.songPosition - Constants.HIT_WINDOW_MS;
var renderWindowStart:Float = conductorInUse.songPosition + RENDER_DISTANCE_MS;
var renderWindowStart:Float = conductorInUse.songPosition + renderDistanceMs;
for (noteIndex in nextNoteIndex...noteData.length)
{
@@ -407,7 +487,7 @@ class Strumline extends FlxSpriteGroup
}
if (note.time > renderWindowStart) break; // Note is too far ahead to render
var noteSprite = buildNoteSprite(note);
var noteSprite:NoteSprite = buildNoteSprite(note);
if (note.length > 0)
{
@@ -424,12 +504,11 @@ class Strumline extends FlxSpriteGroup
{
if (note == null || !note.alive) continue;
var vwoosh:Bool = note.holdNoteSprite == null;
// Set the note's position.
if (!customPositionData) note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime, vwoosh);
if (!customPositionData) note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime) + note.yOffset;
// If the note is miss
var isOffscreen = Preferences.downscroll ? note.y > FlxG.height : note.y < -note.height;
var isOffscreen:Bool = Preferences.downscroll ? note.y > FlxG.height : note.y < -note.height;
if (note.handledMiss && isOffscreen)
{
killNote(note);
@@ -453,7 +532,8 @@ class Strumline extends FlxSpriteGroup
}
}
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
final magicNumberIGuess:Float = 8;
var renderWindowEnd:Float = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + (renderDistanceMs / magicNumberIGuess);
if (holdNote.missedNote && conductorInUse.songPosition >= renderWindowEnd)
{
@@ -494,17 +574,20 @@ class Strumline extends FlxSpriteGroup
var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Constants.PIXELS_PER_MS;
var vwoosh:Bool = false;
if (!customPositionData)
{
if (Preferences.downscroll)
{
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
holdNote.y = this.y
- INITIAL_OFFSET
+ calculateNoteYPos(holdNote.strumTime)
- holdNote.height
+ STRUMLINE_SIZE / 2
+ holdNote.yOffset;
}
else
{
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2;
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + yOffset + STRUMLINE_SIZE / 2 + holdNote.yOffset;
}
}
@@ -544,17 +627,21 @@ class Strumline extends FlxSpriteGroup
{
// Hold note is new, render it normally.
holdNote.visible = true;
var vwoosh:Bool = false;
if (!customPositionData)
{
if (Preferences.downscroll)
{
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
holdNote.y = this.y
- INITIAL_OFFSET
+ calculateNoteYPos(holdNote.strumTime)
- holdNote.height
+ STRUMLINE_SIZE / 2
+ holdNote.yOffset;
}
else
{
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + STRUMLINE_SIZE / 2;
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + STRUMLINE_SIZE / 2 + holdNote.yOffset;
}
}
}
@@ -607,6 +694,9 @@ class Strumline extends FlxSpriteGroup
nextNoteIndex = 0;
}
/**
* Called on each beat of the song.
*/
public function onBeatHit():Void
{
if (notes.members.length > 1) notes.members.insertionSort(compareNoteSprites.bind(FlxSort.ASCENDING));
@@ -614,16 +704,29 @@ class Strumline extends FlxSpriteGroup
if (holdNotes.members.length > 1) holdNotes.members.insertionSort(compareHoldNoteSprites.bind(FlxSort.ASCENDING));
}
/**
* Called when a key is pressed.
* @param dir The direction of the key that was pressed.
*/
public function pressKey(dir:NoteDirection):Void
{
heldKeys[dir] = true;
}
/**
* Called when a key is released.
* @param dir The direction of the key that was released.
*/
public function releaseKey(dir:NoteDirection):Void
{
heldKeys[dir] = false;
}
/**
* Check if a key is held down.
* @param dir The direction of the key to check.
* @return `true` if the key is held down, `false` otherwise.
*/
public function isKeyHeld(dir:NoteDirection):Bool
{
return heldKeys[dir];
@@ -673,6 +776,11 @@ class Strumline extends FlxSpriteGroup
#end
}
/**
* Apply note data from a chart to this strumline.
* Note data should be valid and apply only to this strumline.
* @param data The note data to apply.
*/
public function applyNoteData(data:Array<SongNoteData>):Void
{
this.notes.clear();
@@ -685,6 +793,7 @@ class Strumline extends FlxSpriteGroup
}
/**
* Hit a note.
* @param note The note to hit.
* @param removeNote True to remove the note immediately, false to make it transparent and let it move offscreen.
*/
@@ -716,6 +825,10 @@ class Strumline extends FlxSpriteGroup
#end
}
/**
* Kill a note heading towards the strumline.
* @param note The note to kill. Gets recycled and reused for performance.
*/
public function killNote(note:NoteSprite):Void
{
if (note == null) return;
@@ -730,41 +843,76 @@ class Strumline extends FlxSpriteGroup
}
}
/**
* Get a strumline note sprite by its index.
* @param index The index of the note to get.
* @return The note.
*/
public function getByIndex(index:Int):StrumlineNote
{
return this.strumlineNotes.members[index];
}
/**
* Get a strumline note sprite by its direction.
* @param direction The direction of the note to get.
* @return The note.
*/
public function getByDirection(direction:NoteDirection):StrumlineNote
{
return getByIndex(DIRECTIONS.indexOf(direction));
}
/**
* Play a static animation for a given direction.
* @param direction The direction of the note to play the static animation for.
*/
public function playStatic(direction:NoteDirection):Void
{
getByDirection(direction).playStatic();
}
/**
* Play a press animation for a given direction.
* @param direction The direction of the note to play the press animation for.
*/
public function playPress(direction:NoteDirection):Void
{
getByDirection(direction).playPress();
}
/**
* Play a confirm animation for a given direction.
* @param direction The direction of the note to play the confirm animation for.
*/
public function playConfirm(direction:NoteDirection):Void
{
getByDirection(direction).playConfirm();
}
/**
* Play a confirm animation for a hold note.
* @param direction The direction of the note to play the confirm animation for.
*/
public function holdConfirm(direction:NoteDirection):Void
{
getByDirection(direction).holdConfirm();
}
/**
* Check if a given direction is playing the confirm animation.
* @param direction The direction of the note to check.
* @return `true` if the note is playing the confirm animation, `false` otherwise.
*/
public function isConfirm(direction:NoteDirection):Bool
{
return getByDirection(direction).isConfirm();
}
/**
* Play a note splash for a given direction.
* @param direction The direction of the note to play the splash animation for.
*/
public function playNoteSplash(direction:NoteDirection):Void
{
if (!showNotesplash) return;
@@ -787,6 +935,10 @@ class Strumline extends FlxSpriteGroup
}
}
/**
* Play a note hold cover for a given hold note.
* @param holdNote The hold note to play the cover animation for.
*/
public function playNoteHoldCover(holdNote:SustainTrail):Void
{
if (!showNotesplash) return;
@@ -817,6 +969,11 @@ class Strumline extends FlxSpriteGroup
}
}
/**
* Build a note sprite for a given note data.
* @param note The note data to build the note sprite for.
* @return The note sprite. Will recycle a note sprite from the pool if available for performance.
*/
public function buildNoteSprite(note:SongNoteData):NoteSprite
{
var noteSprite:NoteSprite = constructNoteSprite();
@@ -833,13 +990,17 @@ class Strumline extends FlxSpriteGroup
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
noteSprite.x -= NUDGE;
// noteSprite.x += INITIAL_OFFSET;
noteSprite.y = -9999;
}
return noteSprite;
}
/**
* Build a hold note sprite for a given note data.
* @param note The note data to build the hold note sprite for.
* @return The hold note sprite. Will recycle a hold note sprite from the pool if available for performance.
*/
public function buildHoldNoteSprite(note:SongNoteData):SustainTrail
{
var holdNoteSprite:SustainTrail = constructHoldNoteSprite();
@@ -874,7 +1035,7 @@ class Strumline extends FlxSpriteGroup
}
/**
* Custom recycling behavior.
* Custom recycling behavior for note splashes.
*/
function constructNoteSplash():NoteSplash
{
@@ -908,7 +1069,7 @@ class Strumline extends FlxSpriteGroup
}
/**
* Custom recycling behavior.
* Custom recycling behavior for note hold covers.
*/
function constructNoteHoldCover():NoteHoldCover
{
@@ -942,7 +1103,7 @@ class Strumline extends FlxSpriteGroup
}
/**
* Custom recycling behavior.
* Custom recycling behavior for note sprites.
*/
function constructNoteSprite():NoteSprite
{
@@ -968,7 +1129,7 @@ class Strumline extends FlxSpriteGroup
}
/**
* Custom recycling behavior.
* Custom recycling behavior for hold note sprites.
*/
function constructHoldNoteSprite():SustainTrail
{
@@ -1020,6 +1181,10 @@ class Strumline extends FlxSpriteGroup
FlxTween.tween(arrow, {y: arrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * index)});
}
/**
* Play a fade in animation on all arrows in the strumline.
* Used when starting a song in Freeplay mode.
*/
public function fadeInArrows():Void
{
for (index => arrow in this.strumlineNotes.members.keyValueIterator())
@@ -1028,24 +1193,50 @@ class Strumline extends FlxSpriteGroup
}
}
/**
* Compare two note data objects by their strumtime.
* @param order The order to sort the notes in.
* @param a The first note data object.
* @param b The second note data object.
* @return The comparison result, based on the time of the notes.
*/
function compareNoteData(order:Int, a:SongNoteData, b:SongNoteData):Int
{
return FlxSort.byValues(order, a.time, b.time);
}
/**
* Compare two note sprites by their strumtime.
* @param order The order to sort the notes in.
* @param a The first note sprite.
* @param b The second note sprite.
* @return The comparison result, based on the time of the notes.
*/
function compareNoteSprites(order:Int, a:NoteSprite, b:NoteSprite):Int
{
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
}
/**
* Compare two hold note sprites by their strumtime.
* @param order The order to sort the notes in.
* @param a The first hold note sprite.
* @param b The second hold note sprite.
* @return The comparison result, based on the time of the notes.
*/
function compareHoldNoteSprites(order:Int, a:SustainTrail, b:SustainTrail):Int
{
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
}
override function findMinYHelper()
/**
* Find the minimum Y position of the strumline.
* Ignores the background to ensure the strumline is positioned correctly.
* @return The minimum Y position of the strumline.
*/
override function findMinYHelper():Float
{
var value = Math.POSITIVE_INFINITY;
var value:Float = Math.POSITIVE_INFINITY;
for (member in group.members)
{
if (member == null) continue;
@@ -1053,18 +1244,28 @@ class Strumline extends FlxSpriteGroup
if (member == this.background) continue;
var minY:Float;
if (member.flixelType == SPRITEGROUP) minY = (cast member : FlxSpriteGroup).findMinY();
if (member.flixelType == SPRITEGROUP)
{
minY = (cast member : FlxSpriteGroup).findMinY();
}
else
{
minY = member.y;
}
if (minY < value) value = minY;
}
return value;
}
override function findMaxYHelper()
/**
* Find the maximum Y position of the strumline.
* Ignores the background to ensure the strumline is positioned correctly.
* @return The maximum Y position of the strumline.
*/
override function findMaxYHelper():Float
{
var value = Math.NEGATIVE_INFINITY;
var value:Float = Math.NEGATIVE_INFINITY;
for (member in group.members)
{
if (member == null) continue;
@@ -1072,9 +1273,14 @@ class Strumline extends FlxSpriteGroup
if (member == this.background) continue;
var maxY:Float;
if (member.flixelType == SPRITEGROUP) maxY = (cast member : FlxSpriteGroup).findMaxY();
if (member.flixelType == SPRITEGROUP)
{
maxY = (cast member : FlxSpriteGroup).findMaxY();
}
else
{
maxY = member.y + member.height;
}
if (maxY > value) value = maxY;
}
+7 -2
View File
@@ -26,6 +26,11 @@ class StrumlineNote extends FunkinSprite
return this.direction;
}
/**
* The Y Offset of the note.
*/
public var yOffset:Float = 0.0;
/**
* Set this flag to `true` to disable performance optimizations that cause
* the Strumline note sprite to ignore `velocity` and `acceleration`.
@@ -52,8 +57,8 @@ class StrumlineNote extends FunkinSprite
setup(noteStyle);
this.animation.callback = onAnimationFrame;
this.animation.finishCallback = onAnimationFinished;
this.animation.onFrameChange.add(onAnimationFrame);
this.animation.onFinish.add(onAnimationFinished);
// Must be true for animations to play.
this.active = true;
+5
View File
@@ -33,6 +33,11 @@ class SustainTrail extends FlxSprite
public var cover:NoteHoldCover = null;
/**
* The Y Offset of the note.
*/
public var yOffset:Float = 0.0;
/**
* Set to `true` if the user hit the note and is currently holding the sustain.
* Should display associated effects.
@@ -66,6 +66,8 @@ class NoteKind implements INoteScriptedClass
public function onNoteHit(event:HitNoteScriptEvent):Void {}
public function onNoteMiss(event:NoteScriptEvent):Void {}
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
}
/**
@@ -49,7 +49,7 @@ class NoteKindManager
{
var noteEvent:NoteScriptEvent = cast(event, NoteScriptEvent);
var noteKind:NoteKind = noteKinds.get(noteEvent.note.kind);
var noteKind:NoteKind = noteKinds.get(noteEvent?.note?.kind);
if (noteKind != null)
{
@@ -19,11 +19,6 @@ using funkin.data.animation.AnimationData.AnimationDataUtil;
@:nullSafety
class NoteStyle implements IRegistryEntry<NoteStyleData>
{
/**
* The ID of the note style.
*/
public final id:String;
/**
* Note style data as parsed from the JSON file.
*/
+22 -4
View File
@@ -69,6 +69,19 @@ class Scoring
}
}
public static function getMissScore(scoringSystem:ScoringSystem = PBOT1):Int
{
return switch (scoringSystem)
{
case LEGACY: LEGACY_MISS_SCORE;
case WEEK7: WEEK7_MISS_SCORE;
case PBOT1: PBOT1_MISS_SCORE;
default:
FlxG.log.error('Unknown scoring system: ${scoringSystem}');
0;
}
}
/**
* The maximum score a note can receive.
*/
@@ -92,7 +105,7 @@ class Scoring
/**
* The score a note receives when it is missed.
*/
public static final PBOT1_MISS_SCORE:Int = 0;
public static final PBOT1_MISS_SCORE:Int = -100;
/**
* The threshold at which a note hit is considered perfect and always given the max score.
@@ -228,6 +241,11 @@ class Scoring
*/
public static final LEGACY_SHIT_SCORE:Int = 50;
/**
* The score a note receives when missed.
*/
public static final LEGACY_MISS_SCORE:Int = -10;
static function scoreNoteLEGACY(msTiming:Float):Int
{
var absTiming:Float = Math.abs(msTiming);
@@ -276,6 +294,7 @@ class Scoring
public static final WEEK7_BAD_THRESHOLD:Float = 0.8; // 80% of the hit window, or ~125ms
public static final WEEK7_GOOD_THRESHOLD:Float = 0.55; // 55% of the hit window, or ~91ms
public static final WEEK7_SICK_THRESHOLD:Float = 0.2; // 20% of the hit window, or ~33ms
public static final WEEK7_MISS_SCORE:Int = -10;
public static final WEEK7_SHIT_SCORE:Int = 50;
public static final WEEK7_BAD_SCORE:Int = 100;
public static final WEEK7_GOOD_SCORE:Int = 200;
@@ -363,10 +382,9 @@ class Scoring
// Else, use the standard grades
// Grade % (only good and sick), 1.00 is a full combo
// Final Grade = (Sick + Good - Miss) / (Total Notes)
var grade = (scoreData.tallies.sick + scoreData.tallies.good) / scoreData.tallies.totalNotes;
// Clear % (including bad and shit). 1.00 is a full clear but not a full combo
var clear = (scoreData.tallies.totalNotesHit) / scoreData.tallies.totalNotes;
if (grade == Constants.RANK_PERFECT_THRESHOLD)
{
+12 -20
View File
@@ -65,19 +65,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
*/
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
/**
* The internal ID of the song.
*/
public final id:String;
/**
* Song metadata as parsed from the JSON file.
* This is the data for the `default` variation specifically,
* and is needed for the IRegistryEntry interface.
* Will only be null if the song data could not be loaded.
*/
public final _data:Null<SongMetadata>;
// key = variation id, value = metadata
final _metadata:Map<String, SongMetadata>;
@@ -289,6 +276,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
return diff.album ?? '';
}
public function getStickerPackId(diffId:String, variation:String):Null<String>
{
var diff:Null<SongDifficulty> = getDifficulty(diffId, variation);
if (diff == null) return null;
return diff.stickerPack;
}
/**
* Populate the difficulty data from the provided metadata.
* Does not load chart data (that is triggered later when we want to play the song).
@@ -331,6 +326,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
difficulty.difficultyRating = metadata.playData.ratings.get(diffId) ?? 0;
difficulty.album = metadata.playData.album;
difficulty.stickerPack = metadata.playData.stickerPack;
difficulty.stage = metadata.playData.stage;
difficulty.noteStyle = metadata.playData.noteStyle;
@@ -624,13 +620,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
}
}
public function toString():String
{
return 'Song($id)';
}
public function destroy():Void {}
public function onPause(event:PauseScriptEvent):Void {};
public function onResume(event:ScriptEvent):Void {};
@@ -651,6 +640,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
public function onNoteMiss(event:NoteScriptEvent):Void {};
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void {};
public function onSongEvent(event:SongEventScriptEvent):Void {};
@@ -750,6 +741,7 @@ class SongDifficulty
public var difficultyRating:Int = 0;
public var album:Null<String> = null;
public var stickerPack:Null<String> = null;
public function new(song:Song, diffId:String, variation:String)
{
+4 -2
View File
@@ -107,8 +107,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
if (this.animation != null)
{
this.animation.callback = this.onAnimationFrame;
this.animation.finishCallback = this.onAnimationFinished;
this.animation.onFrameChange.add(this.onAnimationFrame);
this.animation.onFinish.add(this.onAnimationFinished);
}
}
@@ -371,6 +371,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
public function onNoteHit(event:HitNoteScriptEvent) {}
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
public function onNoteMiss(event:NoteScriptEvent) {}
public function onSongEvent(event:SongEventScriptEvent) {}
+2 -14
View File
@@ -30,10 +30,6 @@ typedef StagePropGroup = FlxTypedSpriteGroup<StageProp>;
*/
class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements IRegistryEntry<StageData>
{
public final id:String;
public final _data:StageData;
public var stageName(get, never):String;
function get_stageName():String
@@ -859,16 +855,6 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
}
}
public override function toString():String
{
return 'Stage($id)';
}
static function _fetchData(id:String):Null<StageData>
{
return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id));
}
public function onScriptEvent(event:ScriptEvent)
{
// Ensure all custom events get broadcast to the elements of the stage.
@@ -901,6 +887,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
public function onNoteMiss(event:NoteScriptEvent) {}
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
public function onSongEvent(event:SongEventScriptEvent) {}
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
+14 -6
View File
@@ -43,7 +43,7 @@ class Save
{
if (_instance == null)
{
return _instance = load();
return load();
}
return _instance;
}
@@ -55,7 +55,10 @@ class Save
trace("[SAVE] Loading save...");
// Bind save data.
return loadFromSlot(BASE_SAVE_SLOT);
var loadedSave:Save = loadFromSlot(BASE_SAVE_SLOT);
if (_instance == null) _instance = loadedSave;
return loadedSave;
}
public static function clearData():Void
@@ -112,6 +115,7 @@ class Save
zoomCamera: true,
debugDisplay: false,
autoPause: true,
vsyncMode: lime.ui.WindowVSyncMode.OFF,
strumlineBackgroundOpacity: 0,
autoFullscreen: false,
inputOffset: 0,
@@ -730,10 +734,6 @@ class Save
// Directly set the highscore.
setSongScore(songId, difficultyId, newScoreData);
#if FEATURE_NEWGROUNDS
Leaderboards.submitSongScore(songId, difficultyId, newScoreData.score);
#end
return;
}
@@ -987,6 +987,7 @@ class Save
* If you set slot to `2`, it will load an independent save file from slot 2.
* @param slot
*/
@:haxe.warning("-WDeprecated")
static function loadFromSlot(slot:Int):Save
{
trace('[SAVE] Loading save from slot $slot...');
@@ -1128,6 +1129,7 @@ class Save
* @param slot The slot number to check.
* @return Whether the slot is not empty.
*/
@:haxe.warning("-WDeprecated")
static function querySlot(slot:Int):Bool
{
var targetSaveData = new FlxSave();
@@ -1397,6 +1399,12 @@ typedef SaveDataOptions =
*/
var autoPause:Bool;
/**
* If enabled, the game will utilize VSync (or adaptive VSync) on startup.
* @default `OFF`
*/
var vsyncMode:lime.ui.WindowVSyncMode;
/**
* If >0, the game will display a semi-opaque background under the notes.
* `0` for no background, `100` for solid black if you're freaky like that
+4 -2
View File
@@ -18,6 +18,7 @@ import funkin.input.Controls;
* MusicBeatState actually represents the core utility FlxState of the game.
* It includes functionality for event handling, as well as maintaining BPM-based update events.
*/
@:nullSafety
class MusicBeatState extends FlxTransitionableState implements IEventHandler
{
var controls(get, never):Controls;
@@ -25,8 +26,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
inline function get_controls():Controls
return PlayerSettings.player1.controls;
public var leftWatermarkText:FlxText = null;
public var rightWatermarkText:FlxText = null;
public var leftWatermarkText:Null<FlxText> = null;
public var rightWatermarkText:Null<FlxText> = null;
public var conductorInUse(get, set):Conductor;
@@ -163,6 +164,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
}
@:nullSafety(Off)
override function startOutro(onComplete:() -> Void):Void
{
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
+4 -2
View File
@@ -16,10 +16,11 @@ import funkin.input.Controls;
/**
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
*/
@:nullSafety
class MusicBeatSubState extends FlxSubState implements IEventHandler
{
public var leftWatermarkText:FlxText = null;
public var rightWatermarkText:FlxText = null;
public var leftWatermarkText:Null<FlxText> = null;
public var rightWatermarkText:Null<FlxText> = null;
public var conductorInUse(get, set):Conductor;
@@ -186,6 +187,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
this._parentState.openSubState(substate);
}
@:nullSafety(Off)
override function startOutro(onComplete:() -> Void):Void
{
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
+2 -1
View File
@@ -6,6 +6,7 @@ import funkin.graphics.FlxFilteredSprite;
* The icon that gets used for Freeplay capsules and char select
* NOT to be confused with the CharIcon class, which is for the in-game icons
*/
@:nullSafety
class PixelatedIcon extends FlxFilteredSprite
{
public function new(x:Float, y:Float)
@@ -28,7 +29,7 @@ class PixelatedIcon extends FlxFilteredSprite
charPath += "monsterpixel";
case "mom" | "mom-car":
charPath += "mommypixel";
case "pico-blazin" | "pico-playable" | "pico-speaker":
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
charPath += "picopixel";
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
charPath += "gfpixel";
+3
View File
@@ -3,6 +3,7 @@ package funkin.ui;
import funkin.ui.AtlasText;
import funkin.ui.MenuList;
@:nullSafety
class TextMenuList extends MenuTypedList<TextMenuItem>
{
public function new(navControls:NavControls = Vertical, ?wrapMode)
@@ -18,6 +19,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
}
}
@:nullSafety
class TextMenuItem extends TextTypedMenuItem<AtlasText>
{
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void)
@@ -27,6 +29,7 @@ class TextMenuItem extends TextTypedMenuItem<AtlasText>
}
}
@:nullSafety
class TextTypedMenuItem<T:AtlasText> extends MenuTypedItem<T>
{
public function new(x = 0.0, y = 0.0, label:T, name:String, ?callback:Void->Void)
@@ -54,20 +54,6 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
doFade(animInInfo);
default:
}
#if FEATURE_DEBUG_FUNCTIONS
if (FlxG.keys.justPressed.J)
{
alpha = 1;
x = y = 0;
fadingStatus = FADE_OUT;
}
if (FlxG.keys.justPressed.K)
{
alpha = 0;
fadingStatus = FADE_IN;
}
#end
}
public function onStepHit(event:SongTimeScriptEvent):Void {}
@@ -425,6 +425,7 @@ class CharSelectSubState extends MusicBeatSubState
else
{
#if FEATURE_NEWGROUNDS
// Make the character unlock medal retroactive.
if (availableChars.size() > 1) Medals.award(CharSelect);
#end
@@ -497,7 +498,7 @@ class CharSelectSubState extends MusicBeatSubState
grpIcons.scrollFactor.set();
}
function unLock()
function unLock():Void
{
var index = nonLocks[0];
@@ -570,6 +571,7 @@ class CharSelectSubState extends MusicBeatSubState
});
#if FEATURE_NEWGROUNDS
// Grant the medal when the player unlocks a character.
Medals.award(CharSelect);
#end
+4 -11
View File
@@ -18,9 +18,6 @@ class Nametag extends FlxSprite
shader = mosaicShader;
switchChar("bf");
FlxG.debugger.addTrackerProfile(new TrackerProfile(Nametag, ["midpointX", "midpointY"]));
FlxG.debugger.track(this, "Nametag");
}
public function updatePosition():Void
@@ -71,16 +68,12 @@ class Nametag extends FlxSprite
}
}
function setBlockTimer(frame:Int, ?forceX:Float, ?forceY:Float)
function setBlockTimer(frame:Int, ?forceX:Float, ?forceY:Float):Void
{
var daX:Float = 10 * FlxG.random.int(1, 4);
var daY:Float = 10 * FlxG.random.int(1, 4);
var daX:Float = forceX ?? 10 * FlxG.random.int(1, 4);
var daY:Float = forceY ?? 10 * FlxG.random.int(1, 4);
if (forceX != null) daX = forceX;
if (forceY != null) daY = forceY;
new FlxTimer().start(frame / 30, _ -> {
FlxTimer.wait(frame / 30, () -> {
mosaicShader.setBlockSize(daX, daY);
});
}
+3 -1
View File
@@ -60,7 +60,9 @@ class DebugMenuSubState extends MusicBeatSubState
#if FEATURE_STAGE_EDITOR
createItem("STAGE EDITOR", openStageEditor);
#end
#if FEATURE_RESULTS_DEBUG
createItem("RESULTS SCREEN DEBUG", openTestResultsScreen);
#end
// createItem("Input Offset Testing", openInputOffsetTesting);
// createItem("CHARACTER SELECT", openCharSelect, true);
// createItem("TEST STICKERS", testStickers);
@@ -121,7 +123,7 @@ class DebugMenuSubState extends MusicBeatSubState
function testStickers()
{
openSubState(new funkin.ui.transition.StickerSubState({}));
openSubState(new funkin.ui.transition.stickers.StickerSubState({}));
trace('opened stickers');
}
@@ -528,8 +528,11 @@ class DebugBoundingState extends FlxState
trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
offsetAnimationDropdown.onChange = function(event:UIEvent) {
trace('Selected animation ${event?.data?.id}');
playCharacterAnimation(event.data.id, true);
if (event.data != null)
{
trace('Selected animation ${event.data.id}');
playCharacterAnimation(event.data.id, true);
}
}
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
@@ -22,9 +22,9 @@ class FlxAnimateTest extends MusicBeatState
{
super.create();
sprite = new FlxAtlasSprite(0, 0, 'assets/images/charSelect/maskTest');
sprite = new FlxAtlasSprite(-100, 0, 'week3:assets/week3/images/philly/erect/picoFlowers');
add(sprite);
sprite.playAnimation(null, false, false, true);
sprite.playAnimation(sprite.anim.stageInstance.symbol.name, false, false, true);
}
public override function update(elapsed:Float):Void
@@ -2361,7 +2361,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
}
if (!FileUtil.doesFileExist(chartPath))
if (!FileUtil.fileExists(chartPath))
{
trace('Previously loaded chart file (${chartPath.toString()}) does not exist, disabling link...');
menuItemRecentChart.disabled = true;
@@ -5118,6 +5118,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
var songPosString:String = '${songPosMinutes}:${songPosSeconds}.${songPosMilliseconds}';
var roundedBeat = FlxMath.roundDecimal(Conductor.instance.currentBeatTime, 2);
var parts:Array<String> = Std.string(roundedBeat).split('.');
if (parts.length == 1) parts.push('00');
else if (parts[1].length < 2) parts[1] += '0';
songPosString += ' | Beat: ${parts.join('.')} | Step: ${Conductor.instance.currentStep}';
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
var songRemaining:Float = Math.max(songLengthInMs - songPos, 0.0);
@@ -106,7 +106,7 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
}
}
if (!FileUtil.doesFileExist(chartPath))
if (!FileUtil.fileExists(chartPath))
{
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
linkRecentChart.disabled = true;
@@ -55,6 +55,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
}
var eventType:String = event.data.id;
var sameEvent:Bool = (eventType == chartEditorState.eventKindToPlace);
trace('ChartEditorEventDataToolbox - Event type changed: $eventType');
@@ -69,6 +70,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
return;
}
if (!sameEvent) chartEditorState.eventDataToPlace = {};
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace);
if (!_initializing && chartEditorState.currentEventSelection.length > 0)
@@ -84,15 +86,21 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
chartEditorState.notePreviewDirty = true;
}
}
toolboxEventsEventKind.pauseEvent(UIEvent.CHANGE, true);
var startingEventValue = ChartEditorDropdowns.populateDropdownWithSongEvents(toolboxEventsEventKind, chartEditorState.eventKindToPlace);
trace('ChartEditorEventDataToolbox - Starting event kind: ${startingEventValue}');
toolboxEventsEventKind.value = startingEventValue;
toolboxEventsEventKind.resumeEvent(UIEvent.CHANGE, true, true);
}
public override function refresh():Void
{
super.refresh();
toolboxEventsEventKind.pauseEvent(UIEvent.CHANGE, true);
var newDropdownElement = ChartEditorDropdowns.findDropdownElement(chartEditorState.eventKindToPlace, toolboxEventsEventKind);
if (newDropdownElement == null)
@@ -151,6 +159,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
}
}
}
toolboxEventsEventKind.resumeEvent(UIEvent.CHANGE, true, true);
}
var lastEventKind:String = 'unknown';
@@ -165,8 +175,6 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
// Clear the frame.
target.removeAllComponents();
chartEditorState.eventDataToPlace = {};
for (field in schema)
{
if (field == null) continue;
@@ -259,7 +267,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
var value = event.target.value;
if (field.type == ENUM)
{
value = event.target.value.value;
var drp:DropDown = cast event.target;
value = drp.selectedItem?.value ?? field.defaultValue;
}
else if (field.type == BOOL)
{
@@ -54,7 +54,7 @@ class ResultsDebugSubState extends MusicBeatSubState
FlxG.switchState(() -> new funkin.play.ResultState(resultsParams));
});
});
createToggleListItem("Character", PlayerRegistry.instance.listBaseGamePlayerIds(), function(result:String) {
createToggleListItem("Character", PlayerRegistry.instance.listBaseGameEntryIds(), function(result:String) {
resultsParams.characterId = result;
});
createToggleListItem("Results Mode", ["Debug", "Story", "Freeplay"], function(result:String) {
@@ -125,7 +125,10 @@ class ResultsDebugSubState extends MusicBeatSubState
songId: "cum",
characterId: "bf",
difficultyId: "nightmare",
variationId: "erect",
isNewHighscore: true,
isPracticeMode: true, // Invalidates achievements/scores.
isBotPlayMode: true, // Invalidates achievements/scores.
scoreData:
{
score: 1_234_567,
@@ -1073,7 +1073,7 @@ class StageEditorState extends UIState
var filePath = new haxe.io.Path(file);
var item = new MenuItem();
item.text = filePath.file + "." + filePath.ext;
item.disabled = !FileUtil.doesFileExist(file);
item.disabled = !FileUtil.fileExists(file);
var load = function(file:String) {
currentFile = file;
@@ -32,7 +32,7 @@ class BackupAvailableDialog extends Dialog
{
super();
if (!FileUtil.doesFileExist(filePath)) return;
if (!FileUtil.fileExists(filePath)) return;
// time text
var fileDate = Path.withoutExtension(Path.withoutDirectory(filePath));
@@ -62,7 +62,7 @@ class BackupAvailableDialog extends Dialog
}
buttonOpenBackup.onClick = function(_) {
if (FileUtil.doesFileExist(filePath) && state.welcomeDialog != null) // doing a check in case a sleezy FUCK decides to delete the backup file AFTER dialog opens
if (FileUtil.fileExists(filePath) && state.welcomeDialog != null) // doing a check in case a sleezy FUCK decides to delete the backup file AFTER dialog opens
{
state.welcomeDialog.loadFromFilePath(filePath);
}
@@ -35,7 +35,7 @@ class WelcomeDialog extends Dialog
{
trace(file);
if (!FileUtil.doesFileExist(file)) continue; // whats the point of loading something that doesnt exist
if (!FileUtil.fileExists(file)) continue; // whats the point of loading something that doesnt exist
var patj = new haxe.io.Path(file);
@@ -56,7 +56,7 @@ class WelcomeDialog extends Dialog
boxDrag.onClick = function(_) FileUtil.browseForSaveFile([FileUtil.FILE_FILTER_FNFS], loadFromFilePath, null, null, "Open Stage Data");
var defaultStages = StageRegistry.instance.listBaseGameStageIds();
var defaultStages:Array<String> = StageRegistry.instance.listBaseGameEntryIds();
defaultStages.sort(funkin.util.SortUtil.alphabetically);
for (stage in defaultStages)
-22
View File
@@ -11,16 +11,6 @@ import flixel.graphics.FlxGraphic;
*/
class Album implements IRegistryEntry<AlbumData>
{
/**
* The internal ID for this album.
*/
public final id:String;
/**
* The full data for an album.
*/
public final _data:AlbumData;
public function new(id:String)
{
this.id = id;
@@ -93,16 +83,4 @@ class Album implements IRegistryEntry<AlbumData>
{
return _data.albumTitleAnimations;
}
public function toString():String
{
return 'Album($id)';
}
public function destroy():Void {}
static function _fetchData(id:String):Null<AlbumData>
{
return AlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id));
}
}
-6
View File
@@ -91,20 +91,16 @@ class AlbumRoll extends FlxSpriteGroup
if (albumId == null)
{
this.visible = false;
difficultyStars.stars.visible = false;
return;
}
else
{
this.visible = true;
}
albumData = AlbumRegistry.instance.fetchEntry(albumId);
if (albumData == null)
{
FlxG.log.warn('Could not find album data for album ID: ${albumId}');
return;
};
@@ -113,9 +109,7 @@ class AlbumRoll extends FlxSpriteGroup
newAlbumArt.replaceFrameGraphic(0, albumGraphic);
buildAlbumTitle(albumData.getAlbumTitleAssetKey(), albumData.getAlbumTitleOffsets());
applyExitMovers();
refresh();
}

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