Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90eabf0b38 | ||
|
|
b3959a5935 | ||
|
|
cdca3bf307 | ||
|
|
e491c5e841 | ||
|
|
f9b125e44a | ||
|
|
1ceb4fad1a | ||
|
|
21fc59c997 | ||
|
|
0cbde53f86 | ||
|
|
5119cf1b72 | ||
|
|
927f3fc300 | ||
|
|
d1e4a17f46 | ||
|
|
ec868f8ef5 | ||
|
|
1e0a238463 | ||
|
|
e07e59a288 | ||
|
|
d6410d5e42 | ||
|
|
5974c1a482 | ||
|
|
ffdcbdbf21 | ||
|
|
3b27ec5b66 | ||
|
|
56a56dbfaa | ||
|
|
537bd5dd83 | ||
|
|
b0fd471f8a | ||
|
|
3633df3fdc | ||
|
|
e6234397f5 | ||
|
|
68ce5ce90b | ||
|
|
a6066f9905 | ||
|
|
75506f71a9 | ||
|
|
c4b5462a5a | ||
|
|
47d4d77c86 | ||
|
|
acf17f6165 | ||
|
|
2db26070c6 | ||
|
|
e0f9c7336b | ||
|
|
a28a28ae4c | ||
|
|
7160f748eb | ||
|
|
c8e60bed34 | ||
|
|
11c826daa6 | ||
|
|
6c887b4632 | ||
|
|
ecfb897dae | ||
|
|
56d5767004 | ||
|
|
7d3bfa32ca | ||
|
|
348647f486 | ||
|
|
48ed659800 | ||
|
|
8aafe62f9c | ||
|
|
0f7859bfb6 | ||
|
|
c3d3d2e547 | ||
|
|
37dc3461af | ||
|
|
c11ac1f658 | ||
|
|
47bbf67ae6 | ||
|
|
528e9e4f03 | ||
|
|
7626700495 | ||
|
|
0d2a663346 | ||
|
|
22736b27da | ||
|
|
dcfad7b7e8 | ||
|
|
ef625b3d9d | ||
|
|
6ce3a2e93e | ||
|
|
b8c2854007 | ||
|
|
04fffe8ade | ||
|
|
c37862445f | ||
|
|
0613e9d295 | ||
|
|
bed249dca0 | ||
|
|
139940f9ec | ||
|
|
36e2f47577 | ||
|
|
ce493a03c9 | ||
|
|
aabd1aa9f7 | ||
|
|
89029220e4 | ||
|
|
dca54de087 | ||
|
|
15bef89d71 | ||
|
|
6c1f3800db | ||
|
|
8507f799a1 | ||
|
|
dba2794780 | ||
|
|
48752e444c | ||
|
|
8a1d74b82c | ||
|
|
0d1db6ae9e | ||
|
|
1f1851990f | ||
|
|
67d2a9811b | ||
|
|
a782b3f9e8 | ||
|
|
74e28a18ff | ||
|
|
220636e01e | ||
|
|
4f316003af | ||
|
|
1a04e35db7 | ||
|
|
d3e83dd72d | ||
|
|
c4e2b900c1 | ||
|
|
ac3178118c | ||
|
|
681f7a6d07 | ||
|
|
aa24c94782 | ||
|
|
7075c8af4b | ||
|
|
efc33403b6 | ||
|
|
25a42b8182 | ||
|
|
0389af2202 | ||
|
|
b694ec08d6 | ||
|
|
423ccfab98 | ||
|
|
edc1f7113b | ||
|
|
f0b50de9fa | ||
|
|
0ad2d507ef | ||
|
|
6c13444211 | ||
|
|
5a0b4310a2 | ||
|
|
6b1fc0372b | ||
|
|
cf69125d25 | ||
|
|
84b2d5b48d | ||
|
|
dcb5b3a935 | ||
|
|
011f1541b1 | ||
|
|
13c1bf8ac0 | ||
|
|
1893f2b2f6 | ||
|
|
a0caa41edc | ||
|
|
456b2ff6c4 | ||
|
|
a1d1b1b2d1 | ||
|
|
0db4ce11b7 | ||
|
|
340fd5dcf1 | ||
|
|
d569690a51 | ||
|
|
4a5afa2fc7 | ||
|
|
59c593b812 | ||
|
|
6ba9e00a9f | ||
|
|
ec7bdd32e2 | ||
|
|
939528a9bd | ||
|
|
08b3af959d | ||
|
|
cea54e394c | ||
|
|
e710198afe | ||
|
|
bf3601ea8d | ||
|
|
c5566c8644 | ||
|
|
5944f5b55d | ||
|
|
077fbf6978 | ||
|
|
f02bdc4225 | ||
|
|
8da9ed4ea0 | ||
|
|
5ed4a2ddd0 | ||
|
|
6293e2808b | ||
|
|
c6dc562769 | ||
|
|
a607396d04 | ||
|
|
365f8dcfea | ||
|
|
b3eb709f4e | ||
|
|
fcbce0a66b | ||
|
|
05032e3126 | ||
|
|
b8a7189e24 | ||
|
|
881aba3b03 | ||
|
|
5480774afe | ||
|
|
d62c8af20c | ||
|
|
7d0644f3c8 | ||
|
|
fe21c0addd | ||
|
|
219143c02d | ||
|
|
03930a29c3 | ||
|
|
5d378cd9f2 | ||
|
|
e5db402025 | ||
|
|
d348ef3127 | ||
|
|
9301c0c7dd | ||
|
|
02ee8e64c8 | ||
|
|
40eab2e461 | ||
|
|
d6a65ca681 | ||
|
|
f8841bab35 | ||
|
|
dc23a116dd | ||
|
|
024994e85a | ||
|
|
9be1633320 | ||
|
|
3b15873fc7 | ||
|
|
786f19c57d | ||
|
|
ccb874e97a | ||
|
|
f7fd7a57c3 | ||
|
|
23f79aef58 | ||
|
|
2c394cc636 | ||
|
|
d7a0139e4f | ||
|
|
562d23005d | ||
|
|
671c152463 | ||
|
|
3ce7c7939f | ||
|
|
28450ccfc6 |
@@ -48,19 +48,19 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Mobile Device Model
|
label: Mobile Device Model
|
||||||
description: (Mobile users only) What mobile device are you playing on?
|
description: (Mobile users only) What mobile device are you playing on?
|
||||||
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
|
placeholder: ex. iPhone 17, Galaxy S26, iPad 11th Gen
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Mobile OS Version
|
label: Mobile OS Version
|
||||||
description: (Mobile users only) What version is your Operating System?
|
description: (Mobile users only) What version is your Operating System?
|
||||||
placeholder: ex. iOS 26.3, Android 16, iPadOS 26.3
|
placeholder: ex. iOS 26.4, Android 16, iPadOS 26.4
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.8.3
|
placeholder: ex. 0.8.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.8.3
|
placeholder: ex. 0.8.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Location
|
label: Location
|
||||||
description: Where did you find the issue(s)? Include the name of the song, the variation, the difficulty, the time/section of the song, and any images or videos.
|
description: Where did you find the issue(s)? Include the name of the song, the variation, the difficulty, the time/section of the song, and any images or videos.
|
||||||
placeholder: ex. Cocoa Erect on Erect/Nightmare difficulties at Section 30
|
placeholder: ex. Cocoa Erect [Nightmare] at Section 30, step 5
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
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.
|
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.8.3
|
placeholder: ex. 0.8.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -49,19 +49,19 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Mobile Device Model
|
label: Mobile Device Model
|
||||||
description: (Mobile users only) What mobile device are you playing on?
|
description: (Mobile users only) What mobile device are you playing on?
|
||||||
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
|
placeholder: ex. iPhone 17, Galaxy S26, iPad 11th Gen
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Mobile OS Version
|
label: Mobile OS Version
|
||||||
description: (Mobile users only) What version is your Operating System?
|
description: (Mobile users only) What version is your Operating System?
|
||||||
placeholder: ex. iOS 26.3, Android 16, iPadOS 26.3
|
placeholder: ex. iOS 26.4, Android 16, iPadOS 26.4
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.8.3
|
placeholder: ex. 0.8.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-2
@@ -72,7 +72,6 @@
|
|||||||
"haxecheckstyle.externalSourceRoots": [],
|
"haxecheckstyle.externalSourceRoots": [],
|
||||||
"haxecheckstyle.configurationFile": "checkstyle.json",
|
"haxecheckstyle.configurationFile": "checkstyle.json",
|
||||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||||
"lime.projectFile": "project.hxp",
|
|
||||||
"lime.targets": [
|
"lime.targets": [
|
||||||
{ "name": "windows", "enabled": true, "label": "Windows" },
|
{ "name": "windows", "enabled": true, "label": "Windows" },
|
||||||
{ "name": "mac", "enabled": true, "label": "macOS" },
|
{ "name": "mac", "enabled": true, "label": "macOS" },
|
||||||
@@ -80,8 +79,8 @@
|
|||||||
{ "name": "html5", "enabled": true, "label": "HTML5" },
|
{ "name": "html5", "enabled": true, "label": "HTML5" },
|
||||||
{ "name": "android", "enabled": true, "label": "Android" },
|
{ "name": "android", "enabled": true, "label": "Android" },
|
||||||
{ "name": "ios", "enabled": true, "label": "iOS" },
|
{ "name": "ios", "enabled": true, "label": "iOS" },
|
||||||
|
{ "name": "hl", "enabled": true, "label": "HashLink" },
|
||||||
// Disabled targets
|
// Disabled targets
|
||||||
{ "name": "hl", "enabled": false, "label": "HashLink" },
|
|
||||||
{ "name": "air", "enabled": false },
|
{ "name": "air", "enabled": false },
|
||||||
{ "name": "electron", "enabled": false },
|
{ "name": "electron", "enabled": false },
|
||||||
{ "name": "flash", "enabled": false },
|
{ "name": "flash", "enabled": false },
|
||||||
|
|||||||
+126
@@ -4,6 +4,132 @@ All notable changes will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.8.4] - 2026-03-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added several new features to Polymod:
|
||||||
|
- Added FULL support for abstracts! (Previously, only `FlxColor` was supported)
|
||||||
|
- Added support for getting the code of a single character string (i.e `’h’.code`) (Thanks NotHyper-474!)
|
||||||
|
- Added support for import defaults (`import.hx`/`import.hxc`), allowing scripted classes with the same package to share imports. (Thanks KoloInDaCrib!)
|
||||||
|
- Added `scriptInit` and `listScriptClasses` functions to scripted classes. (Thanks KoloInDaCrib!)
|
||||||
|
- `.hx` files can now be parsed. (Thanks Starexify!)
|
||||||
|
- Added support for preprocessing (`#if`/`#else` syntax), allowing for platform-specific code to be implemented in an easier way. (Thanks KoloInDaCrib!)
|
||||||
|
- Added support for mod IDs. (Thanks Starexify!)
|
||||||
|
- Implemented `_merge` support for scripts. (Thanks KoloInDaCrib!)
|
||||||
|
- Please refer to the Cookbook to learn how to use this.
|
||||||
|
- Added widescreen scale properties to the Zoom Camera event in the Chart Editor to account for widescreen resolutions.
|
||||||
|
- Added a flipY field for Story Mode level props. ([48ed659](https://github.com/FunkinCrew/Funkin/commit/48ed65980005366371fda7a66c04f73bb5b86200)) - by @ExtraCode75 in [#6340](https://github.com/FunkinCrew/Funkin/pull/6340)
|
||||||
|
- Implemented working start and end times for Freeplay song previews. ([40eab2e](https://github.com/FunkinCrew/Funkin/commit/40eab2e4618ebb6dc67a06ef67df817920773c56)) - by @ADA-Funni in [#6725](https://github.com/FunkinCrew/Funkin/pull/6725)
|
||||||
|
- Added a new albumOSTName property to Freeplay album data to make the "OFFICIAL OST" text moddable. ([67d2a98](https://github.com/FunkinCrew/Funkin/commit/67d2a9811b5c92409bdc76e287c984d5777d8621)) - by @NotHyper-474 in [#6578](https://github.com/FunkinCrew/Funkin/pull/6578)
|
||||||
|
- Added an optional 'capsule' field to level data to display any week label on Freeplay capsules. ([528e9e4](https://github.com/FunkinCrew/Funkin/commit/528e9e4f03c40e59f3932d8b4c522b6eeb66783b)) - by @Trayfellow in [#7149](https://github.com/FunkinCrew/Funkin/pull/7149)
|
||||||
|
- Implemented ScriptedBaseFreeplayDJ, allowing modders to customize how custom Freeplay DJs are rendered. ([423ccfa](https://github.com/FunkinCrew/Funkin/commit/423ccfab98a29646e4998dacedaf403319d956ff)) - by @PurSnake in [#6806](https://github.com/FunkinCrew/Funkin/pull/6806)
|
||||||
|
- Added a new FunkinGroup class to make groups of sprites easier to modify. ([4f31600](https://github.com/FunkinCrew/Funkin/commit/4f316003af9a6ca46c77bf4971059e20543cbd7b)) - by @MightyTheArmiddilo in [#6758](https://github.com/FunkinCrew/Funkin/pull/6758)
|
||||||
|
- Added `Reflect.makeVarArgs` to allow modders to create functions with a variable number of arguments. ([3633df3](https://github.com/FunkinCrew/Funkin/commit/3633df3fdc6dc0af4f9886fcb1e1d4a8d2b8d5aa)) - by @Starexify in [#6738](https://github.com/FunkinCrew/Funkin/pull/6738)
|
||||||
|
- Added script event support for song events. ([15bef89](https://github.com/FunkinCrew/Funkin/commit/15bef89d71982c5685f4ce6f6d7e6e4d8321508c)) - by @KoloInDaCrib in [#4130](https://github.com/FunkinCrew/Funkin/pull/4130)
|
||||||
|
- Added a new Chart Editor event type: Set Target Bop Speed. ([1893f2b](https://github.com/FunkinCrew/Funkin/commit/1893f2b2f679d8a4d262052793dde24e94719ee6)) - by @roma-perec-bp in [#6769](https://github.com/FunkinCrew/Funkin/pull/6769)
|
||||||
|
- Added shortcuts to select all notes above or below the playhead in the Chart Editor. ([08b3af9](https://github.com/FunkinCrew/Funkin/commit/08b3af959d6657511bfe8418da545731050cb24a)) - by @Lasercar in [#4306](https://github.com/FunkinCrew/Funkin/pull/4306)
|
||||||
|
- Added the ability to view and edit the timing of a note or event through the Chart Editor context menu. ([a782b3f](https://github.com/FunkinCrew/Funkin/commit/a782b3f9e8498634babb864341fd1437bb2c01f6)) - by @Lasercar in [#4200](https://github.com/FunkinCrew/Funkin/pull/4200)
|
||||||
|
- Added an "Edit Note" option to the Chart Editor context menu that opens the Notes window. ([e710198](https://github.com/FunkinCrew/Funkin/commit/e710198afec9a0fe19effe4cd196ab3a247f4784)) - by @Honton129 in [#7010](https://github.com/FunkinCrew/Funkin/pull/7010)
|
||||||
|
- Added the ability to Ctrl-click on health icons in the Chart Editor to open character previews. ([011f154](https://github.com/FunkinCrew/Funkin/commit/011f1541b197fd4f962ab8a910cbd417f78c2d09)) - by @Trofem in [#7089](https://github.com/FunkinCrew/Funkin/pull/7089)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgraded the input handling system from SDL2 to SDL3!
|
||||||
|
- Added an “Unlocked Framerate” preference to Desktop platforms.
|
||||||
|
- The input system is now much more responsive and accurate down to the nanosecond.
|
||||||
|
- The game now properly supports more controllers and input devices.
|
||||||
|
- The game now shows popups when installed mods have issues, such as when the API version is incorrect.
|
||||||
|
- Unfocusing the game now reduces the game’s volume to 25% (down from 50%).
|
||||||
|
- The notekinds dropdown are no longer hardcoded in the Chart Editor.
|
||||||
|
- The Character Select key is now hidden in the Controls menu until a new character is unlocked. ([6c13444](https://github.com/FunkinCrew/Funkin/commit/6c134442118d3f5f61b9e3faa631512d210422fb)) - by @Lasercar in [#4882](https://github.com/FunkinCrew/Funkin/pull/4882)
|
||||||
|
- The Story Mode level score now displays with commas. ([04fffe8](https://github.com/FunkinCrew/Funkin/commit/04fffe8ade4c4b236d59a236f08534e8eede179f)) - by @JVNpixels in [#4965](https://github.com/FunkinCrew/Funkin/pull/4965)
|
||||||
|
- Freeplay capsule week labels are now softcoded and rendered using a font. ([48752e4](https://github.com/FunkinCrew/Funkin/commit/48752e444cc5f1b8cd0a3b579f843e9ab6580c46)) - by @KoloInDaCrib in [#5249](https://github.com/FunkinCrew/Funkin/pull/5249)
|
||||||
|
- The Freeplay change character hint now changes when a gamepad is connected. ([0f7859b](https://github.com/FunkinCrew/Funkin/commit/0f7859bfb6b829290804efab9d5c67fbdbec0903)) - by @MrMadera in [#6609](https://github.com/FunkinCrew/Funkin/pull/6609)
|
||||||
|
- Moved the Winter Horrorland intro cutscene logic into a script. ([9bbe73b](https://github.com/FunkinCrew/funkin.assets/commit/9bbe73b21ccbea29a5d5187709f91b4822bf7e31)) - by @mario64thane in [funkin.assets#63](https://github.com/FunkinCrew/funkin.assets/pull/63)
|
||||||
|
- Merged FlxFilteredSprite functionality into FunkinSprite. ([22736b2](https://github.com/FunkinCrew/Funkin/commit/22736b27da7d8de8b11ab6cc6c2a622e5579dccd)) - by @TechnikTil in [#7114](https://github.com/FunkinCrew/Funkin/pull/7114)
|
||||||
|
- Adjusted some wording in the Chart/Stage Editor backup window to be clearer. ([a607396](https://github.com/FunkinCrew/Funkin/commit/a607396d04f24388691ee23bd04022681ca54756)) - by @Marcosayfi in [#7071](https://github.com/FunkinCrew/Funkin/pull/7071)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Conjoined string interpolation `$a$b` now parses properly in Polymod.
|
||||||
|
- Video cutscenes are no longer muted at volumes of 3 or lower.
|
||||||
|
- Video cutscenes no longer occasionally show glitches at the beginning.
|
||||||
|
- [MOBILE] Loading a cloud save from Newgrounds no longer carries over the volume level from Desktop.
|
||||||
|
- [HTML5] Scrolling the game out of view now pauses the game instead of allowing gameplay to continue processing.
|
||||||
|
- The Boyfriend Freeplay DJ new rank animation now transitions smoothly back to its idle animation.
|
||||||
|
- Hold notes now score much more consistently across different framerate values.
|
||||||
|
- The bricks in Week 7 stages are now better quality.
|
||||||
|
- Trimmed out the silent beat at the beginning of Ugh Erect.
|
||||||
|
- Camera bopping no longer stops at zoom values of 1.35x or higher.
|
||||||
|
- Charting many hold notes in a row no longer breaks audio handling.
|
||||||
|
- Adding collapsible frames in event schemas no longer crashes the game.
|
||||||
|
- Some event properties have been grouped under Advanced frames.
|
||||||
|
- Chart Editor dropdowns no longer refocus when a tooltip appears.
|
||||||
|
- Focus and Zoom Camera event durations now account for playback speed.
|
||||||
|
- [WINDOWS] Exiting the game no longer leaves it running as a background process. ([881aba3](https://github.com/FunkinCrew/Funkin/commit/881aba3b03a26764374441cab244cd2d360a30b9)) - by @AnimatingLegend in [#6885](https://github.com/FunkinCrew/Funkin/pull/6885)
|
||||||
|
- Story Mode now only shows difficulties that all of a level's songs have. ([365f8dc](https://github.com/FunkinCrew/Funkin/commit/365f8dcfea9569f2edba1ebfceb3d49819ad2b57)) - by @Starexify in [#7046](https://github.com/FunkinCrew/Funkin/pull/7046)
|
||||||
|
- The Girlfriend Story Mode prop now bops continuously when scrolling through levels. ([84b2d5b](https://github.com/FunkinCrew/Funkin/commit/84b2d5b48d9eb76c918fdba6987ccc1f5da76133)) - by @Redar13 in [#6830](https://github.com/FunkinCrew/Funkin/pull/6830)
|
||||||
|
- The Freeplay new rank slam animation now properly plays its glow effect. ([fcbce0a](https://github.com/FunkinCrew/Funkin/commit/fcbce0a66b75b0c1224c6a0e2907c170b71998ad)) - by @FuroYT in [#7015](https://github.com/FunkinCrew/Funkin/pull/7015)
|
||||||
|
- The Freeplay Random capsule no longer displays BPM and difficulty numbers. ([0ad2d50](https://github.com/FunkinCrew/Funkin/commit/0ad2d507efa9dcb577184e28bfd04091b3424bfb)) - by @Lasercar in [#4927](https://github.com/FunkinCrew/Funkin/pull/4927)
|
||||||
|
- Favoriting a song in Freeplay no longer breaks capsule text cycling. ([ce493a0](https://github.com/FunkinCrew/Funkin/commit/ce493a03c9ffb4c43c2c27885ed0fab0c1c3531d)) - by @VirtuGuy in [#7134](https://github.com/FunkinCrew/Funkin/pull/7134)
|
||||||
|
- Freeplay songs with invalid pixel icons no longer duplicate the icon from the previous song in the list. ([b3eb709](https://github.com/FunkinCrew/Funkin/commit/b3eb709f4ed535ed2544f3bb530ff3724e07be3f)) - by @FuroYT in [#7006](https://github.com/FunkinCrew/Funkin/pull/7006)
|
||||||
|
- A Freeplay clear percent of 100% now properly exits to the right when returning to the Main Menu. ([5974c1a](https://github.com/FunkinCrew/Funkin/commit/5974c1a48293f3185c5f249acb9130ebd6b35ae4)) - by @Trayfellow in [#7183](https://github.com/FunkinCrew/Funkin/pull/7183)
|
||||||
|
- Nudged the Freeplay backing image down by one pixel to align with the bottom of the screen. ([b8c2854](https://github.com/FunkinCrew/Funkin/commit/b8c2854007f36ac7a2b6897eead3f979e11f0ad7)) - by @SuperEric1 in [#6893](https://github.com/FunkinCrew/Funkin/pull/6893)
|
||||||
|
- Improved performance for songs that use a lot of events. ([3b27ec5](https://github.com/FunkinCrew/Funkin/commit/3b27ec5b66b2fa00d59a77f6ec07aff68e440860)) - by @KoloInDaCrib in [#7171](https://github.com/FunkinCrew/Funkin/pull/7171)
|
||||||
|
- Adjusted hit window logic to accurately reflect threshold values for all judgments. ([dcb5b3a](https://github.com/FunkinCrew/Funkin/commit/dcb5b3a9358a9bb126153b57aa2d38dc0684c39d)) - by @Rudyrue in [#7104](https://github.com/FunkinCrew/Funkin/pull/7104)
|
||||||
|
- Adjusted offsets for all health icons to be more consistent. ([83d8134](https://github.com/FunkinCrew/funkin.assets/commit/83d8134f9d0b3cf3b59e9e1d972fa1e200a98ba1)) - by @LeGooeyy in [funkin.assets#278](https://github.com/FunkinCrew/funkin.assets/pull/278)
|
||||||
|
- Hold notes no longer shift upward when hit early. ([d62c8af](https://github.com/FunkinCrew/Funkin/commit/d62c8af20c90ec3d6e4274aba8ddbfb378ab0256)) - by @VirtuGuy in [#7060](https://github.com/FunkinCrew/Funkin/pull/7060)
|
||||||
|
- The opponent no longer stops singing upon missing if Boyfriend has no vocals file. ([cea54e3](https://github.com/FunkinCrew/Funkin/commit/cea54e394c78544dd0ec0339721faaac140caa65)) - by @FuroYT in [#7007](https://github.com/FunkinCrew/Funkin/pull/7007)
|
||||||
|
- onPause script functions now properly trigger when pausing during a cutscene or dialogue. ([5a0b431](https://github.com/FunkinCrew/Funkin/commit/5a0b4310a2c39b033989e0e36b8f456bdac65a5b)) - by @MightyTheArmiddilo in [#6804](https://github.com/FunkinCrew/Funkin/pull/6804)
|
||||||
|
- Camera events now consistently pause when the Pause Menu is opened. ([e623439](https://github.com/FunkinCrew/Funkin/commit/e6234397f57d2af37035a0ff2504b8241e23949d)) - by @VirtuGuy in [#7145](https://github.com/FunkinCrew/Funkin/pull/7145)
|
||||||
|
- The Pause Menu global offset value now adjusts at the same rate across all framerates. ([348647f](https://github.com/FunkinCrew/Funkin/commit/348647f48629a24ee3bd2b759bbfc1bff168fb81)) - by @KolzeYT in [#7147](https://github.com/FunkinCrew/Funkin/pull/7147)
|
||||||
|
- Spirit's trail effect now renders consistently across all framerates. ([75506f7](https://github.com/FunkinCrew/Funkin/commit/75506f71a97d468f516a0232c8be5560afb0682e)) - by @KolzeYT in [#7179](https://github.com/FunkinCrew/Funkin/pull/7179)
|
||||||
|
- The sound tray now appears consistently and plays sound effects at the right volume. ([2b9906d](https://github.com/FunkinCrew/Funkin/commit/2b9906df29660057410d3054e897012896896d6a)) - by @Raltyro in [#7068](https://github.com/FunkinCrew/Funkin/pull/7068)
|
||||||
|
- The sticker transition now properly pauses when losing focus with Pause on Unfocus enabled. ([8a1d74b](https://github.com/FunkinCrew/Funkin/commit/8a1d74b82cceab97e777dc49e7a2117b8d27e59b)) - by @ComedyLost in [#7129](https://github.com/FunkinCrew/Funkin/pull/7129)
|
||||||
|
- [HTML5] Rimlight alt masks now render properly. ([0613e9d](https://github.com/FunkinCrew/Funkin/commit/0613e9d295179e66d30db3d2f6805870eac847d6)) - by @VirtuGuy in [#6900](https://github.com/FunkinCrew/Funkin/pull/6900)
|
||||||
|
- The Week 2 Erect stage lightning strike fade now pauses and cancels appropriately. ([cd05267](https://github.com/FunkinCrew/funkin.assets/commit/cd05267023859d04cc8920c787b8280682e5f0f5)) - by @ComedyLost in [funkin.assets#390](https://github.com/FunkinCrew/funkin.assets/pull/390)
|
||||||
|
- A-Bot (Dark)'s frame no longer renders as completely dark in Week 2 Erect. ([a142530](https://github.com/FunkinCrew/funkin.assets/commit/a142530cae6198ca17b560ff73e36d50f1582768)) - by @ComedyLost in [funkin.assets#368](https://github.com/FunkinCrew/funkin.assets/pull/368)
|
||||||
|
- Dying on the same frame as a lightning strike in Week 2 Erect no longer throws a script error. ([ef89bde](https://github.com/FunkinCrew/funkin.assets/commit/ef89bde19c397f15a53b0fda2f64ba93ff96f56f)) - by @KoloInDaCrib in [funkin.assets#363](https://github.com/FunkinCrew/funkin.assets/pull/363)
|
||||||
|
- Pico (Dark) and Nene (Dark) no longer throw script errors in debug editors. ([b16ce44](https://github.com/FunkinCrew/funkin.assets/commit/b16ce44beb43d4511c61418c824929fb4dfabb63)) - by @ComedyLost in [funkin.assets#367](https://github.com/FunkinCrew/funkin.assets/pull/367)
|
||||||
|
- Girlfriend's hair no longer falls when restarting the song in a Week 3 stage. ([ade80ab](https://github.com/FunkinCrew/funkin.assets/commit/ade80ab72915ddd47724c380bb41d6df4c1b79bd)) - by @AnimatingLegend in [funkin.assets#370](https://github.com/FunkinCrew/funkin.assets/pull/370)
|
||||||
|
- The camera transition to Results no longer reveals the edges of stage art in the Week 6 Erect stage. ([ecf4c54](https://github.com/FunkinCrew/funkin.assets/commit/ecf4c5458a2e1fadba7db31c3272cea485d22b2f)) - by @ComedyLost in [funkin.assets#383](https://github.com/FunkinCrew/funkin.assets/pull/383)
|
||||||
|
- Fine-tuned offsets for pixel characters. ([8b3c268](https://github.com/FunkinCrew/funkin.assets/commit/8b3c2680a6a966e81b62598e783c60f46355c759)) - by @CEliuxJV in [funkin.assets#376](https://github.com/FunkinCrew/funkin.assets/pull/376)
|
||||||
|
- Tankman's game over lines now properly play during game overs in Ugh (Pico Mix) and Guns (Pico Mix). ([b900208](https://github.com/FunkinCrew/funkin.assets/commit/b900208143f980adee25d70db247e0b176dd659c)) - by @hucks5 in [funkin.assets#373](https://github.com/FunkinCrew/funkin.assets/pull/373)
|
||||||
|
- Restarting during the mid-song animation in Stress (Pico Mix) now properly resets the characters' animations. ([2ca92de](https://github.com/FunkinCrew/funkin.assets/commit/2ca92de46eced3f53d305579704fcb7e8c051f3a)) - by @requazar in [funkin.assets#364](https://github.com/FunkinCrew/funkin.assets/pull/364)
|
||||||
|
- The rain shader now properly fades out with the transition to the Results Screen. ([a0caa41](https://github.com/FunkinCrew/Funkin/commit/a0caa41edcb26298e4f8c243a5eed0b4fe93fcf7)) - by @VirtuGuy in [#6776](https://github.com/FunkinCrew/Funkin/pull/6776)
|
||||||
|
- Exiting a Chart Editor playtest while using the pixel notestyle no longer crashes the game. ([6c1f380](https://github.com/FunkinCrew/Funkin/commit/6c1f3800dbc9b13fb4b409abce53c1dafc9bbe8f)) - by @Starexify and @KoloInDaCrib in [#7108](https://github.com/FunkinCrew/Funkin/pull/7108)
|
||||||
|
- Chart Editor Edit buttons are now appropriately disabled when nothing is selected. ([6b1fc03](https://github.com/FunkinCrew/Funkin/commit/6b1fc0372bca202d80f4ccf316580b13a5a80879)) - by @Lasercar in [#4386](https://github.com/FunkinCrew/Funkin/pull/4386)
|
||||||
|
- Undoing a Chart Editor event now plays the undo sound. ([f7fd7a5](https://github.com/FunkinCrew/Funkin/commit/f7fd7a57c36f0461f56c2ab627a17d752cb82320)) - by @VirtuGuy in [#6963](https://github.com/FunkinCrew/Funkin/pull/6963)
|
||||||
|
- The playback speed audio setting no longer rounds incorrectly upon reopening the Chart Editor. ([3b15873](https://github.com/FunkinCrew/Funkin/commit/3b15873fc7d0c20840c313f1d2cfed91014c664f)) - by @AnimatingLegend in [#7023](https://github.com/FunkinCrew/Funkin/pull/7023)
|
||||||
|
- The Chart Editor easing preview graph now properly displays tweens that exceed start/end values. ([7626700](https://github.com/FunkinCrew/Funkin/commit/7626700495743862caddbf50a7dafef51529a2a6)) - by @echolotl in [#7152](https://github.com/FunkinCrew/Funkin/pull/7152)
|
||||||
|
- The Chart Editor playbar head is no longer draggable during a playtest. ([ccb874e](https://github.com/FunkinCrew/Funkin/commit/ccb874e97a121859c26a8a71b394743d27f6ed37)) - by @ComedyLost in [#6987](https://github.com/FunkinCrew/Funkin/pull/6987)
|
||||||
|
- The Results Screen no longer shows up after a Chart Editor playtest with Bot Play enabled. ([786f19c](https://github.com/FunkinCrew/Funkin/commit/786f19c57d999dba9309cae28d912b080968670d)) - by @ComedyLost in [#7022](https://github.com/FunkinCrew/Funkin/pull/7022)
|
||||||
|
- The Osu!Mania Chart Editor importer no longer accepts non-Osu!Mania beatmaps. ([456b2ff](https://github.com/FunkinCrew/Funkin/commit/456b2ff6c4a127733120cef3d03d6102bdd7522f)) - by @FuroYT in [#6624](https://github.com/FunkinCrew/Funkin/pull/6624)
|
||||||
|
- Characters in the Stage Editor no longer fly away when clicking and dragging them. ([0389af2](https://github.com/FunkinCrew/Funkin/commit/0389af2202aa4dc6a21ce9b7eea95944bfaa6946)) - by @KoloInDaCrib in [#7002](https://github.com/FunkinCrew/Funkin/pull/7002)
|
||||||
|
- Characters with alternating idle animations now properly play danceLeft when pressing Space in the Animation Editor. ([05032e3](https://github.com/FunkinCrew/Funkin/commit/05032e3126eda78a2bbf70e8c975bcb04eed201b)) - by @ComedyLost in [#7056](https://github.com/FunkinCrew/Funkin/pull/7056)
|
||||||
|
- [LINUX] Paths are now properly sanitized as they are on other platforms. ([219143c](https://github.com/FunkinCrew/Funkin/commit/219143c02dcf65e68aec80701a82023ca0d70db6)) - by @NotHyper-474 in [#6395](https://github.com/FunkinCrew/Funkin/pull/6395)
|
||||||
|
- OpenFL and some Lime classes are now accessible to modders. ([56a56db](https://github.com/FunkinCrew/Funkin/commit/56a56dbfaa3f62a3f684df49c682f69a496e45f4)) - by @FuroYT in [#7115](https://github.com/FunkinCrew/Funkin/pull/7115)
|
||||||
|
- Sandboxed API classes can now be accessed by mods. ([03930a2](https://github.com/FunkinCrew/Funkin/commit/03930a29c3d8aa6a310147a7131c6b7cddefd475)) - by @MightyTheArmiddilo in [#6952](https://github.com/FunkinCrew/Funkin/pull/6952)
|
||||||
|
- Blacklisted some more classes for security.
|
||||||
|
- And even more fixes!
|
||||||
|
|
||||||
|
## New Contributors for 0.8.4
|
||||||
|
|
||||||
|
* @roma-perec-bp made their first contribution in [#6769](https://github.com/FunkinCrew/Funkin/pull/6769)
|
||||||
|
* @Redar13 made their first contribution in [#6830](https://github.com/FunkinCrew/Funkin/pull/6830)
|
||||||
|
* @SuperEric1 made their first contribution in [#6893](https://github.com/FunkinCrew/Funkin/pull/6893)
|
||||||
|
* @AnimatingLegend made their first contribution in [#7023](https://github.com/FunkinCrew/Funkin/pull/7023)
|
||||||
|
* @Marcosayfi made their first contribution in [#7071](https://github.com/FunkinCrew/Funkin/pull/7071)
|
||||||
|
* @Rudyrue made their first contribution in [#7104](https://github.com/FunkinCrew/Funkin/pull/7104)
|
||||||
|
* @KolzeYT made their first contribution in [#7147](https://github.com/FunkinCrew/Funkin/pull/7147)
|
||||||
|
* @echolotl made their first contribution in [#7152](https://github.com/FunkinCrew/Funkin/pull/7152)
|
||||||
|
* @mario64thane made their first contribution in [funkin.assets#63](https://github.com/FunkinCrew/funkin.assets/pull/63)
|
||||||
|
* @LeGooeyy made their first contribution in [funkin.assets#278](https://github.com/FunkinCrew/funkin.assets/pull/278)
|
||||||
|
* @requazar made their first contribution in [funkin.assets#364](https://github.com/FunkinCrew/funkin.assets/pull/364)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.3] - 2026-02-23
|
## [0.8.3] - 2026-02-23
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
Submodule assets updated: cd36d5bfcb...d1d027d474
@@ -198,6 +198,8 @@
|
|||||||
"assets/week1/images/erect/orangeLight.png",
|
"assets/week1/images/erect/orangeLight.png",
|
||||||
"assets/week2/images/erect/stairsDark.png",
|
"assets/week2/images/erect/stairsDark.png",
|
||||||
"assets/week2/images/erect/stairsLight.png",
|
"assets/week2/images/erect/stairsLight.png",
|
||||||
"assets/week6/"
|
"assets/week6/",
|
||||||
|
"assets/week7/images/bricksGround.png",
|
||||||
|
"assets/week7/images/erect/bricksGround.png"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ This section provides guidelines to follow when [opening an issue](https://githu
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Make sure you're playing:
|
Make sure you're playing:
|
||||||
- the latest version of the game (currently v0.8.3)
|
- the latest version of the game (currently v0.8.4)
|
||||||
- without any mods
|
- without any mods
|
||||||
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ Most of this functionality is only available on debug builds of the game!
|
|||||||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
- `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!
|
- `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!
|
||||||
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||||
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace. (Only works in the Main Menu on debug builds).
|
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||||
|
|
||||||
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
|
||||||
|
|
||||||
## **Play State**
|
## **Play State**
|
||||||
- `H`: ***HIDE UI***: Makes the user interface invisible. Works in Pause Menu, great for screenshots.
|
- `H`: ***HIDE UI***: Makes the user interface invisible. Works in Pause Menu, great for screenshots.
|
||||||
@@ -27,6 +25,7 @@ Most of this functionality is only available on debug builds of the game!
|
|||||||
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
|
- `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.
|
- `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.
|
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
|
||||||
|
- `CTRL-ACCEPT`: ***MIRRORED MODE***: Start a song with mirrored notes enabled.
|
||||||
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
||||||
|
|
||||||
## **Title State**
|
## **Title State**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"name": "FlxPartialSound",
|
"name": "FlxPartialSound",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "26ed20b9c181a576f4cbe18ccdea38912c01476e",
|
"ref": "2f984e244f1544ca98c0e03b9b21ae570f07ac55",
|
||||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"name": "extension-admob",
|
"name": "extension-admob",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "4c69bb43f1fb89fd6003dc98d2f3a9d75c5364b6",
|
"ref": "5c908b3f7c74115a8b52eddf1e05525f4873f26e",
|
||||||
"url": "https://github.com/FunkinCrew/extension-admob"
|
"url": "https://github.com/FunkinCrew/extension-admob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -52,14 +52,14 @@
|
|||||||
"name": "flixel",
|
"name": "flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "c8e79d2a42f9c11c639519ef2a6fd7ef2379a8aa",
|
"ref": "f7b94eebf7dbb452a929d0c67ab31a9cbd71d3a0",
|
||||||
"url": "https://github.com/FunkinCrew/flixel"
|
"url": "https://github.com/FunkinCrew/flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-addons",
|
"name": "flixel-addons",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "6fa30b3f5209146c852c25f3d1003e08898083e2",
|
"ref": "187f93b34f93c6a405d634a42913c745e443463a",
|
||||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "088b153b35985f8e4419ef710057ebd5b3749513",
|
"ref": "09f2512564b45e92229e21433e6dd482391f7b7d",
|
||||||
"url": "https://github.com/FunkinCrew/haxeui-core"
|
"url": "https://github.com/FunkinCrew/haxeui-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
"name": "hxcpp",
|
"name": "hxcpp",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "5932340d095a7eea8635fe4d1355f1c0efd0b3c2",
|
"ref": "450d112e50acff57b1bc9d584dcf1374c9e33995",
|
||||||
"url": "https://github.com/FunkinCrew/hxcpp"
|
"url": "https://github.com/FunkinCrew/hxcpp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hxvlc",
|
"name": "hxvlc",
|
||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
"version": "2.2.5"
|
"version": "2.2.6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "json2object",
|
"name": "json2object",
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "cae0c0bc10915f6281625d07e5527f3cecb4347f",
|
"ref": "58dd49b018729f86600f719a98728ffb024724e3",
|
||||||
"url": "https://github.com/FunkinCrew/lime"
|
"url": "https://github.com/FunkinCrew/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -183,14 +183,14 @@
|
|||||||
"name": "openfl",
|
"name": "openfl",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "5399946b68fd98f345d6b3e52b95ed6b78bf427c",
|
"ref": "88534506595a32c3f02b21b3987e789a24074ae7",
|
||||||
"url": "https://github.com/FunkinCrew/openfl"
|
"url": "https://github.com/FunkinCrew/openfl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "polymod",
|
"name": "polymod",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "5d47a5c7c6b4e0cb94bd8fd45d012ca93bde9ab7",
|
"ref": "fb123758b90127f5647727727f273d27e93cabdb",
|
||||||
"url": "https://github.com/FunkinCrew/polymod"
|
"url": "https://github.com/FunkinCrew/polymod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+26
-36
@@ -32,13 +32,13 @@ class Project extends HXProject
|
|||||||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||||
* You only have to change it here, the rest of the game will query this value.
|
* You only have to change it here, the rest of the game will query this value.
|
||||||
*/
|
*/
|
||||||
static final VERSION:String = "0.8.3";
|
static final VERSION:String = "0.8.4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The build's number and version code.
|
* The build's number and version code.
|
||||||
* Used when publishing the game to mobile app stores. Should increment with each patch.
|
* Used when publishing the game to mobile app stores. Should increment with each patch.
|
||||||
*/
|
*/
|
||||||
static final BUILD_NUMBER:Int = 77;
|
static final BUILD_NUMBER:Int = 85;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The game's name. Used as the default window title.
|
* The game's name. Used as the default window title.
|
||||||
@@ -114,11 +114,7 @@ class Project extends HXProject
|
|||||||
*/
|
*/
|
||||||
static final ANDROID_TARGET_SDK_VERSION:Int = 35;
|
static final ANDROID_TARGET_SDK_VERSION:Int = 35;
|
||||||
|
|
||||||
static final ANDROID_EXTENSIONS:Array<String> = [
|
static final ANDROID_EXTENSIONS:Array<String> = ["funkin.extensions.CallbackUtil", "funkin.extensions.FNFCExtension", "funkin.extensions.AudioSession"];
|
||||||
"funkin.extensions.CallbackUtil",
|
|
||||||
"funkin.extensions.FNFCExtension",
|
|
||||||
"funkin.extensions.AudioSession"
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The team ID to use for the iOS app. Configured in XCode.
|
* The team ID to use for the iOS app. Configured in XCode.
|
||||||
@@ -171,11 +167,7 @@ class Project extends HXProject
|
|||||||
/**
|
/**
|
||||||
* Asset path globs to exclude on platforms with `CENSOR_EXPLETIVES` disabled.
|
* Asset path globs to exclude on platforms with `CENSOR_EXPLETIVES` disabled.
|
||||||
*/
|
*/
|
||||||
static final EXCLUDE_ASSETS_UNCENSORED:Array<String> = [
|
static final EXCLUDE_ASSETS_UNCENSORED:Array<String> = ["stressCutscene-censored.mp4", "stressPicoCutscene-censored.mp4", "darnellCutscene-censored.mp4"];
|
||||||
"stressCutscene-censored.mp4",
|
|
||||||
"stressPicoCutscene-censored.mp4",
|
|
||||||
"darnellCutscene-censored.mp4"
|
|
||||||
];
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ASSET EMBEDDING
|
// ASSET EMBEDDING
|
||||||
@@ -869,10 +861,6 @@ class Project extends HXProject
|
|||||||
// FEATURE FLAGS
|
// FEATURE FLAGS
|
||||||
//
|
//
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
FEATURE_FUNKVIS.apply(this, true);
|
FEATURE_FUNKVIS.apply(this, true);
|
||||||
FEATURE_LOG_TRACE.apply(this, true);
|
FEATURE_LOG_TRACE.apply(this, true);
|
||||||
FEATURE_DEBUG_FILE_LOGGING.apply(this, FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
FEATURE_DEBUG_FILE_LOGGING.apply(this, FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||||
@@ -894,7 +882,7 @@ class Project extends HXProject
|
|||||||
|
|
||||||
// Should be true on desktop, non-tester builds.
|
// Should be true on desktop, non-tester builds.
|
||||||
// We don't want testers to accidentally leak songs to their Discord friends!
|
// We don't want testers to accidentally leak songs to their Discord friends!
|
||||||
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this) && !isHashLink());
|
||||||
|
|
||||||
FEATURE_LOST_FOCUS_VOLUME.apply(this, isDesktop());
|
FEATURE_LOST_FOCUS_VOLUME.apply(this, isDesktop());
|
||||||
|
|
||||||
@@ -1133,6 +1121,13 @@ class Project extends HXProject
|
|||||||
// Ensure all flixel-animate classes are available at runtime.
|
// Ensure all flixel-animate classes are available at runtime.
|
||||||
addHaxeMacro("include('animate')");
|
addHaxeMacro("include('animate')");
|
||||||
|
|
||||||
|
// Ensure all openfl classes are available at runtime.
|
||||||
|
addHaxeMacro("include('openfl')");
|
||||||
|
|
||||||
|
// Ensure all lime classes are available at runtime.
|
||||||
|
// Explicitly ignore tools and internal packages.
|
||||||
|
addHaxeMacro("include('lime', true, ['lime.tools.*', 'lime._internal.*'])");
|
||||||
|
|
||||||
// Ensure all Flixel classes are available at runtime.
|
// Ensure all Flixel classes are available at runtime.
|
||||||
// Explicitly ignore packages which require additional dependencies.
|
// Explicitly ignore packages which require additional dependencies.
|
||||||
addHaxeMacro("include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*', 'flixel.addons.tile.FlxRayCastTilemap' ])");
|
addHaxeMacro("include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*', 'flixel.addons.tile.FlxRayCastTilemap' ])");
|
||||||
@@ -1143,8 +1138,9 @@ class Project extends HXProject
|
|||||||
*/
|
*/
|
||||||
function configureCustomMacros()
|
function configureCustomMacros()
|
||||||
{
|
{
|
||||||
// This macro allows addition of new functionality to existing Flixel. -->
|
// These macros allow additions of new functionality to existing Flixel. -->
|
||||||
addHaxeMacro("addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')");
|
addHaxeMacro("addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')");
|
||||||
|
addHaxeMacro("addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxSprite())', 'flixel.FlxSprite')");
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureOutputDir()
|
function configureOutputDir()
|
||||||
@@ -1228,21 +1224,12 @@ class Project extends HXProject
|
|||||||
intentFilter.addChild(category);
|
intentFilter.addChild(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
final intentFilterDatas:Map<String, Array<String>> = [
|
final intentFilterDatas:Map<String,
|
||||||
"android:mimeType" => ["application/zip"],
|
Array<String>> = ["android:mimeType" => ["application/zip"], // When this is added, it makes the game show up only when it's a FNFC file.
|
||||||
// When this is added, it makes the game show up only when it's a FNFC file.
|
|
||||||
// But it also for some reason make it not show up at all in ZArchiver no matter what the file is...
|
// But it also for some reason make it not show up at all in ZArchiver no matter what the file is...
|
||||||
// "android:host" => ["*"],
|
// "android:host" => ["*"],
|
||||||
"android:scheme" => ["file", "content"],
|
"android:scheme" => ["file", "content"], // I think having all these makes it so the extension check would work even when the files has dots like "epic-song-v2.0.fnfc"
|
||||||
// I think having all these makes it so the extension check would work even when the files has dots like "epic-song-v2.0.fnfc"
|
"android:pathPattern" => [".*\\.fnfc", ".*\\..*\\.fnfc", ".*\\..*\\..*\\.fnfc", ".*\\..*\\..*\\..*\\.fnfc", ".*\\..*\\..*\\..*\\..*\\.fnfc"]];
|
||||||
"android:pathPattern" => [
|
|
||||||
".*\\.fnfc",
|
|
||||||
".*\\..*\\.fnfc",
|
|
||||||
".*\\..*\\..*\\.fnfc",
|
|
||||||
".*\\..*\\..*\\..*\\.fnfc",
|
|
||||||
".*\\..*\\..*\\..*\\..*\\.fnfc"
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
for (key => values in intentFilterDatas)
|
for (key => values in intentFilterDatas)
|
||||||
{
|
{
|
||||||
@@ -1351,12 +1338,12 @@ class Project extends HXProject
|
|||||||
addHaxelib('thx.core'); // General utility library, "the lodash of Haxe"
|
addHaxelib('thx.core'); // General utility library, "the lodash of Haxe"
|
||||||
addHaxelib('thx.semver'); // Version string handling
|
addHaxelib('thx.semver'); // Version string handling
|
||||||
|
|
||||||
if (isDebug())
|
if (isDebug() && isCPP())
|
||||||
{
|
{
|
||||||
addHaxelib('hxcpp-debug-server'); // VSCode debug support
|
addHaxelib('hxcpp-debug-server'); // VSCode debug support
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDesktop() || isMobile() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this))
|
if ((isDesktop() || isMobile()) && FEATURE_VIDEO_PLAYBACK.isEnabled(this))
|
||||||
{
|
{
|
||||||
// hxvlc doesn't function on HashLink or non-desktop platforms
|
// hxvlc doesn't function on HashLink or non-desktop platforms
|
||||||
// It's also unnecessary if video playback is disabled
|
// It's also unnecessary if video playback is disabled
|
||||||
@@ -1422,8 +1409,11 @@ class Project extends HXProject
|
|||||||
|
|
||||||
function configureAssets()
|
function configureAssets()
|
||||||
{
|
{
|
||||||
// Delete the assets folder from the export folder before adding anything.
|
if (!isDisplay())
|
||||||
clearAssets();
|
{
|
||||||
|
// Delete the assets folder from the export folder before adding anything.
|
||||||
|
clearAssets();
|
||||||
|
}
|
||||||
|
|
||||||
var exclude = EXCLUDE_ASSETS.concat(isWeb() ? EXCLUDE_ASSETS_WEB : EXCLUDE_ASSETS_NATIVE);
|
var exclude = EXCLUDE_ASSETS.concat(isWeb() ? EXCLUDE_ASSETS_WEB : EXCLUDE_ASSETS_NATIVE);
|
||||||
var shouldPreload = PRELOAD_ALL.isEnabled(this);
|
var shouldPreload = PRELOAD_ALL.isEnabled(this);
|
||||||
@@ -1535,7 +1525,7 @@ class Project extends HXProject
|
|||||||
var exportPath:Null<String> = app.path ?? "";
|
var exportPath:Null<String> = app.path ?? "";
|
||||||
|
|
||||||
// mac target uses `macos` for the export folder instead of `mac`
|
// mac target uses `macos` for the export folder instead of `mac`
|
||||||
var platform:String = (!isMac() ? Std.string(this.target) : 'macos');
|
var platform:String = isHashLink() ? 'hl' : (!isMac() ? Std.string(this.target) : 'macos');
|
||||||
var basePath:String = Path.join([exportPath, platform, "bin"]);
|
var basePath:String = Path.join([exportPath, platform, "bin"]);
|
||||||
var assetsPath:String = "";
|
var assetsPath:String = "";
|
||||||
|
|
||||||
|
|||||||
+23
-18
@@ -87,6 +87,21 @@ class Main extends Sprite
|
|||||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if (sys && !mobile)
|
||||||
|
// Force-kill the game to prevent background processing.
|
||||||
|
Lib.current.stage.window.onClose.add(function()
|
||||||
|
{
|
||||||
|
trace(' EXITING '.bold().bg_red() + ' Game is exiting, cleaning up resources...');
|
||||||
|
|
||||||
|
#if hxvlc
|
||||||
|
// Clean up VLC threads to prevent memory leaks.
|
||||||
|
hxvlc.util.Handle.dispose();
|
||||||
|
#end
|
||||||
|
|
||||||
|
Sys.exit(0);
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
|
||||||
// Manually crash the game when using a software renderer in order to give a nicer error message.
|
// Manually crash the game when using a software renderer in order to give a nicer error message.
|
||||||
var context = stage.window.context.type;
|
var context = stage.window.context.type;
|
||||||
if (context != WEBGL && context != OPENGL && context != OPENGLES)
|
if (context != WEBGL && context != OPENGL && context != OPENGLES)
|
||||||
@@ -149,20 +164,15 @@ class Main extends Sprite
|
|||||||
});
|
});
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// Don't call anything from the preferences until the save is loaded!
|
|
||||||
#if web
|
|
||||||
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
|
||||||
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
|
||||||
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
|
||||||
#end
|
|
||||||
|
|
||||||
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
|
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
|
||||||
|
|
||||||
// Force a `FunkinCamera` to be the default camera.
|
// Force a `FunkinCamera` to be the default camera.
|
||||||
// This allows the blend mode shader to work everywhere.
|
// This allows the blend mode shader to work everywhere.
|
||||||
untyped FlxG.cameras = new funkin.graphics.FunkinCameraFrontEnd();
|
untyped FlxG.cameras = new funkin.graphics.FunkinCameraFrontEnd();
|
||||||
|
|
||||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash,
|
var framerate:Int = Preferences.unlockedFramerate ? 0 : Preferences.framerate;
|
||||||
|
|
||||||
|
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash,
|
||||||
(FlxG.stage.window.fullscreen || Preferences.autoFullscreen));
|
(FlxG.stage.window.fullscreen || Preferences.autoFullscreen));
|
||||||
|
|
||||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||||
@@ -174,7 +184,7 @@ class Main extends Sprite
|
|||||||
addChild(game);
|
addChild(game);
|
||||||
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
#if !FLX_NO_DEBUG game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil()); #end
|
||||||
funkin.util.macro.ConsoleMacro.init();
|
funkin.util.macro.ConsoleMacro.init();
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@@ -236,14 +246,7 @@ class Main extends Sprite
|
|||||||
function repositionCounters(lerp:Bool):Void
|
function repositionCounters(lerp:Bool):Void
|
||||||
{
|
{
|
||||||
// Calling this so it gets scaled based on the resolution of the game and device's resolution.
|
// Calling this so it gets scaled based on the resolution of the game and device's resolution.
|
||||||
var scale:Float = Math.min(FlxG.stage.stageWidth / FlxG.width, FlxG.stage.stageHeight / FlxG.height);
|
var scale:Float = Math.max(Math.min(FlxG.stage.stageWidth / FlxG.width, FlxG.stage.stageHeight / FlxG.height), 1);
|
||||||
|
|
||||||
#if android
|
|
||||||
scale = Math.max(scale, 1);
|
|
||||||
#else
|
|
||||||
scale = Math.min(scale, 1);
|
|
||||||
#end
|
|
||||||
final thypos:Float = Math.max(FullScreenScaleMode.notchSize.x, 10);
|
|
||||||
|
|
||||||
if (debugDisplay != null)
|
if (debugDisplay != null)
|
||||||
{
|
{
|
||||||
@@ -251,13 +254,15 @@ class Main extends Sprite
|
|||||||
|
|
||||||
if (FlxG.game != null)
|
if (FlxG.game != null)
|
||||||
{
|
{
|
||||||
|
final thypos:Float = Math.max(FullScreenScaleMode.notchSize.x, 10);
|
||||||
|
|
||||||
if (lerp)
|
if (lerp)
|
||||||
{
|
{
|
||||||
debugDisplay.x = flixel.math.FlxMath.lerp(debugDisplay.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
|
debugDisplay.x = flixel.math.FlxMath.lerp(debugDisplay.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
debugDisplay.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
|
debugDisplay.x = FlxG.game.x + thypos;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugDisplay.y = FlxG.game.y + (3 * scale);
|
debugDisplay.y = FlxG.game.y + (3 * scale);
|
||||||
|
|||||||
+4
-2
@@ -2,6 +2,8 @@ package source; // Yeah, I know...
|
|||||||
|
|
||||||
import sys.io.File;
|
import sys.io.File;
|
||||||
|
|
||||||
|
using tools.AnsiUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A script which executes before the game is built.
|
* A script which executes before the game is built.
|
||||||
*/
|
*/
|
||||||
@@ -12,13 +14,13 @@ class Prebuild
|
|||||||
static function main():Void
|
static function main():Void
|
||||||
{
|
{
|
||||||
var start:Float = Sys.time();
|
var start:Float = Sys.time();
|
||||||
// Sys.println('[INFO] Performing pre-build tasks...');
|
// Sys.println(' INFO '.bold().bg_blue() + " Performing pre-build tasks...");
|
||||||
|
|
||||||
saveBuildTime();
|
saveBuildTime();
|
||||||
|
|
||||||
var end:Float = Sys.time();
|
var end:Float = Sys.time();
|
||||||
var duration:Float = end - start;
|
var duration:Float = end - start;
|
||||||
// Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
|
// Sys.println(' INFO '.bold().bg_blue() + ' Finished pre-build tasks in $duration seconds.');
|
||||||
}
|
}
|
||||||
|
|
||||||
static function saveBuildTime():Void
|
static function saveBuildTime():Void
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class InitState extends FlxState
|
|||||||
|
|
||||||
#if mobile
|
#if mobile
|
||||||
// Setup Mobile FNFC launcher.
|
// Setup Mobile FNFC launcher.
|
||||||
funkin.mobile.util.FNFCUtil.init();
|
funkin.mobile.util.FNFCProvider.init();
|
||||||
#end
|
#end
|
||||||
|
|
||||||
SongEventHelper.generateEaseGraphsBitmaps();
|
SongEventHelper.generateEaseGraphsBitmaps();
|
||||||
@@ -311,19 +311,34 @@ class InitState extends FlxState
|
|||||||
#if FEATURE_LOST_FOCUS_VOLUME
|
#if FEATURE_LOST_FOCUS_VOLUME
|
||||||
@:noCompletion var _lastFocusVolume:Null<Float>;
|
@:noCompletion var _lastFocusVolume:Null<Float>;
|
||||||
|
|
||||||
function onLostFocus()
|
function onLostFocus():Void
|
||||||
{
|
{
|
||||||
if (FlxG.sound.muted || FlxG.sound.volume == 0 || FlxG.autoPause) return;
|
if (FlxG.sound.muted || FlxG.sound.volume == 0 || FlxG.autoPause) return;
|
||||||
_lastFocusVolume = FlxG.sound.volume;
|
_lastFocusVolume = FlxG.sound.volume;
|
||||||
FlxG.sound.volume *= Constants.LOST_FOCUS_VOLUME_MULTIPLIER;
|
FlxG.sound.volume *= Constants.LOST_FOCUS_VOLUME_MULTIPLIER;
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
function onGainFocus()
|
function onGainFocus():Void
|
||||||
{
|
{
|
||||||
|
#if !mobile
|
||||||
|
if (Preferences.unlockedFramerate)
|
||||||
|
{
|
||||||
|
FlxG.updateFramerate = 0;
|
||||||
|
FlxG.drawFramerate = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.updateFramerate = Preferences.framerate;
|
||||||
|
FlxG.drawFramerate = Preferences.framerate;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
#if FEATURE_LOST_FOCUS_VOLUME
|
||||||
if (FlxG.sound.muted || FlxG.autoPause) return;
|
if (FlxG.sound.muted || FlxG.autoPause) return;
|
||||||
if (_lastFocusVolume != null) FlxG.sound.volume = _lastFocusVolume;
|
if (_lastFocusVolume != null) FlxG.sound.volume = _lastFocusVolume;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the game.
|
* Start the game.
|
||||||
@@ -411,7 +426,6 @@ class InitState extends FlxState
|
|||||||
function startGameNormally():Void
|
function startGameNormally():Void
|
||||||
{
|
{
|
||||||
var params:CLIParams = CLIUtil.processArgs();
|
var params:CLIParams = CLIUtil.processArgs();
|
||||||
trace('Command line args: ${params}');
|
|
||||||
|
|
||||||
if (params.chart.shouldLoadChart)
|
if (params.chart.shouldLoadChart)
|
||||||
{
|
{
|
||||||
@@ -445,7 +459,7 @@ class InitState extends FlxState
|
|||||||
{
|
{
|
||||||
// FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
// FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||||
#if mobile
|
#if mobile
|
||||||
funkin.mobile.util.FNFCUtil.onFNFCOpen.add(function(fnfcFile:String)
|
funkin.mobile.util.FNFCProvider.onFNFCOpen.add(function(fnfcFile:String)
|
||||||
{
|
{
|
||||||
flixel.tweens.FlxTween.globalManager.clear();
|
flixel.tweens.FlxTween.globalManager.clear();
|
||||||
flixel.util.FlxTimer.globalManager.clear();
|
flixel.util.FlxTimer.globalManager.clear();
|
||||||
@@ -459,7 +473,7 @@ class InitState extends FlxState
|
|||||||
FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile));
|
FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile));
|
||||||
});
|
});
|
||||||
|
|
||||||
final fnfcFile = funkin.mobile.util.FNFCUtil.queryFNFC();
|
final fnfcFile = funkin.mobile.util.FNFCProvider.queryFNFC();
|
||||||
if (fnfcFile != null)
|
if (fnfcFile != null)
|
||||||
{
|
{
|
||||||
trace('launching FNFC from $fnfcFile');
|
trace('launching FNFC from $fnfcFile');
|
||||||
@@ -574,7 +588,7 @@ class InitState extends FlxState
|
|||||||
//
|
//
|
||||||
// FLIXEL DEBUG SETUP
|
// FLIXEL DEBUG SETUP
|
||||||
//
|
//
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
#if (FEATURE_DEBUG_FUNCTIONS && !FLX_NO_DEBUG)
|
||||||
trace('Initializing Flixel debugger...');
|
trace('Initializing Flixel debugger...');
|
||||||
|
|
||||||
#if !debug
|
#if !debug
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class PlayerSettings
|
|||||||
*/
|
*/
|
||||||
function new(id:Int)
|
function new(id:Int)
|
||||||
{
|
{
|
||||||
trace('loading player settings for id: $id');
|
trace('Loading player settings for id: $id');
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.controls = new Controls('player$id', None);
|
this.controls = new Controls('player$id', None);
|
||||||
|
|||||||
@@ -41,12 +41,23 @@ class Preferences
|
|||||||
{
|
{
|
||||||
#if web
|
#if web
|
||||||
return 60;
|
return 60;
|
||||||
|
#elseif mobile
|
||||||
|
var refreshRate:Int = FlxG.stage.window.displayMode.refreshRate;
|
||||||
|
|
||||||
|
if (refreshRate < 60) refreshRate = 60;
|
||||||
|
|
||||||
|
return refreshRate;
|
||||||
#else
|
#else
|
||||||
var save:Save = Save.instance;
|
var save:Save = Save.instance;
|
||||||
save.options.framerate = value;
|
save.options.framerate = value;
|
||||||
Save.system.flush();
|
Save.system.flush();
|
||||||
FlxG.updateFramerate = value;
|
|
||||||
FlxG.drawFramerate = value;
|
if (!unlockedFramerate)
|
||||||
|
{
|
||||||
|
FlxG.updateFramerate = value;
|
||||||
|
FlxG.drawFramerate = value;
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
@@ -69,13 +80,13 @@ class Preferences
|
|||||||
static function set_naughtyness(value:Bool):Bool
|
static function set_naughtyness(value:Bool):Bool
|
||||||
{
|
{
|
||||||
#if NO_FEATURE_NAUGHTYNESS
|
#if NO_FEATURE_NAUGHTYNESS
|
||||||
value = false;
|
return false;
|
||||||
#end
|
#else
|
||||||
|
|
||||||
var save:Save = Save.instance;
|
var save:Save = Save.instance;
|
||||||
save.options.naughtyness = value;
|
save.options.naughtyness = value;
|
||||||
Save.system.flush();
|
Save.system.flush();
|
||||||
return value;
|
return value;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,19 +157,33 @@ class Preferences
|
|||||||
{
|
{
|
||||||
#if NO_FEATURE_DEBUG_DISPLAY
|
#if NO_FEATURE_DEBUG_DISPLAY
|
||||||
return DebugDisplayMode.Off;
|
return DebugDisplayMode.Off;
|
||||||
|
#else
|
||||||
|
#if hl
|
||||||
|
// Account for when debugDisplay used to be a boolean
|
||||||
|
var options:Null<SaveDataOptions> = Save.instance?.options;
|
||||||
|
if (options != null && Std.isOfType(options.debugDisplay, Bool))
|
||||||
|
{
|
||||||
|
var convertedDebugDisplay = cast(options.debugDisplay, Bool) ? DebugDisplayMode.Simple : DebugDisplayMode.Off;
|
||||||
|
options.debugDisplay = convertedDebugDisplay;
|
||||||
|
Save.system.flush();
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
return Save?.instance?.options?.debugDisplay ?? 'Off';
|
return Save?.instance?.options?.debugDisplay ?? 'Off';
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_debugDisplay(value:DebugDisplayMode):DebugDisplayMode
|
static function set_debugDisplay(value:DebugDisplayMode):DebugDisplayMode
|
||||||
{
|
{
|
||||||
|
#if NO_FEATURE_DEBUG_DISPLAY
|
||||||
|
return DebugDisplayMode.Off;
|
||||||
|
#else
|
||||||
if (value != Save.instance.options.debugDisplay) setDebugDisplayMode(value);
|
if (value != Save.instance.options.debugDisplay) setDebugDisplayMode(value);
|
||||||
|
|
||||||
var save = Save.instance;
|
var save = Save.instance;
|
||||||
save.options.debugDisplay = value;
|
save.options.debugDisplay = value;
|
||||||
Save.system.flush();
|
Save.system.flush();
|
||||||
return value;
|
return value;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,18 +278,23 @@ class Preferences
|
|||||||
{
|
{
|
||||||
#if mobile
|
#if mobile
|
||||||
return false;
|
return false;
|
||||||
#end
|
#else
|
||||||
return Save?.instance?.options?.autoPause ?? true;
|
return Save?.instance?.options?.autoPause ?? true;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_autoPause(value:Bool):Bool
|
static function set_autoPause(value:Bool):Bool
|
||||||
{
|
{
|
||||||
|
#if mobile
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
if (value != Save.instance.options.autoPause) FlxG.autoPause = value;
|
if (value != Save.instance.options.autoPause) FlxG.autoPause = value;
|
||||||
|
|
||||||
var save:Save = Save.instance;
|
var save:Save = Save.instance;
|
||||||
save.options.autoPause = value;
|
save.options.autoPause = value;
|
||||||
Save.system.flush();
|
Save.system.flush();
|
||||||
return value;
|
return value;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -314,6 +344,9 @@ class Preferences
|
|||||||
|
|
||||||
static function get_vsyncMode():lime.ui.WindowVSyncMode
|
static function get_vsyncMode():lime.ui.WindowVSyncMode
|
||||||
{
|
{
|
||||||
|
#if (mobile || web)
|
||||||
|
return lime.ui.WindowVSyncMode.OFF;
|
||||||
|
#else
|
||||||
var value = Save?.instance?.options?.vsyncMode ?? "Off";
|
var value = Save?.instance?.options?.vsyncMode ?? "Off";
|
||||||
|
|
||||||
return switch (value)
|
return switch (value)
|
||||||
@@ -327,10 +360,14 @@ class Preferences
|
|||||||
default:
|
default:
|
||||||
lime.ui.WindowVSyncMode.OFF;
|
lime.ui.WindowVSyncMode.OFF;
|
||||||
};
|
};
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_vsyncMode(value:lime.ui.WindowVSyncMode):lime.ui.WindowVSyncMode
|
static function set_vsyncMode(value:lime.ui.WindowVSyncMode):lime.ui.WindowVSyncMode
|
||||||
{
|
{
|
||||||
|
#if (mobile || web)
|
||||||
|
return lime.ui.WindowVSyncMode.OFF;
|
||||||
|
#else
|
||||||
var string;
|
var string;
|
||||||
|
|
||||||
switch (value)
|
switch (value)
|
||||||
@@ -351,51 +388,37 @@ class Preferences
|
|||||||
save.options.vsyncMode = string;
|
save.options.vsyncMode = string;
|
||||||
Save.system.flush();
|
Save.system.flush();
|
||||||
return value;
|
return value;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var unlockedFramerate(get, set):Bool;
|
public static var unlockedFramerate(get, set):Bool;
|
||||||
|
|
||||||
static function get_unlockedFramerate():Bool
|
static function get_unlockedFramerate():Bool
|
||||||
{
|
{
|
||||||
|
#if (mobile || web)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
return Save?.instance?.options?.unlockedFramerate ?? false;
|
return Save?.instance?.options?.unlockedFramerate ?? false;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_unlockedFramerate(value:Bool):Bool
|
static function set_unlockedFramerate(value:Bool):Bool
|
||||||
{
|
{
|
||||||
|
#if (mobile || web)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
if (value != Save.instance.options.unlockedFramerate)
|
if (value != Save.instance.options.unlockedFramerate)
|
||||||
{
|
{
|
||||||
#if web
|
|
||||||
toggleFramerateCap(value);
|
toggleFramerateCap(value);
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var save:Save = Save.instance;
|
var save:Save = Save.instance;
|
||||||
save.options.unlockedFramerate = value;
|
save.options.unlockedFramerate = value;
|
||||||
Save.system.flush();
|
Save.system.flush();
|
||||||
return value;
|
return value;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
#if web
|
|
||||||
// We create a haxe version of this just for readability.
|
|
||||||
// We use these to override `window.requestAnimationFrame` in Javascript to uncap the framerate / "animation" request rate
|
|
||||||
// Javascript is crazy since u can just do stuff like that lol
|
|
||||||
|
|
||||||
public static function unlockedFramerateFunction(callback, element)
|
|
||||||
{
|
|
||||||
var currTime = Date.now().getTime();
|
|
||||||
var timeToCall = 0;
|
|
||||||
var id = js.Browser.window.setTimeout(function()
|
|
||||||
{
|
|
||||||
callback(currTime + timeToCall);
|
|
||||||
}, timeToCall);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lime already implements their own little framerate cap, so we can just use that
|
|
||||||
// This also gets set in the init function in Main.hx, since we need to definitely override it
|
|
||||||
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If >0, the game will display a semi-opaque background under the notes.
|
* 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
|
* `0` for no background, `100` for solid black if you're freaky like that
|
||||||
@@ -485,9 +508,7 @@ class Preferences
|
|||||||
setDebugDisplayMode(Preferences.debugDisplay);
|
setDebugDisplayMode(Preferences.debugDisplay);
|
||||||
setDebugDisplayBGOpacity(Preferences.debugDisplayBGOpacity / 100);
|
setDebugDisplayBGOpacity(Preferences.debugDisplayBGOpacity / 100);
|
||||||
|
|
||||||
#if web
|
|
||||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||||
#end
|
|
||||||
|
|
||||||
#if mobile
|
#if mobile
|
||||||
// Apply the allowScreenTimeout setting.
|
// Apply the allowScreenTimeout setting.
|
||||||
@@ -497,9 +518,9 @@ class Preferences
|
|||||||
|
|
||||||
static function toggleFramerateCap(unlocked:Bool):Void
|
static function toggleFramerateCap(unlocked:Bool):Void
|
||||||
{
|
{
|
||||||
#if web
|
#if !(mobile || web)
|
||||||
var framerateFunction = unlocked ? unlockedFramerateFunction : lockedFramerateFunction;
|
FlxG.drawFramerate = unlocked ? 0 : framerate;
|
||||||
untyped js.Syntax.code("window.requestAnimationFrame = framerateFunction;");
|
FlxG.updateFramerate = unlocked ? 0 : framerate;
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ import funkin.audio.waveform.WaveformData;
|
|||||||
@:nullSafety
|
@:nullSafety
|
||||||
class VoicesGroup extends SoundGroup
|
class VoicesGroup extends SoundGroup
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Whenever or not the game is using the legacy vocals system (shared Voices.ogg)
|
||||||
|
*/
|
||||||
|
public var legacyVoiceSystem:Bool = false;
|
||||||
|
|
||||||
|
public var legacyVoiceUsesPlayer:Bool = false;
|
||||||
|
|
||||||
var playerVoices:Null<FlxTypedGroup<FunkinSound>>;
|
var playerVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||||
var opponentVoices:Null<FlxTypedGroup<FunkinSound>>;
|
var opponentVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ package funkin.audio.waveform;
|
|||||||
@:nullSafety
|
@:nullSafety
|
||||||
class WaveformDataParser
|
class WaveformDataParser
|
||||||
{
|
{
|
||||||
|
@:native("int16_max")
|
||||||
static final INT16_MAX:Int = 32767;
|
static final INT16_MAX:Int = 32767;
|
||||||
|
@:native("int16_min")
|
||||||
static final INT16_MIN:Int = -32768;
|
static final INT16_MIN:Int = -32768;
|
||||||
|
|
||||||
|
@:native("int8_max")
|
||||||
static final INT8_MAX:Int = 127;
|
static final INT8_MAX:Int = 127;
|
||||||
|
@:native("int8_min")
|
||||||
static final INT8_MIN:Int = -128;
|
static final INT8_MIN:Int = -128;
|
||||||
|
|
||||||
public static function interpretFlxSound(sound:Null<flixel.sound.FlxSound>):Null<WaveformData>
|
public static function interpretFlxSound(sound:Null<flixel.sound.FlxSound>):Null<WaveformData>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class DataError
|
|||||||
case TEnum(c):
|
case TEnum(c):
|
||||||
trace(' ERROR '.error() + '(${Type.getEnumName(c)}) ${e.toString()}');
|
trace(' ERROR '.error() + '(${Type.getEnumName(c)}) ${e.toString()}');
|
||||||
default:
|
default:
|
||||||
trace(' ERROR '.error() + '(${Type.typeof(e)}) ${e.toString()}');
|
trace(' ERROR '.error() + '(${Type.typeof(e)}) ${Std.string(e)}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package funkin.data.event;
|
|||||||
|
|
||||||
import flixel.util.FlxSort;
|
import flixel.util.FlxSort;
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
import funkin.play.event.ScriptedSongEvent;
|
import funkin.play.event.ScriptedSongEvent;
|
||||||
import funkin.play.event.SongEvent;
|
import funkin.play.event.SongEvent;
|
||||||
import funkin.util.macro.ClassMacro;
|
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
|
import funkin.util.macro.ClassMacro;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class statically handles the parsing of internal and scripted song event handlers.
|
* This class statically handles the parsing of internal and scripted song event handlers.
|
||||||
@@ -133,24 +135,38 @@ class SongEventRegistry
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Caching the index for the next event to query greatly reduces lag.
|
||||||
|
* Kinda nasty that it's tied to a static class though.
|
||||||
|
*/
|
||||||
|
static var nextEventIndex:Int = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the list of events to activate this frame.
|
||||||
|
*
|
||||||
* @param events The list of available song events.
|
* @param events The list of available song events.
|
||||||
* @param currentTime The current time in milliseconds.
|
* @param currentTime The current time in milliseconds.
|
||||||
|
* @param startIndex The index to start querying from.
|
||||||
|
* Defaults to the index of the last event handled.
|
||||||
* @return The list of events which haven't been handled yet.
|
* @return The list of events which haven't been handled yet.
|
||||||
*/
|
*/
|
||||||
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
|
public static function queryEvents(events:Array<SongEventData>, currentTime:Float, ?startIndex:Int):Array<SongEventData>
|
||||||
{
|
{
|
||||||
var result:Array<SongEventData> = events.filter(function(event:SongEventData):Bool
|
startIndex ??= nextEventIndex;
|
||||||
|
|
||||||
|
var result:Array<SongEventData> = [];
|
||||||
|
|
||||||
|
for (i in startIndex...events.length)
|
||||||
{
|
{
|
||||||
// If the event is already activated, don't activate it again.
|
if (events[i].activated) continue;
|
||||||
if (event.activated) return false;
|
|
||||||
|
|
||||||
// If the event is in the future, don't activate it.
|
if (events[i].time > currentTime)
|
||||||
if (event.time > currentTime) return false;
|
{
|
||||||
|
nextEventIndex = i;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
result.push(events[i]);
|
||||||
});
|
}
|
||||||
|
|
||||||
result.sort(SortUtil.eventDataByTime.bind(FlxSort.ASCENDING));
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -183,10 +199,26 @@ class SongEventRegistry
|
|||||||
*/
|
*/
|
||||||
public static function resetEvents(events:Array<SongEventData>):Void
|
public static function resetEvents(events:Array<SongEventData>):Void
|
||||||
{
|
{
|
||||||
|
events.sort(SortUtil.eventDataByTime.bind(FlxSort.ASCENDING));
|
||||||
|
nextEventIndex = 0;
|
||||||
|
allEventHandlers.resize(0);
|
||||||
|
|
||||||
for (event in events)
|
for (event in events)
|
||||||
{
|
{
|
||||||
event.activated = false;
|
event.activated = false;
|
||||||
// TODO: Add an onReset() method to SongEvent?
|
|
||||||
|
var handler:Null<SongEvent> = getEvent(event.eventKind);
|
||||||
|
if (handler != null && !allEventHandlers.contains(handler)) allEventHandlers.push(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allEventHandlers:Array<SongEvent> = [];
|
||||||
|
|
||||||
|
public static inline function callEvent(scriptEvent:ScriptEvent):Void
|
||||||
|
{
|
||||||
|
for (event in allEventHandlers)
|
||||||
|
{
|
||||||
|
ScriptEventDispatcher.callEvent(event, scriptEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,4 +49,10 @@ typedef AlbumData =
|
|||||||
@:optional
|
@:optional
|
||||||
@:default([])
|
@:default([])
|
||||||
public var albumTitleAnimations:Array<AnimationData>;
|
public var albumTitleAnimations:Array<AnimationData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional override for the Freeplay OST text.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var albumOSTName:String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData, AlbumEntryParams> imp
|
|||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateAlbumData()` function.
|
* and adding migration to the `migrateAlbumData()` function.
|
||||||
*/
|
*/
|
||||||
public static final ALBUM_DATA_VERSION:thx.semver.Version = '1.0.0';
|
public static final ALBUM_DATA_VERSION:thx.semver.Version = '1.0.3';
|
||||||
|
|
||||||
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,17 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.3]
|
||||||
|
### Added
|
||||||
|
- Added `albumOSTName` to change the OST at the top right of the Freeplay menu.
|
||||||
|
|
||||||
|
## [1.0.2]
|
||||||
|
### Added
|
||||||
|
- Added `albumTitleOffsets` to change the horizontal and vertical position of the album title.
|
||||||
|
|
||||||
|
## [1.0.1]
|
||||||
|
### Added
|
||||||
|
- Added `albumTitleAnimations` to modify animation data for the album art.
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
Initial release.
|
Initial release.
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package funkin.data.song;
|
package funkin.data.song;
|
||||||
|
|
||||||
|
import funkin.ui.debug.charting.ChartEditorState;
|
||||||
import funkin.data.event.SongEventRegistry;
|
import funkin.data.event.SongEventRegistry;
|
||||||
import funkin.play.event.SongEvent;
|
import funkin.play.event.SongEvent;
|
||||||
import funkin.data.event.SongEventSchema;
|
import funkin.data.event.SongEventSchema;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import thx.semver.Version;
|
import thx.semver.Version;
|
||||||
import funkin.util.tools.ICloneable;
|
import funkin.util.tools.ICloneable;
|
||||||
|
import funkin.play.notes.notekind.NoteKind;
|
||||||
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data containing information about a song.
|
* Data containing information about a song.
|
||||||
@@ -119,6 +122,18 @@ class SongMetadata implements ICloneable<SongMetadata>
|
|||||||
// Update generatedBy and version before writing.
|
// Update generatedBy and version before writing.
|
||||||
updateVersionToLatest();
|
updateVersionToLatest();
|
||||||
|
|
||||||
|
// This might not be a very wise hack, but it works.
|
||||||
|
#if FEATURE_CHART_EDITOR
|
||||||
|
if (Std.isOfType(FlxG.state, ChartEditorState))
|
||||||
|
{
|
||||||
|
var state:ChartEditorState = cast(FlxG.state, ChartEditorState);
|
||||||
|
|
||||||
|
final songLength:Float = @:privateAccess state.songLengthInMs;
|
||||||
|
if (playData.previewStart > 1) playData.previewStart /= songLength;
|
||||||
|
if (playData.previewEnd > 1) playData.previewEnd /= songLength;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
var ignoreNullOptionals = true;
|
var ignoreNullOptionals = true;
|
||||||
var writer = new json2object.JsonWriter<SongMetadata>(ignoreNullOptionals);
|
var writer = new json2object.JsonWriter<SongMetadata>(ignoreNullOptionals);
|
||||||
// I believe @:jignored should be ignored by the writer?
|
// I believe @:jignored should be ignored by the writer?
|
||||||
@@ -483,17 +498,17 @@ class SongPlayData implements ICloneable<SongPlayData>
|
|||||||
* @since `2.2.2`
|
* @since `2.2.2`
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
@:default(0)
|
@:default(funkin.util.Constants.DEFAULT_PREVIEW_START_TIME)
|
||||||
public var previewStart:Int;
|
public var previewStart:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The end time for the audio preview in Freeplay.
|
* The end time for the audio preview in Freeplay.
|
||||||
* Defaults to 15 seconds in.
|
* Defaults to 20% seconds in.
|
||||||
* @since `2.2.2`
|
* @since `2.2.2`
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
@:default(15000)
|
@:default(funkin.util.Constants.DEFAULT_PREVIEW_END_TIME)
|
||||||
public var previewEnd:Int;
|
public var previewEnd:Float;
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
@@ -848,7 +863,7 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
|||||||
public function getInt(key:String):Null<Int>
|
public function getInt(key:String):Null<Int>
|
||||||
{
|
{
|
||||||
if (this.value == null) return null;
|
if (this.value == null) return null;
|
||||||
var result = Reflect.field(this.value, key);
|
var result:Any = Reflect.field(this.value, key);
|
||||||
if (result == null) return null;
|
if (result == null) return null;
|
||||||
if (Std.isOfType(result, Int)) return result;
|
if (Std.isOfType(result, Int)) return result;
|
||||||
if (Std.isOfType(result, String)) return Std.parseInt(cast result);
|
if (Std.isOfType(result, String)) return Std.parseInt(cast result);
|
||||||
@@ -863,7 +878,7 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
|||||||
public function getFloat(key:String):Null<Float>
|
public function getFloat(key:String):Null<Float>
|
||||||
{
|
{
|
||||||
if (this.value == null) return null;
|
if (this.value == null) return null;
|
||||||
var result = Reflect.field(this.value, key);
|
var result:Any = Reflect.field(this.value, key);
|
||||||
if (result == null) return null;
|
if (result == null) return null;
|
||||||
if (Std.isOfType(result, Float)) return result;
|
if (Std.isOfType(result, Float)) return result;
|
||||||
if (Std.isOfType(result, String)) return Std.parseFloat(cast result);
|
if (Std.isOfType(result, String)) return Std.parseFloat(cast result);
|
||||||
@@ -1303,12 +1318,15 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||||||
*/
|
*/
|
||||||
public function buildTooltip():String
|
public function buildTooltip():String
|
||||||
{
|
{
|
||||||
if ((this.kind?.length ?? 0) == 0) return "";
|
if ((this.kind?.length ?? 0) == 0) return '';
|
||||||
|
|
||||||
var result:String = 'Kind: ${this.kind}';
|
var noteKind:Null<NoteKind> = NoteKindManager.getNoteKind(this.kind);
|
||||||
|
var noteKindDesc:String = noteKind?.description ?? this.kind;
|
||||||
|
|
||||||
|
var result:String = 'Kind: $noteKindDesc';
|
||||||
if (this.params.length == 0) return result;
|
if (this.params.length == 0) return result;
|
||||||
|
|
||||||
result += "\nParams:";
|
result += '\nParams:';
|
||||||
|
|
||||||
for (param in params)
|
for (param in params)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ typedef OsuManiaData =
|
|||||||
var General:
|
var General:
|
||||||
{
|
{
|
||||||
var PreviewTime:Int;
|
var PreviewTime:Int;
|
||||||
|
var Mode:Int; // Should only be 3 if you are sure this is a mania beatmap
|
||||||
};
|
};
|
||||||
var Editor:
|
var Editor:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class OsuManiaImporter
|
|||||||
{
|
{
|
||||||
var parts:Array<String> = line.split(":");
|
var parts:Array<String> = line.split(":");
|
||||||
var key:String = StringTools.trim(parts.shift());
|
var key:String = StringTools.trim(parts.shift());
|
||||||
var value:String = StringTools.trim(parts.join(":"));
|
var value:Any = parseValue(StringTools.trim(parts.join(":")));
|
||||||
if (Reflect.field(result, currentSection) == null) Reflect.setField(result, currentSection, {
|
if (Reflect.field(result, currentSection) == null) Reflect.setField(result, currentSection, {
|
||||||
});
|
});
|
||||||
Reflect.setField(Reflect.field(result, currentSection), key, value);
|
Reflect.setField(Reflect.field(result, currentSection), key, value);
|
||||||
@@ -200,4 +200,22 @@ class OsuManiaImporter
|
|||||||
return new ManiaHitObject(time, noteD, holdDuration);
|
return new ManiaHitObject(time, noteD, holdDuration);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function parseValue(v:String):Any
|
||||||
|
{
|
||||||
|
var result:Null<Any>;
|
||||||
|
|
||||||
|
if (v.contains('.'))
|
||||||
|
{
|
||||||
|
result = Std.parseFloat(v);
|
||||||
|
if (!Math.isNaN(result)) return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = Std.parseInt(v);
|
||||||
|
if (result != null) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.2]
|
||||||
|
### Added
|
||||||
|
- Added `capsule` field to change the Level's capsule name on the Freeplay menu.
|
||||||
|
|
||||||
## [1.0.1]
|
## [1.0.1]
|
||||||
|
### Added
|
||||||
- Added `visible` attribute.
|
- Added `visible` attribute.
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ typedef LevelData =
|
|||||||
*/
|
*/
|
||||||
var name:String;
|
var name:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional data for how a level title is labeled on a capsule.
|
||||||
|
* Used when displaying songs in Freeplay.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var capsule:Null<LevelCapsuleData>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The graphic for the level, as seen in the scrolling list.
|
* The graphic for the level, as seen in the scrolling list.
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +63,24 @@ typedef LevelData =
|
|||||||
var background:String;
|
var background:String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for a level capsule text.
|
||||||
|
*/
|
||||||
|
typedef LevelCapsuleData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The label for the capsule text.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var name:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset on the position to render the capsule text at.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var offsets:Array<Float>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data for a single prop for a story mode level.
|
* Data for a single prop for a story mode level.
|
||||||
*/
|
*/
|
||||||
@@ -129,4 +154,11 @@ typedef LevelPropData =
|
|||||||
@:default(false)
|
@:default(false)
|
||||||
@:optional
|
@:optional
|
||||||
var flipX:Null<Bool>;
|
var flipX:Null<Bool>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flips the sprite on Y axis.
|
||||||
|
*/
|
||||||
|
@:default(false)
|
||||||
|
@:optional
|
||||||
|
var flipY:Null<Bool>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class LevelRegistry extends BaseRegistry<Level, LevelData, LevelEntryParams> imp
|
|||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateLevelData()` function.
|
* and adding migration to the `migrateLevelData()` function.
|
||||||
*/
|
*/
|
||||||
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.1";
|
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.2";
|
||||||
|
|
||||||
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class FunkTrail extends FlxTrail
|
|||||||
* @param alpha The alpha value for the very first trailsprite.
|
* @param alpha The alpha value for the very first trailsprite.
|
||||||
* @param diff How much lower the alpha of the next trailsprite is.
|
* @param diff How much lower the alpha of the next trailsprite is.
|
||||||
*/
|
*/
|
||||||
public function new(target:FlxSprite, ?graphic:FlxGraphicAsset, length:Int = 10, delay:Int = 3, alpha:Float = 0.4, diff:Float = 0.05)
|
public function new(target:FlxSprite, ?graphic:FlxGraphicAsset, length:Int = 10, delay:Float = 0.1, alpha:Float = 0.4, diff:Float = 0.05)
|
||||||
{
|
{
|
||||||
super(target, graphic, length, delay, alpha, diff);
|
super(target, graphic, length, delay, alpha, diff);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -5,9 +5,9 @@ package funkin.external.apple;
|
|||||||
* A utility class for FNFC loading on apple devices.
|
* A utility class for FNFC loading on apple devices.
|
||||||
*/
|
*/
|
||||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||||
@:include('FNFCUtil.hpp')
|
@:include('FNFCExtern.hpp')
|
||||||
@:unreflective
|
@:unreflective
|
||||||
extern class FNFCUtil
|
extern class FNFCExtern
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Copies the FNFC resource from the specified URL into the cache.
|
* Copies the FNFC resource from the specified URL into the cache.
|
||||||
@@ -15,7 +15,7 @@ extern class FNFCUtil
|
|||||||
* @param url The URL of the FNFC resource to copy into the cache.
|
* @param url The URL of the FNFC resource to copy into the cache.
|
||||||
* @param callback A function to be called when the copy operation completes.
|
* @param callback A function to be called when the copy operation completes.
|
||||||
*/
|
*/
|
||||||
@:native('Apple_FNFCUtil_CopyFNFCIntoCache')
|
@:native('Apple_FNFCExtern_CopyFNFCIntoCache')
|
||||||
static function copyFNFCIntoCache(url:cpp.ConstCharStar, callback:cpp.Callable<(event:cpp.ConstCharStar, value:cpp.ConstCharStar) -> Void>):Void;
|
static function copyFNFCIntoCache(url:cpp.ConstCharStar, callback:cpp.Callable<(event:cpp.ConstCharStar, value:cpp.ConstCharStar) -> Void>):Void;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
<file name="${this_dir}/src/AudioSession.mm" />
|
<file name="${this_dir}/src/AudioSession.mm" />
|
||||||
<file name="${this_dir}/src/MemoryUtil.mm" />
|
<file name="${this_dir}/src/MemoryUtil.mm" />
|
||||||
<file name="${this_dir}/src/ScreenUtil.mm" />
|
<file name="${this_dir}/src/ScreenUtil.mm" />
|
||||||
<file name="${this_dir}/src/FNFCUtil.mm" />
|
<file name="${this_dir}/src/FNFCExtern.mm" />
|
||||||
</files>
|
</files>
|
||||||
|
|
||||||
<target id="haxe">
|
<target id="haxe">
|
||||||
|
|||||||
+1
-1
@@ -14,4 +14,4 @@ typedef void (*FNFCCallback)(const char* event, const char* value);
|
|||||||
* @param url The URL of the FNFC resource to copy into the cache.
|
* @param url The URL of the FNFC resource to copy into the cache.
|
||||||
* @param callback A function to be called when the copy operation completes.
|
* @param callback A function to be called when the copy operation completes.
|
||||||
*/
|
*/
|
||||||
void Apple_FNFCUtil_CopyFNFCIntoCache(const char *url, FNFCCallback callback);
|
void Apple_FNFCExtern_CopyFNFCIntoCache(const char *url, FNFCCallback callback);
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
#include "FNFCUtil.hpp"
|
#include "FNFCExtern.hpp"
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
static FNFCCallback fnfcCallback = nullptr;
|
static FNFCCallback fnfcCallback = nullptr;
|
||||||
|
|
||||||
void Apple_FNFCUtil_CopyFNFCIntoCache(const char *url, FNFCCallback callback)
|
void Apple_FNFCExtern_CopyFNFCIntoCache(const char *url, FNFCCallback callback)
|
||||||
{
|
{
|
||||||
#if TARGET_OS_IOS
|
#if TARGET_OS_IOS
|
||||||
if (!fnfcCallback)
|
if (!fnfcCallback)
|
||||||
+27
-16
@@ -11,16 +11,18 @@ void Apple_ScreenUtil_GetSafeAreaInsets(double* top, double* bottom, double* lef
|
|||||||
#if TARGET_OS_IOS
|
#if TARGET_OS_IOS
|
||||||
if (@available(iOS 11, *))
|
if (@available(iOS 11, *))
|
||||||
{
|
{
|
||||||
UIWindow* window = [UIApplication sharedApplication].windows[0];
|
UIWindow *window = [UIApplication sharedApplication].windows[0];
|
||||||
UIEdgeInsets safeAreaInsets = window.safeAreaInsets;
|
if (window)
|
||||||
float scale = [UIScreen mainScreen].scale;
|
{
|
||||||
|
UIEdgeInsets insets = window.safeAreaInsets;
|
||||||
|
CGFloat scale = window.screen.nativeScale;
|
||||||
|
|
||||||
(*top) = safeAreaInsets.top * scale;
|
(*top) = (double)(insets.top * scale);
|
||||||
(*bottom) = safeAreaInsets.bottom * scale;
|
(*bottom) = (double)(insets.bottom * scale);
|
||||||
(*left) = safeAreaInsets.left * scale;
|
(*left) = (double)(insets.left * scale);
|
||||||
(*right) = safeAreaInsets.right * scale;
|
(*right) = (double)(insets.right * scale);
|
||||||
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -33,13 +35,22 @@ void Apple_ScreenUtil_GetSafeAreaInsets(double* top, double* bottom, double* lef
|
|||||||
void Apple_ScreenUtil_GetScreenSize(double* width, double* height)
|
void Apple_ScreenUtil_GetScreenSize(double* width, double* height)
|
||||||
{
|
{
|
||||||
#if TARGET_OS_IOS
|
#if TARGET_OS_IOS
|
||||||
CGRect screenRect = [[UIScreen mainScreen] bounds];
|
if (@available(iOS 11, *))
|
||||||
float scale = [UIScreen mainScreen].scale;
|
{
|
||||||
|
UIWindow *window = [UIApplication sharedApplication].windows[0];
|
||||||
|
if (window && window.rootViewController)
|
||||||
|
{
|
||||||
|
UIView *view = window.rootViewController.view;
|
||||||
|
CGSize size = view.bounds.size;
|
||||||
|
CGFloat scale = window.screen.nativeScale;
|
||||||
|
|
||||||
(*width) = (double)screenRect.size.width * scale;
|
(*width) = (double)((int)(size.width * scale));
|
||||||
(*height) = (double)screenRect.size.height * scale;
|
(*height) = (double)((int)(size.height * scale));
|
||||||
#else
|
return;
|
||||||
(*width) = 0.0;
|
}
|
||||||
(*width) = 0.0;
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
(*width) = 0.0;
|
||||||
|
(*height) = 0.0;
|
||||||
}
|
}
|
||||||
|
|||||||
-47
@@ -8,36 +8,6 @@ package funkin.external.windows;
|
|||||||
@:include('winapi.hpp')
|
@:include('winapi.hpp')
|
||||||
extern class WinAPI
|
extern class WinAPI
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Shows a message box with an error icon.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window.
|
|
||||||
* @param message The message to display.
|
|
||||||
* @param title The title of the message box.
|
|
||||||
*/
|
|
||||||
@:native('WINAPI_ShowError')
|
|
||||||
static function showError(handle:cpp.RawPointer<cpp.Void>, message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a message box with a warning icon.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window.
|
|
||||||
* @param message The message to display.
|
|
||||||
* @param title The title of the message box.
|
|
||||||
*/
|
|
||||||
@:native('WINAPI_ShowWarning')
|
|
||||||
static function showWarning(handle:cpp.RawPointer<cpp.Void>, message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a message box with an information icon.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window.
|
|
||||||
* @param message The message to display.
|
|
||||||
* @param title The title of the message box.
|
|
||||||
*/
|
|
||||||
@:native('WINAPI_ShowInformation')
|
|
||||||
static function showInformation(handle:cpp.RawPointer<cpp.Void>, message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables the "Report to Microsoft" dialog that appears when the application crashes.
|
* Disables the "Report to Microsoft" dialog that appears when the application crashes.
|
||||||
*/
|
*/
|
||||||
@@ -57,22 +27,5 @@ extern class WinAPI
|
|||||||
*/
|
*/
|
||||||
@:native('WINAPI_GetProcessMemoryWorkingSetSize')
|
@:native('WINAPI_GetProcessMemoryWorkingSetSize')
|
||||||
static function getProcessMemoryWorkingSetSize():cpp.SizeT;
|
static function getProcessMemoryWorkingSetSize():cpp.SizeT;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the dark mode for the active window.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window.
|
|
||||||
* @param enable Whether to enable or disable dark mode.
|
|
||||||
*/
|
|
||||||
@:native('WINAPI_SetDarkMode')
|
|
||||||
static function setDarkMode(handle:cpp.RawPointer<cpp.Void>, enable:Bool):Void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the system is using dark mode.
|
|
||||||
*
|
|
||||||
* @return True if system is in dark mode, false otherwise.
|
|
||||||
*/
|
|
||||||
@:native('WINAPI_IsSystemDarkMode')
|
|
||||||
static function isSystemDarkMode():Bool;
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|||||||
@@ -1,32 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows an error message box.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window
|
|
||||||
* @param message The error message to display
|
|
||||||
* @param title The title of the message box
|
|
||||||
*/
|
|
||||||
void WINAPI_ShowError(void* handle, const char *message, const char *title);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a warning message box.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window
|
|
||||||
* @param message The warning message to display
|
|
||||||
* @param title The title of the message box
|
|
||||||
*/
|
|
||||||
void WINAPI_ShowWarning(void* handle, const char *message, const char *title);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows an information message box.
|
|
||||||
*
|
|
||||||
* @param handle A handle to the parent window
|
|
||||||
* @param message The information message to display
|
|
||||||
* @param title The title of the message box
|
|
||||||
*/
|
|
||||||
void WINAPI_ShowInformation(void* handle, const char *message, const char *title);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables Windows error reporting dialogs.
|
* Disables Windows error reporting dialogs.
|
||||||
*/
|
*/
|
||||||
@@ -45,18 +18,3 @@ void WINAPI_DisableWindowsGhosting();
|
|||||||
* @return The working set size in bytes. Returns 0 if the query fails.
|
* @return The working set size in bytes. Returns 0 if the query fails.
|
||||||
*/
|
*/
|
||||||
size_t WINAPI_GetProcessMemoryWorkingSetSize();
|
size_t WINAPI_GetProcessMemoryWorkingSetSize();
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets dark mode on a window
|
|
||||||
* @param handle A handle to the parent window.
|
|
||||||
*
|
|
||||||
* @param enable True to enable dark mode, false to disable
|
|
||||||
*/
|
|
||||||
void WINAPI_SetDarkMode(void* handle, bool enable);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the system is using dark mode.
|
|
||||||
*
|
|
||||||
* @return True if system is in dark mode, false otherwise
|
|
||||||
*/
|
|
||||||
bool WINAPI_IsSystemDarkMode();
|
|
||||||
|
|||||||
@@ -11,37 +11,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
void WINAPI_ShowError(void* handle, const char *message, const char *title)
|
|
||||||
{
|
|
||||||
HWND hwnd = (HWND)handle;
|
|
||||||
|
|
||||||
if (!IsWindow(hwnd))
|
|
||||||
hwnd = nullptr;
|
|
||||||
|
|
||||||
MessageBox(hwnd, message, title, MB_OK | MB_ICONERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WINAPI_ShowWarning(void* handle, const char *message, const char *title)
|
|
||||||
{
|
|
||||||
HWND hwnd = (HWND)handle;
|
|
||||||
|
|
||||||
if (!IsWindow(hwnd))
|
|
||||||
hwnd = nullptr;
|
|
||||||
|
|
||||||
MessageBox(hwnd, message, title, MB_OK | MB_ICONWARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WINAPI_ShowInformation(void* handle, const char *message, const char *title)
|
|
||||||
{
|
|
||||||
HWND hwnd = (HWND)handle;
|
|
||||||
|
|
||||||
if (!IsWindow(hwnd))
|
|
||||||
hwnd = nullptr;
|
|
||||||
|
|
||||||
MessageBox(hwnd, message, title, MB_OK | MB_ICONINFORMATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void WINAPI_DisableErrorReporting()
|
void WINAPI_DisableErrorReporting()
|
||||||
{
|
{
|
||||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||||
@@ -61,39 +30,3 @@ size_t WINAPI_GetProcessMemoryWorkingSetSize()
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WINAPI_SetDarkMode(void* handle, bool enable)
|
|
||||||
{
|
|
||||||
HWND hwnd = (HWND)handle;
|
|
||||||
|
|
||||||
if (!IsWindow(hwnd))
|
|
||||||
return;
|
|
||||||
|
|
||||||
int darkMode = enable ? 1 : 0;
|
|
||||||
|
|
||||||
if (DwmSetWindowAttribute(hwnd, 20, &darkMode, sizeof(darkMode)) != S_OK)
|
|
||||||
DwmSetWindowAttribute(hwnd, 19, &darkMode, sizeof(darkMode));
|
|
||||||
|
|
||||||
UpdateWindow(hwnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WINAPI_IsSystemDarkMode()
|
|
||||||
{
|
|
||||||
HKEY hKey;
|
|
||||||
DWORD dwValue = 0;
|
|
||||||
DWORD dwSize = sizeof(DWORD);
|
|
||||||
DWORD dwType = REG_DWORD;
|
|
||||||
|
|
||||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
|
|
||||||
{
|
|
||||||
if (RegQueryValueEx(hKey, "AppsUseLightTheme", NULL, &dwType, (LPBYTE)&dwValue, &dwSize) == ERROR_SUCCESS)
|
|
||||||
{
|
|
||||||
RegCloseKey(hKey);
|
|
||||||
return dwValue == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
RegCloseKey(hKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,420 +0,0 @@
|
|||||||
package funkin.graphics;
|
|
||||||
|
|
||||||
import flixel.FlxBasic;
|
|
||||||
import flixel.FlxCamera;
|
|
||||||
import flixel.FlxG;
|
|
||||||
import flixel.FlxSprite;
|
|
||||||
import flixel.graphics.FlxGraphic;
|
|
||||||
import flixel.graphics.frames.FlxFrame;
|
|
||||||
import flixel.math.FlxMatrix;
|
|
||||||
import flixel.math.FlxRect;
|
|
||||||
import lime.graphics.cairo.Cairo;
|
|
||||||
import openfl.display.BitmapData;
|
|
||||||
import openfl.display.BlendMode;
|
|
||||||
import openfl.display.DisplayObjectRenderer;
|
|
||||||
import openfl.display.Graphics;
|
|
||||||
import openfl.display.OpenGLRenderer;
|
|
||||||
import openfl.display3D.Context3D;
|
|
||||||
import openfl.filters.BitmapFilter;
|
|
||||||
import openfl.geom.ColorTransform;
|
|
||||||
import openfl.geom.Matrix;
|
|
||||||
import openfl.geom.Rectangle;
|
|
||||||
#if (js && html5)
|
|
||||||
import lime._internal.graphics.ImageCanvasUtil;
|
|
||||||
import openfl.display.CanvasRenderer;
|
|
||||||
import openfl.display._internal.CanvasGraphics as GfxRenderer;
|
|
||||||
#else
|
|
||||||
import openfl.display.CairoRenderer;
|
|
||||||
import openfl.display._internal.CairoGraphics as GfxRenderer;
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A modified `FlxSprite` that supports filters.
|
|
||||||
* The name's pretty much self-explanatory.
|
|
||||||
*/
|
|
||||||
@:nullSafety
|
|
||||||
@:access(openfl.geom.Rectangle)
|
|
||||||
@:access(openfl.filters.BitmapFilter)
|
|
||||||
@:access(flixel.graphics.frames.FlxFrame)
|
|
||||||
class FlxFilteredSprite extends FlxSprite
|
|
||||||
{
|
|
||||||
@:noCompletion var _renderer:FlxAnimateFilterRenderer = new FlxAnimateFilterRenderer();
|
|
||||||
|
|
||||||
@:noCompletion var _filterMatrix:FlxMatrix = new FlxMatrix();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An `Array` of shader filters (aka `BitmapFilter`).
|
|
||||||
*/
|
|
||||||
public var filters(default, set):Null<Array<BitmapFilter>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a flag to update the image with the filters.
|
|
||||||
* Useful when trying to render a shader at all times.
|
|
||||||
*/
|
|
||||||
public var filterDirty:Bool = false;
|
|
||||||
|
|
||||||
@:noCompletion var filtered:Bool = false;
|
|
||||||
|
|
||||||
// These appear to be a little troublesome to null safe.
|
|
||||||
@:nullSafety(Off)
|
|
||||||
@:noCompletion var _blankFrame:FlxFrame;
|
|
||||||
|
|
||||||
@:nullSafety(Off)
|
|
||||||
var _filterBmp1:BitmapData;
|
|
||||||
@:nullSafety(Off)
|
|
||||||
var _filterBmp2:BitmapData;
|
|
||||||
|
|
||||||
override public function update(elapsed:Float)
|
|
||||||
{
|
|
||||||
super.update(elapsed);
|
|
||||||
if (!filterDirty && filters != null)
|
|
||||||
{
|
|
||||||
for (filter in filters)
|
|
||||||
{
|
|
||||||
if (filter.__renderDirty)
|
|
||||||
{
|
|
||||||
filterDirty = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion
|
|
||||||
override function initVars():Void
|
|
||||||
{
|
|
||||||
super.initVars();
|
|
||||||
_filterMatrix = new FlxMatrix();
|
|
||||||
filters = null;
|
|
||||||
filtered = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public function draw():Void
|
|
||||||
{
|
|
||||||
checkEmptyFrame();
|
|
||||||
|
|
||||||
if (alpha == 0 || _frame.type == FlxFrameType.EMPTY) return;
|
|
||||||
|
|
||||||
if (dirty) // rarely
|
|
||||||
calcFrame(useFramePixels);
|
|
||||||
|
|
||||||
if (filterDirty) filterFrame();
|
|
||||||
|
|
||||||
for (camera in cameras)
|
|
||||||
{
|
|
||||||
if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue;
|
|
||||||
|
|
||||||
getScreenPosition(_point, camera).subtractPoint(offset);
|
|
||||||
|
|
||||||
if (isSimpleRender(camera)) drawSimple(camera);
|
|
||||||
else
|
|
||||||
drawComplex(camera);
|
|
||||||
|
|
||||||
#if FLX_DEBUG
|
|
||||||
FlxBasic.visibleCount++;
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FLX_DEBUG
|
|
||||||
if (FlxG.debugger.drawDebug) drawDebug();
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion
|
|
||||||
override function drawComplex(camera:FlxCamera):Void
|
|
||||||
{
|
|
||||||
_frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
|
|
||||||
_matrix.concat(_filterMatrix);
|
|
||||||
_matrix.translate(-origin.x, -origin.y);
|
|
||||||
_matrix.scale(scale.x, scale.y);
|
|
||||||
|
|
||||||
if (bakedRotationAngle <= 0)
|
|
||||||
{
|
|
||||||
updateTrig();
|
|
||||||
|
|
||||||
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
|
|
||||||
}
|
|
||||||
|
|
||||||
_point.add(origin.x, origin.y);
|
|
||||||
_matrix.translate(_point.x, _point.y);
|
|
||||||
|
|
||||||
if (isPixelPerfectRender(camera))
|
|
||||||
{
|
|
||||||
_matrix.tx = Math.floor(_matrix.tx);
|
|
||||||
_matrix.ty = Math.floor(_matrix.ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
camera.drawPixels((filtered) ? _blankFrame : _frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion
|
|
||||||
function filterFrame()
|
|
||||||
{
|
|
||||||
filterDirty = false;
|
|
||||||
_filterMatrix.identity();
|
|
||||||
|
|
||||||
if (filters != null && filters.length > 0)
|
|
||||||
{
|
|
||||||
_flashRect.setEmpty();
|
|
||||||
|
|
||||||
for (filter in filters)
|
|
||||||
{
|
|
||||||
_flashRect.__expand(-filter.__leftExtension,
|
|
||||||
-filter.__topExtension, filter.__leftExtension
|
|
||||||
+ filter.__rightExtension,
|
|
||||||
filter.__topExtension
|
|
||||||
+ filter.__bottomExtension);
|
|
||||||
}
|
|
||||||
_flashRect.width += frameWidth;
|
|
||||||
_flashRect.height += frameHeight;
|
|
||||||
@:nullSafety(Off)
|
|
||||||
if (_blankFrame == null) _blankFrame = new FlxFrame(null);
|
|
||||||
|
|
||||||
if (_blankFrame.parent == null || _flashRect.width > _blankFrame.parent.width || _flashRect.height > _blankFrame.parent.height)
|
|
||||||
{
|
|
||||||
if (_blankFrame.parent != null)
|
|
||||||
{
|
|
||||||
_blankFrame.parent.destroy();
|
|
||||||
_filterBmp1.dispose();
|
|
||||||
_filterBmp2.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_blankFrame.parent = FlxGraphic.fromRectangle(Math.ceil(_flashRect.width * 1.25), Math.ceil(_flashRect.height * 1.25), 0, true);
|
|
||||||
_filterBmp1 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
|
||||||
_filterBmp2 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
|
||||||
}
|
|
||||||
_blankFrame.offset.copyFrom(_frame.offset);
|
|
||||||
@:nullSafety(Off)
|
|
||||||
_blankFrame.parent.bitmap = _renderer.applyFilter(_blankFrame.parent.bitmap, _filterBmp1, _filterBmp2, frame.parent.bitmap, filters, _flashRect,
|
|
||||||
frame.frame.copyToFlash());
|
|
||||||
_blankFrame.frame = FlxRect.get(0, 0, _blankFrame.parent.bitmap.width, _blankFrame.parent.bitmap.height);
|
|
||||||
_filterMatrix.translate(_flashRect.x, _flashRect.y);
|
|
||||||
_frame = _blankFrame.copyTo();
|
|
||||||
filtered = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resetFrame();
|
|
||||||
filtered = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion
|
|
||||||
function set_filters(value:Null<Array<BitmapFilter>>)
|
|
||||||
{
|
|
||||||
if (filters != value) filterDirty = true;
|
|
||||||
|
|
||||||
return filters = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion
|
|
||||||
override function set_frame(value:FlxFrame)
|
|
||||||
{
|
|
||||||
if (value != frame) filterDirty = true;
|
|
||||||
|
|
||||||
return super.set_frame(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
override public function destroy()
|
|
||||||
{
|
|
||||||
super.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion
|
|
||||||
@:access(openfl.display.OpenGLRenderer)
|
|
||||||
@:access(openfl.filters.BitmapFilter)
|
|
||||||
@:access(openfl.geom.Rectangle)
|
|
||||||
@:access(openfl.display.Stage)
|
|
||||||
@:access(openfl.display.Graphics)
|
|
||||||
@:access(openfl.display.Shader)
|
|
||||||
@:access(openfl.display.BitmapData)
|
|
||||||
@:access(openfl.geom.ColorTransform)
|
|
||||||
@:access(openfl.display.DisplayObject)
|
|
||||||
@:access(openfl.display3D.Context3D)
|
|
||||||
@:access(openfl.display.CanvasRenderer)
|
|
||||||
@:access(openfl.display.CairoRenderer)
|
|
||||||
@:access(openfl.display3D.Context3D)
|
|
||||||
class FlxAnimateFilterRenderer
|
|
||||||
{
|
|
||||||
var renderer:OpenGLRenderer;
|
|
||||||
var context:Context3D;
|
|
||||||
|
|
||||||
public function new()
|
|
||||||
{
|
|
||||||
// context = new openfl.display3D.Context3D(null);
|
|
||||||
renderer = new OpenGLRenderer(FlxG.game.stage.context3D);
|
|
||||||
renderer.__worldTransform = new Matrix();
|
|
||||||
renderer.__worldColorTransform = new ColorTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
@:noCompletion function setRenderer(renderer:DisplayObjectRenderer, rect:Rectangle)
|
|
||||||
{
|
|
||||||
@:privateAccess
|
|
||||||
if (true)
|
|
||||||
{
|
|
||||||
var displayObject = FlxG.game;
|
|
||||||
var pixelRatio = FlxG.game.stage.__renderer.__pixelRatio;
|
|
||||||
|
|
||||||
var offsetX = rect.x > 0 ? Math.ceil(rect.x) : Math.floor(rect.x);
|
|
||||||
var offsetY = rect.y > 0 ? Math.ceil(rect.y) : Math.floor(rect.y);
|
|
||||||
if (renderer.__worldTransform == null)
|
|
||||||
{
|
|
||||||
renderer.__worldTransform = new Matrix();
|
|
||||||
renderer.__worldColorTransform = new ColorTransform();
|
|
||||||
}
|
|
||||||
if (displayObject.__cacheBitmapColorTransform == null) displayObject.__cacheBitmapColorTransform = new ColorTransform();
|
|
||||||
|
|
||||||
renderer.__stage = displayObject.stage;
|
|
||||||
|
|
||||||
renderer.__allowSmoothing = true;
|
|
||||||
renderer.__setBlendMode(NORMAL);
|
|
||||||
renderer.__worldAlpha = 1 / displayObject.__worldAlpha;
|
|
||||||
|
|
||||||
renderer.__worldTransform.identity();
|
|
||||||
renderer.__worldTransform.invert();
|
|
||||||
renderer.__worldTransform.concat(new Matrix());
|
|
||||||
renderer.__worldTransform.tx -= offsetX;
|
|
||||||
renderer.__worldTransform.ty -= offsetY;
|
|
||||||
renderer.__worldTransform.scale(pixelRatio, pixelRatio);
|
|
||||||
|
|
||||||
renderer.__pixelRatio = pixelRatio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function applyFilter(target:BitmapData = null, target1:BitmapData = null, target2:BitmapData = null, bmp:BitmapData, filters:Array<BitmapFilter>,
|
|
||||||
rect:Rectangle, bmpRect:Rectangle)
|
|
||||||
{
|
|
||||||
if (filters == null || filters.length == 0) return bmp;
|
|
||||||
|
|
||||||
renderer.__setBlendMode(NORMAL);
|
|
||||||
renderer.__worldAlpha = 1;
|
|
||||||
|
|
||||||
if (renderer.__worldTransform == null)
|
|
||||||
{
|
|
||||||
renderer.__worldTransform = new Matrix();
|
|
||||||
renderer.__worldColorTransform = new ColorTransform();
|
|
||||||
}
|
|
||||||
renderer.__worldTransform.identity();
|
|
||||||
renderer.__worldColorTransform.__identity();
|
|
||||||
|
|
||||||
var bitmap:BitmapData = (target == null) ? new BitmapData(Math.ceil(rect.width * 1.25), Math.ceil(rect.height * 1.25), true, 0) : target;
|
|
||||||
|
|
||||||
var bitmap2 = (target1 == null) ? new BitmapData(Math.ceil(rect.width * 1.25), Math.ceil(rect.height * 1.25), true, 0) : target1,
|
|
||||||
bitmap3 = (target2 == null) ? bitmap2.clone() : target2;
|
|
||||||
renderer.__setRenderTarget(bitmap);
|
|
||||||
|
|
||||||
bmp.__renderTransform.translate(Math.abs(rect.x) - bmpRect.x, Math.abs(rect.y) - bmpRect.y);
|
|
||||||
bmpRect.x = Math.abs(rect.x);
|
|
||||||
bmpRect.y = Math.abs(rect.y);
|
|
||||||
|
|
||||||
var bestResolution = renderer.__context3D.__backBufferWantsBestResolution;
|
|
||||||
renderer.__context3D.__backBufferWantsBestResolution = false;
|
|
||||||
renderer.__scissorRect(bmpRect);
|
|
||||||
renderer.__renderFilterPass(bmp, renderer.__defaultDisplayShader, true);
|
|
||||||
renderer.__scissorRect();
|
|
||||||
|
|
||||||
renderer.__context3D.__backBufferWantsBestResolution = bestResolution;
|
|
||||||
|
|
||||||
bmp.__renderTransform.identity();
|
|
||||||
|
|
||||||
var shader, cacheBitmap = null;
|
|
||||||
for (filter in filters)
|
|
||||||
{
|
|
||||||
if (filter.__preserveObject)
|
|
||||||
{
|
|
||||||
renderer.__setRenderTarget(bitmap3);
|
|
||||||
renderer.__renderFilterPass(bitmap, renderer.__defaultDisplayShader, filter.__smooth);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0...filter.__numShaderPasses)
|
|
||||||
{
|
|
||||||
shader = filter.__initShader(renderer, i, (filter.__preserveObject) ? bitmap3 : null);
|
|
||||||
renderer.__setBlendMode(filter.__shaderBlendMode);
|
|
||||||
renderer.__setRenderTarget(bitmap2);
|
|
||||||
renderer.__renderFilterPass(bitmap, shader, filter.__smooth);
|
|
||||||
|
|
||||||
cacheBitmap = bitmap;
|
|
||||||
bitmap = bitmap2;
|
|
||||||
bitmap2 = cacheBitmap;
|
|
||||||
}
|
|
||||||
filter.__renderDirty = false;
|
|
||||||
}
|
|
||||||
if (target1 == null) bitmap2.dispose();
|
|
||||||
if (target2 == null) bitmap3.dispose();
|
|
||||||
|
|
||||||
// var gl = renderer.__gl;
|
|
||||||
|
|
||||||
// var renderBuffer = bitmap.getTexture(renderer.__context3D);
|
|
||||||
// @:privateAccess
|
|
||||||
// gl.readPixels(0, 0, bitmap.width, bitmap.height, renderBuffer.__format, gl.UNSIGNED_BYTE, bitmap.image.data);
|
|
||||||
// bitmap.image.version = 0;
|
|
||||||
// @:privateAccess
|
|
||||||
// bitmap.__textureVersion = -1;
|
|
||||||
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function applyBlend(blend:BlendMode, bitmap:BitmapData)
|
|
||||||
{
|
|
||||||
bitmap.__update(false, true);
|
|
||||||
var bmp = new BitmapData(bitmap.width, bitmap.height, 0);
|
|
||||||
|
|
||||||
#if (js && html5)
|
|
||||||
ImageCanvasUtil.convertToCanvas(bmp.image);
|
|
||||||
@:privateAccess
|
|
||||||
var renderer = new CanvasRenderer(bmp.image.buffer.__srcContext);
|
|
||||||
#else
|
|
||||||
var renderer = new CairoRenderer(new Cairo(bmp.getSurface()));
|
|
||||||
#end
|
|
||||||
|
|
||||||
// setRenderer(renderer, bmp.rect);
|
|
||||||
|
|
||||||
var m = new Matrix();
|
|
||||||
var c = new ColorTransform();
|
|
||||||
renderer.__allowSmoothing = true;
|
|
||||||
renderer.__overrideBlendMode = blend;
|
|
||||||
renderer.__worldTransform = m;
|
|
||||||
renderer.__worldAlpha = 1;
|
|
||||||
renderer.__worldColorTransform = c;
|
|
||||||
|
|
||||||
renderer.__setBlendMode(blend);
|
|
||||||
#if (js && html5)
|
|
||||||
bmp.__drawCanvas(bitmap, renderer);
|
|
||||||
#else
|
|
||||||
bmp.__drawCairo(bitmap, renderer);
|
|
||||||
#end
|
|
||||||
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function graphicstoBitmapData(gfx:Graphics)
|
|
||||||
{
|
|
||||||
if (gfx.__bounds == null) return null;
|
|
||||||
// var cacheRTT = renderer.__context3D.__state.renderToTexture;
|
|
||||||
// var cacheRTTDepthStencil = renderer.__context3D.__state.renderToTextureDepthStencil;
|
|
||||||
// var cacheRTTAntiAlias = renderer.__context3D.__state.renderToTextureAntiAlias;
|
|
||||||
// var cacheRTTSurfaceSelector = renderer.__context3D.__state.renderToTextureSurfaceSelector;
|
|
||||||
|
|
||||||
// var bmp = new BitmapData(Math.ceil(gfx.__width), Math.ceil(gfx.__height), 0);
|
|
||||||
// renderer.__context3D.setRenderToTexture(bmp.getTexture(renderer.__context3D));
|
|
||||||
// gfx.__owner.__renderTransform.identity();
|
|
||||||
// gfx.__renderTransform.identity();
|
|
||||||
// Context3DGraphics.render(gfx, renderer);
|
|
||||||
GfxRenderer.render(gfx, cast renderer.__softwareRenderer);
|
|
||||||
var bmp = gfx.__bitmap;
|
|
||||||
|
|
||||||
gfx.__bitmap = null;
|
|
||||||
|
|
||||||
// if (cacheRTT != null)
|
|
||||||
// {
|
|
||||||
// renderer.__context3D.setRenderToTexture(cacheRTT, cacheRTTDepthStencil, cacheRTTAntiAlias, cacheRTTSurfaceSelector);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// renderer.__context3D.setRenderToBackBuffer();
|
|
||||||
// }
|
|
||||||
|
|
||||||
return bmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package funkin.graphics;
|
package funkin.graphics;
|
||||||
|
|
||||||
import funkin.graphics.FunkinSprite;
|
|
||||||
import animate.FlxAnimateController;
|
import animate.FlxAnimateController;
|
||||||
|
|
||||||
|
@:access(funkin.graphics.FunkinSprite)
|
||||||
class FunkinAnimationController extends FlxAnimateController
|
class FunkinAnimationController extends FlxAnimateController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -16,6 +16,12 @@ class FunkinAnimationController extends FlxAnimateController
|
|||||||
_parentSprite = sprite;
|
_parentSprite = sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override function set_frameIndex(frame:Int):Int
|
||||||
|
{
|
||||||
|
_parentSprite._renderTextureDirty = true;
|
||||||
|
return super.set_frameIndex(frame);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We override `FlxAnimationController`'s `play` method to account for texture atlases.
|
* We override `FlxAnimationController`'s `play` method to account for texture atlases.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -186,11 +186,15 @@ class FunkinCamera extends FlxCamera
|
|||||||
|
|
||||||
_backgroundFrame.parent.bitmap = _blendRenderTexture.graphic.bitmap;
|
_backgroundFrame.parent.bitmap = _blendRenderTexture.graphic.bitmap;
|
||||||
|
|
||||||
_backgroundRenderTexture.init(Std.int(this.width * Lib.current.stage.window.scale), Std.int(this.height * Lib.current.stage.window.scale));
|
// On some displays, the DPI can be less than 1, which causes the blend shader to look bad
|
||||||
|
// We just clamp the scale to 1 to avoid this!
|
||||||
|
var clampedScale:Float = Math.max(1, Lib.current.stage.window.scale);
|
||||||
|
|
||||||
|
_backgroundRenderTexture.init(Std.int(this.width * clampedScale), Std.int(this.height * clampedScale));
|
||||||
_backgroundRenderTexture.drawToCamera((camera, matrix) ->
|
_backgroundRenderTexture.drawToCamera((camera, matrix) ->
|
||||||
{
|
{
|
||||||
camera.zoom = this.zoom;
|
camera.zoom = this.zoom;
|
||||||
matrix.scale(Lib.current.stage.window.scale, Lib.current.stage.window.scale);
|
matrix.scale(clampedScale, clampedScale);
|
||||||
camera.drawPixels(_backgroundFrame, null, matrix, canvas.transform.colorTransform, null, false, _blendShader);
|
camera.drawPixels(_backgroundFrame, null, matrix, canvas.transform.colorTransform, null, false, _blendShader);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,7 +202,7 @@ class FunkinCamera extends FlxCamera
|
|||||||
|
|
||||||
// Resize the frame so it always fills the screen
|
// Resize the frame so it always fills the screen
|
||||||
_cameraMatrix.identity();
|
_cameraMatrix.identity();
|
||||||
_cameraMatrix.scale(1 / (this.scaleX * Lib.current.stage.window.scale), 1 / (this.scaleY * Lib.current.stage.window.scale));
|
_cameraMatrix.scale(1 / (this.scaleX * clampedScale), 1 / (this.scaleY * clampedScale));
|
||||||
_cameraMatrix.translate(((width - width / this.scaleX) * 0.5), ((height - height / this.scaleY) * 0.5));
|
_cameraMatrix.translate(((width - width / this.scaleX) * 0.5), ((height - height / this.scaleY) * 0.5));
|
||||||
|
|
||||||
super.drawPixels(_backgroundRenderTexture.graphic.imageFrame.frame, null, _cameraMatrix, null, null, smoothing, null);
|
super.drawPixels(_backgroundRenderTexture.graphic.imageFrame.frame, null, _cameraMatrix, null, null, smoothing, null);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import flixel.graphics.FlxGraphic;
|
|||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import openfl.display3D.textures.TextureBase;
|
import openfl.display3D.textures.TextureBase;
|
||||||
import funkin.graphics.framebuffer.FixedBitmapData;
|
import funkin.graphics.framebuffer.FixedBitmapData;
|
||||||
|
import funkin.graphics.framebuffer.FunkinFilterRenderer;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
@@ -21,6 +22,8 @@ import animate.internal.elements.AtlasInstance;
|
|||||||
import animate.internal.elements.SymbolInstance;
|
import animate.internal.elements.SymbolInstance;
|
||||||
import animate.FlxAnimate;
|
import animate.FlxAnimate;
|
||||||
import animate.FlxAnimateFrames;
|
import animate.FlxAnimateFrames;
|
||||||
|
import animate.internal.RenderTexture;
|
||||||
|
import openfl.filters.BitmapFilter;
|
||||||
import haxe.io.Path;
|
import haxe.io.Path;
|
||||||
|
|
||||||
using StringTools;
|
using StringTools;
|
||||||
@@ -114,6 +117,11 @@ typedef AtlasSpriteSettings =
|
|||||||
@:access(animate.FlxAnimateController)
|
@:access(animate.FlxAnimateController)
|
||||||
class FunkinSprite extends FlxAnimate
|
class FunkinSprite extends FlxAnimate
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The filters array to be applied to the sprite.
|
||||||
|
*/
|
||||||
|
public var filters(default, set):Null<Array<BitmapFilter>> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param x Starting X position
|
* @param x Starting X position
|
||||||
* @param y Starting Y position
|
* @param y Starting Y position
|
||||||
@@ -124,6 +132,8 @@ class FunkinSprite extends FlxAnimate
|
|||||||
{
|
{
|
||||||
super(x, y);
|
super(x, y);
|
||||||
|
|
||||||
|
filterRenderer = new FunkinFilterRenderer(this);
|
||||||
|
|
||||||
if (path != null)
|
if (path != null)
|
||||||
{
|
{
|
||||||
var ext:String = Path.extension(path);
|
var ext:String = Path.extension(path);
|
||||||
@@ -774,10 +784,136 @@ class FunkinSprite extends FlxAnimate
|
|||||||
matrix.ty = Math.round(matrix.ty / this.scale.y) * this.scale.y;
|
matrix.ty = Math.round(matrix.ty / this.scale.y) * this.scale.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filterRenderer:FunkinFilterRenderer;
|
||||||
|
var filtered:Bool = false;
|
||||||
|
var filterOffsets:Array<Float> = [0, 0];
|
||||||
|
|
||||||
|
override function checkRenderTexture():Bool
|
||||||
|
{
|
||||||
|
// Forcefully enable render texture when we have filters.
|
||||||
|
if (filters != null && filters.length > 0) return true;
|
||||||
|
|
||||||
|
return super.checkRenderTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_filters(value:Null<Array<BitmapFilter>>):Null<Array<BitmapFilter>>
|
||||||
|
{
|
||||||
|
if (filters != value) _renderTextureDirty = true;
|
||||||
|
filters = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function draw():Void
|
||||||
|
{
|
||||||
|
for (filter in filters ?? [])
|
||||||
|
{
|
||||||
|
@:privateAccess
|
||||||
|
if (filter.__renderDirty) _renderTextureDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
override function drawFrameComplex(frame:FlxFrame, camera:FlxCamera):Void
|
||||||
|
{
|
||||||
|
final willUseRenderTexture = checkRenderTexture();
|
||||||
|
final matrix = this._matrix;
|
||||||
|
|
||||||
|
frame.prepareMatrix(matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
|
||||||
|
prepareDrawMatrix(matrix, camera);
|
||||||
|
|
||||||
|
if (willUseRenderTexture)
|
||||||
|
{
|
||||||
|
var bounds:Array<Int> = [Math.ceil(frame.frame.width), Math.ceil(frame.frame.height)];
|
||||||
|
if (_renderTexture == null) _renderTexture = new RenderTexture(bounds[0], bounds[1]);
|
||||||
|
|
||||||
|
if (_renderTextureDirty)
|
||||||
|
{
|
||||||
|
_renderTexture.init(bounds[0], bounds[1]);
|
||||||
|
_renderTexture.drawToCamera((camera, mat) ->
|
||||||
|
{
|
||||||
|
camera.drawPixels(frame, framePixels, mat, null, null, antialiasing, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
_renderTexture.render();
|
||||||
|
|
||||||
|
filterRenderer.applyFilters();
|
||||||
|
_renderTextureDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtered)
|
||||||
|
{
|
||||||
|
matrix.translate(filterOffsets[0], filterOffsets[1]);
|
||||||
|
camera.drawPixels(filterRenderer.graphic?.imageFrame.frame, null, matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
camera.drawPixels(_renderTexture.graphic.imageFrame.frame, framePixels, matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
camera.drawPixels(frame, framePixels, matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function drawAnimate(camera:FlxCamera):Void
|
||||||
|
{
|
||||||
|
final willUseRenderTexture = checkRenderTexture();
|
||||||
|
final matrix = _matrix;
|
||||||
|
matrix.identity();
|
||||||
|
|
||||||
|
@:privateAccess
|
||||||
|
var bounds = timeline._bounds;
|
||||||
|
if (!willUseRenderTexture) matrix.translate(-bounds.x, -bounds.y);
|
||||||
|
|
||||||
|
prepareAnimateMatrix(matrix, camera, bounds);
|
||||||
|
|
||||||
|
if (renderStage) drawStage(camera);
|
||||||
|
|
||||||
|
timeline.currentFrame = animation.frameIndex;
|
||||||
|
|
||||||
|
#if !flash
|
||||||
|
if (willUseRenderTexture)
|
||||||
|
{
|
||||||
|
if (_renderTexture == null) _renderTexture = new RenderTexture(Math.ceil(bounds.width), Math.ceil(bounds.height));
|
||||||
|
|
||||||
|
if (_renderTextureDirty)
|
||||||
|
{
|
||||||
|
_renderTexture.init(Math.ceil(bounds.width), Math.ceil(bounds.height));
|
||||||
|
_renderTexture.drawToCamera((camera, matrix) ->
|
||||||
|
{
|
||||||
|
matrix.translate(-bounds.x, -bounds.y);
|
||||||
|
timeline.draw(camera, matrix, null, null, antialiasing, null);
|
||||||
|
});
|
||||||
|
_renderTexture.render();
|
||||||
|
|
||||||
|
filterRenderer.applyFilters();
|
||||||
|
_renderTextureDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtered)
|
||||||
|
{
|
||||||
|
matrix.translate(filterOffsets[0], filterOffsets[1]);
|
||||||
|
camera.drawPixels(filterRenderer.graphic?.imageFrame.frame, null, matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
camera.drawPixels(_renderTexture.graphic.imageFrame.frame, framePixels, matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#end
|
||||||
|
{
|
||||||
|
timeline.draw(camera, matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
@:nullSafety(Off) // TODO: Remove when flixel.FlxSprite is null safed.
|
@:nullSafety(Off) // TODO: Remove when flixel.FlxSprite is null safed.
|
||||||
frames = null;
|
frames = null;
|
||||||
|
filterRenderer.destroy();
|
||||||
// Cancel all tweens so they don't continue to run on a destroyed sprite.
|
// Cancel all tweens so they don't continue to run on a destroyed sprite.
|
||||||
// This prevents crashes.
|
// This prevents crashes.
|
||||||
FlxTween.cancelTweensOf(this);
|
FlxTween.cancelTweensOf(this);
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package funkin.graphics.framebuffer;
|
||||||
|
|
||||||
|
import funkin.graphics.framebuffer.FixedBitmapData;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
import flixel.math.FlxRect;
|
||||||
|
import flixel.graphics.FlxGraphic;
|
||||||
|
import flixel.util.FlxDestroyUtil;
|
||||||
|
import funkin.graphics.FunkinSprite;
|
||||||
|
import openfl.filters.BitmapFilter;
|
||||||
|
import animate.internal.FilterRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper for rendering filters on `FunkinSprite` instances.
|
||||||
|
*/
|
||||||
|
@:access(animate.FlxAnimate)
|
||||||
|
@:access(openfl.filters.BitmapFilter)
|
||||||
|
@:access(animate.internal.FilterRenderer)
|
||||||
|
@:access(openfl.display.OpenGLRenderer)
|
||||||
|
@:access(openfl.geom.ColorTransform)
|
||||||
|
@:access(openfl.display.BitmapData)
|
||||||
|
@:nullSafety
|
||||||
|
class FunkinFilterRenderer implements IFlxDestroyable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Graphic containing the current frame with filters.
|
||||||
|
*/
|
||||||
|
public var graphic(default, null):Null<FlxGraphic>;
|
||||||
|
|
||||||
|
var bitmapPool:Map<String, Array<BitmapData>> = [];
|
||||||
|
var parent:FunkinSprite;
|
||||||
|
|
||||||
|
public function new(parent:FunkinSprite)
|
||||||
|
{
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply filters to the current frame.
|
||||||
|
* The result will be contained in the `graphic` variable.
|
||||||
|
*/
|
||||||
|
public function applyFilters():Void
|
||||||
|
{
|
||||||
|
parent.filtered = false;
|
||||||
|
if (parent.filters == null || parent.filters.length < 1) return;
|
||||||
|
|
||||||
|
var textureBitmap:BitmapData = parent._renderTexture.graphic.bitmap;
|
||||||
|
|
||||||
|
var bounds:FlxRect = FlxRect.get().copyFromFlash(textureBitmap.rect);
|
||||||
|
FilterRenderer.expandFilterBounds(bounds, parent.filters);
|
||||||
|
parent.filterOffsets = [bounds.x * parent.scale.x, bounds.y * parent.scale.x];
|
||||||
|
|
||||||
|
var ceilWidth:Int = Math.ceil(bounds.width);
|
||||||
|
var ceilHeight:Int = Math.ceil(bounds.height);
|
||||||
|
|
||||||
|
if (graphic != null) putBitmap(graphic.bitmap);
|
||||||
|
var bitmap:BitmapData = getBitmap(ceilWidth, ceilHeight);
|
||||||
|
|
||||||
|
if (graphic == null)
|
||||||
|
{
|
||||||
|
graphic = FlxGraphic.fromBitmapData(bitmap, false, null, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
graphic.bitmap = bitmap;
|
||||||
|
graphic.imageFrame.frame.frame.set(0, 0, bitmap.width, bitmap.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterBmp1:Null<BitmapData> = null;
|
||||||
|
var filterBmp2:Null<BitmapData> = null;
|
||||||
|
|
||||||
|
var needsSecondBitmap:Bool = false;
|
||||||
|
var needsPreserveObject:Bool = false;
|
||||||
|
for (filter in parent.filters)
|
||||||
|
{
|
||||||
|
if (filter != null)
|
||||||
|
{
|
||||||
|
if (filter.__needSecondBitmapData) needsSecondBitmap = true;
|
||||||
|
if (filter.__preserveObject) needsPreserveObject = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsSecondBitmap) filterBmp1 = getBitmap(graphic.width, graphic.height);
|
||||||
|
if (needsPreserveObject) filterBmp2 = getBitmap(filterBmp1?.width ?? 1, filterBmp1?.height ?? 1);
|
||||||
|
|
||||||
|
_applyFilters(graphic.bitmap, textureBitmap, parent.filters, filterBmp1, filterBmp2, bounds);
|
||||||
|
|
||||||
|
if (filterBmp1 != null) putBitmap(filterBmp1);
|
||||||
|
if (filterBmp2 != null) putBitmap(filterBmp2);
|
||||||
|
|
||||||
|
bounds.put();
|
||||||
|
parent.filtered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _applyFilters(target:BitmapData, bmp:BitmapData, filters:Array<BitmapFilter>, target1:Null<BitmapData>, target2:Null<BitmapData>,
|
||||||
|
bounds:FlxRect):Void
|
||||||
|
{
|
||||||
|
var renderer = FilterRenderer.renderer;
|
||||||
|
|
||||||
|
var bitmap:BitmapData = target;
|
||||||
|
var bitmap2:BitmapData = target1 ?? bmp;
|
||||||
|
var bitmap3:BitmapData = target2 ?? bmp;
|
||||||
|
|
||||||
|
renderer.__setBlendMode(NORMAL);
|
||||||
|
renderer.__worldAlpha = 1;
|
||||||
|
if (renderer.__worldTransform == null)
|
||||||
|
{
|
||||||
|
renderer.__worldTransform = new openfl.geom.Matrix();
|
||||||
|
renderer.__worldColorTransform = new openfl.geom.ColorTransform();
|
||||||
|
}
|
||||||
|
renderer.__worldTransform.identity();
|
||||||
|
renderer.__worldColorTransform.__identity();
|
||||||
|
bmp.__renderTransform.identity();
|
||||||
|
bmp.__renderTransform.translate(-bounds.x, -bounds.y);
|
||||||
|
renderer.setShader(renderer.__defaultShader);
|
||||||
|
renderer.__setRenderTarget(bitmap);
|
||||||
|
renderer.__scissorRect(null);
|
||||||
|
renderer.__renderFilterPass(bmp, renderer.__defaultDisplayShader, true);
|
||||||
|
for (filter in filters)
|
||||||
|
{
|
||||||
|
if (filter == null) continue;
|
||||||
|
bitmap = FilterRenderer.__renderGpuFilter(filter, bitmap, bitmap2, bitmap3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBitmap(width:Int, height:Int):BitmapData
|
||||||
|
{
|
||||||
|
final id:String = Std.string(width) + 'x' + Std.string(height);
|
||||||
|
var bitmaps:Array<BitmapData> = bitmapPool.get(id) ?? [];
|
||||||
|
if (bitmaps.length < 1)
|
||||||
|
{
|
||||||
|
var bitmap:BitmapData = FixedBitmapData.create(width, height);
|
||||||
|
bitmaps.push(bitmap);
|
||||||
|
}
|
||||||
|
var bitmap:Null<BitmapData> = bitmaps.shift();
|
||||||
|
if (bitmap == null) throw 'The bitmap is null???? :whattheshit:';
|
||||||
|
bitmap.__fillRect(bitmap.rect, 0, true);
|
||||||
|
bitmapPool.set(id, bitmaps);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function putBitmap(bitmap:BitmapData):Void
|
||||||
|
{
|
||||||
|
final id:String = Std.string(bitmap.width) + 'x' + Std.string(bitmap.height);
|
||||||
|
var bitmaps:Array<BitmapData> = bitmapPool.get(id) ?? [];
|
||||||
|
if (!bitmaps.contains(bitmap)) bitmaps.push(bitmap);
|
||||||
|
bitmapPool.set(id, bitmaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up memory.
|
||||||
|
*/
|
||||||
|
public function destroy():Void
|
||||||
|
{
|
||||||
|
for (bitmaps in bitmapPool.iterator())
|
||||||
|
{
|
||||||
|
for (bitmap in bitmaps)
|
||||||
|
{
|
||||||
|
if (bitmap.__texture != null) bitmap.__texture.dispose();
|
||||||
|
bitmap.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -216,14 +216,7 @@ class DropShadowShader extends FlxShader
|
|||||||
*/
|
*/
|
||||||
public function loadAltMask(path:String):Void
|
public function loadAltMask(path:String):Void
|
||||||
{
|
{
|
||||||
#if html5
|
|
||||||
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData)
|
|
||||||
{
|
|
||||||
altMaskImage = bmp;
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
altMaskImage = Assets.getBitmapData(path, false);
|
altMaskImage = Assets.getBitmapData(path, false);
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,16 +8,9 @@ class TextureSwap extends FlxShader
|
|||||||
public var swappedImage(default, set):BitmapData;
|
public var swappedImage(default, set):BitmapData;
|
||||||
public var amount(default, set):Float;
|
public var amount(default, set):Float;
|
||||||
|
|
||||||
public function loadSwapImage(path:String)
|
public function loadSwapImage(path:String):Void
|
||||||
{
|
{
|
||||||
#if html5
|
swappedImage = Assets.getBitmapData(path, false);
|
||||||
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData)
|
|
||||||
{
|
|
||||||
swappedImage = bmp;
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
swappedImage = BitmapData.fromFile(path);
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
||||||
|
|||||||
@@ -0,0 +1,770 @@
|
|||||||
|
package funkin.group;
|
||||||
|
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import flixel.FlxG;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.util.FlxSort;
|
||||||
|
import funkin.util.SortUtil;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A FunkinGroup of FlxSprites.
|
||||||
|
*/
|
||||||
|
typedef FunkinSpriteGroup = FunkinGroup<FlxSprite>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlxSpriteGroup but better. Kinda like if `FlxNestedSprite` and `FlxSpriteGroup` were merged.
|
||||||
|
*/
|
||||||
|
class FunkinGroup<T:FlxSprite> extends FlxSprite
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The children of this FunkinGroup.
|
||||||
|
*/
|
||||||
|
public var children:Null<Array<T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of this FunkinGroup. Read only.
|
||||||
|
*/
|
||||||
|
public var size(get, never):Int;
|
||||||
|
|
||||||
|
function get_size():Int
|
||||||
|
{
|
||||||
|
return children.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of this FunkinGroup. Read only.
|
||||||
|
*
|
||||||
|
* Alias to `size`.
|
||||||
|
*/
|
||||||
|
public var length(get, never):Int;
|
||||||
|
|
||||||
|
function get_length():Int
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The max size of this FunkinGroup. 0 and below is infinite.
|
||||||
|
*/
|
||||||
|
public var maxSize(default, set):Int = 0;
|
||||||
|
|
||||||
|
function set_maxSize(value:Int):Int
|
||||||
|
{
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
|
||||||
|
maxSize = value;
|
||||||
|
|
||||||
|
if (size > maxSize && maxSize > 0)
|
||||||
|
{
|
||||||
|
for (child in 0...size)
|
||||||
|
{
|
||||||
|
if (child > maxSize) remove(children[child]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of all the FunkinGroup's children's displays put together.
|
||||||
|
*
|
||||||
|
* Meant as a replacement of frameWidth.
|
||||||
|
*/
|
||||||
|
public var accurateWidth(get, null):Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of all the FunkinGroup's children's displays put together.
|
||||||
|
*
|
||||||
|
* Meant as a replacement of frameHeight.
|
||||||
|
*/
|
||||||
|
public var accurateHeight(get, null):Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is false, the FunkinGroup will update children normally. Otherwise,
|
||||||
|
* it will not (obviously).
|
||||||
|
*
|
||||||
|
* Useful for outside objects to modify this group's children. (Extending
|
||||||
|
* classes can just override updateChildren)
|
||||||
|
*
|
||||||
|
* `false` by default.
|
||||||
|
*/
|
||||||
|
public var customChildUpdate:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should this FunkinGroup treat itself more like one image (in scale terms).
|
||||||
|
*
|
||||||
|
* `true` by default.
|
||||||
|
*/
|
||||||
|
public var preciseScale:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should this FunkinGroup treat itself more like one image (in angle terms).
|
||||||
|
*
|
||||||
|
* `true` by default.
|
||||||
|
*/
|
||||||
|
public var preciseAngle:Bool = true;
|
||||||
|
|
||||||
|
override function get_width():Float
|
||||||
|
{
|
||||||
|
if (size < 1) return 0;
|
||||||
|
|
||||||
|
var leftMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, x + a.localX, x + b.localX);
|
||||||
|
}, false)[0];
|
||||||
|
|
||||||
|
var rightMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, (x + a.localX) + a.width, (x + b.localX) + b.width);
|
||||||
|
}, false, FlxSort.DESCENDING)[0];
|
||||||
|
|
||||||
|
return Math.abs((x + rightMostSpr.localX) + rightMostSpr.width) - (x + leftMostSpr.localX);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get_height():Float
|
||||||
|
{
|
||||||
|
if (size < 1) return 0;
|
||||||
|
|
||||||
|
var downwardsMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, (y + a.localY) + a.height, (y + b.localY) + b.height);
|
||||||
|
}, false, FlxSort.DESCENDING)[0];
|
||||||
|
|
||||||
|
var upwardsMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, y + a.localY, y + b.localY);
|
||||||
|
}, false)[0];
|
||||||
|
|
||||||
|
return Math.abs(((y + downwardsMostSpr.localY) + downwardsMostSpr.height) - (y + upwardsMostSpr.localY));
|
||||||
|
}
|
||||||
|
|
||||||
|
// include scale with frame sizes for better accuracy
|
||||||
|
|
||||||
|
function get_accurateWidth():Float
|
||||||
|
{
|
||||||
|
if (size < 1) return 0;
|
||||||
|
|
||||||
|
var leftMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, x + a.localX, x + b.localX);
|
||||||
|
}, false)[0];
|
||||||
|
|
||||||
|
var rightMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, (x + a.localX) + (a.frameWidth * a.scale.x), (x + b.localX) + (b.frameWidth * a.scale.x));
|
||||||
|
}, false, FlxSort.DESCENDING)[0];
|
||||||
|
|
||||||
|
return Math.abs(((x + rightMostSpr.localX) + (rightMostSpr.frameWidth * rightMostSpr.scale.x)) - (x + leftMostSpr.localX));
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_accurateHeight():Float
|
||||||
|
{
|
||||||
|
if (size < 1) return 0;
|
||||||
|
|
||||||
|
var downwardsMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, (y + a.localY) + (a.frameHeight * a.scale.y), (y + b.localY) + (b.frameHeight * a.scale.y));
|
||||||
|
}, false, FlxSort.DESCENDING)[0];
|
||||||
|
|
||||||
|
var upwardsMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, y + a.localY, y + b.localY);
|
||||||
|
}, false)[0];
|
||||||
|
|
||||||
|
return Math.abs(((y + downwardsMostSpr.localY) + (downwardsMostSpr.frameHeight * downwardsMostSpr.scale.y)) - (y + upwardsMostSpr.localY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this FunkinGroup's `origin` to the center of its complete graphic.
|
||||||
|
*
|
||||||
|
* Replacement for `centerOrigin`.
|
||||||
|
*/
|
||||||
|
public function resetOrigin():Void
|
||||||
|
{
|
||||||
|
if (size < 1) return;
|
||||||
|
|
||||||
|
var upwardsMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, y + a.localY, y + b.localY);
|
||||||
|
}, false)[0];
|
||||||
|
|
||||||
|
var leftMostSpr:T = sort(function(order:Int, a:T, b:T):Int
|
||||||
|
{
|
||||||
|
if (a == null || b == null) return 0;
|
||||||
|
return FlxSort.byValues(order, x + a.localX, x + b.localX);
|
||||||
|
}, false)[0];
|
||||||
|
|
||||||
|
origin.set(leftMostSpr.localX + width / 2, upwardsMostSpr.localY + height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for FunkinGroup.
|
||||||
|
*
|
||||||
|
* @param x Starting X.
|
||||||
|
* @param y Starting Y.
|
||||||
|
* @param maxSize Starting max size.
|
||||||
|
* @param preciseScale Whether to treat the FunkinGroup like one image (with scale).
|
||||||
|
* @param preciseAngle Whether to treat the FunkinGroup like one image (with angle).
|
||||||
|
*/
|
||||||
|
public function new(?x:Float, ?y:Float, ?maxSize:Int, ?preciseScale:Bool, ?preciseAngle:Bool)
|
||||||
|
{
|
||||||
|
super(x, y);
|
||||||
|
|
||||||
|
children = [];
|
||||||
|
|
||||||
|
this.maxSize = maxSize ?? 0;
|
||||||
|
|
||||||
|
if (preciseScale != null) this.preciseScale = preciseScale;
|
||||||
|
if (preciseAngle != null) this.preciseAngle = preciseAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the child at an index.
|
||||||
|
*
|
||||||
|
* @param index The position.
|
||||||
|
* @return The child or null.
|
||||||
|
*/
|
||||||
|
public inline function getChildAt(index:Int):Null<T>
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= size) return null;
|
||||||
|
|
||||||
|
return children[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the child at an index.
|
||||||
|
*
|
||||||
|
* @param index The position.
|
||||||
|
* @param replacement A new child to replace the old one.
|
||||||
|
*/
|
||||||
|
public inline function setChildAt(index:Int, replacement:T):Void
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= size) return;
|
||||||
|
|
||||||
|
children[index] = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
updateChildren();
|
||||||
|
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child != null && child.exists && child.active) child.update(elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function draw():Void
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child != null && child.exists && child.visible) child.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the children here. Uses the child's local variables like `localX` and `localY` to update the child's position. Like `FlxNestedSprite`!
|
||||||
|
* Can be overriden by outside classes with `customChildUpdate`.
|
||||||
|
*/
|
||||||
|
public function updateChildren():Void
|
||||||
|
{
|
||||||
|
if (customChildUpdate) return;
|
||||||
|
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child != null && child.exists && child.active)
|
||||||
|
{
|
||||||
|
child.angle = angle + child.localAngle;
|
||||||
|
child.scale.x = scale.x * child.localScale.x;
|
||||||
|
child.scale.y = scale.y * child.localScale.y;
|
||||||
|
|
||||||
|
var displace = new FlxPoint(child.localX, child.localY);
|
||||||
|
|
||||||
|
var dx:Float = 0;
|
||||||
|
var dy:Float = 0;
|
||||||
|
|
||||||
|
dx = origin.x - child.width / 2;
|
||||||
|
dy = origin.y - child.height / 2;
|
||||||
|
|
||||||
|
if (preciseScale && !preciseAngle)
|
||||||
|
{
|
||||||
|
dx += scale.x * (child.localX - origin.x + child.width / 2);
|
||||||
|
|
||||||
|
dy += scale.y * (child.localY - origin.y + child.height / 2);
|
||||||
|
}
|
||||||
|
else if (preciseAngle && !preciseScale)
|
||||||
|
{
|
||||||
|
var radians:Float = angle * (Math.PI / 180);
|
||||||
|
var cos:Float = Math.cos(radians);
|
||||||
|
var sin:Float = Math.sin(radians);
|
||||||
|
|
||||||
|
dx += cos * (child.localX - origin.x + child.width / 2);
|
||||||
|
dx -= sin * (child.localY - origin.y + child.height / 2);
|
||||||
|
|
||||||
|
dy += cos * (child.localY - origin.y + child.height / 2);
|
||||||
|
dy += sin * (child.localX - origin.x + child.width / 2);
|
||||||
|
}
|
||||||
|
else if (preciseAngle && preciseScale)
|
||||||
|
{
|
||||||
|
var radians:Float = angle * (Math.PI / 180);
|
||||||
|
var cos:Float = Math.cos(radians);
|
||||||
|
var sin:Float = Math.sin(radians);
|
||||||
|
|
||||||
|
dx += scale.x * cos * (child.localX - origin.x + child.width / 2);
|
||||||
|
dx -= scale.y * sin * (child.localY - origin.y + child.height / 2);
|
||||||
|
|
||||||
|
dy += scale.y * cos * (child.localY - origin.y + child.height / 2);
|
||||||
|
dy += scale.x * sin * (child.localX - origin.x + child.width / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preciseScale || preciseAngle) displace.set(dx, dy);
|
||||||
|
|
||||||
|
child.x = x + displace.x;
|
||||||
|
child.y = y + displace.y;
|
||||||
|
|
||||||
|
child.alpha = alpha * child.localAlpha;
|
||||||
|
child.visible = visible && child.localVisible;
|
||||||
|
// force child cameras to the group's cameras.
|
||||||
|
if (child.cameras != cameras) child.cameras = cameras;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child to this FunkinGroup. Will also return said child for convenience.
|
||||||
|
* Can't add if `size` is at `maxSize`, instead returning null.
|
||||||
|
*
|
||||||
|
* @param child The child that the caller wants this FunkinGroup to add.
|
||||||
|
* @return The same child or null.
|
||||||
|
*/
|
||||||
|
public function add(child:T):Null<T>
|
||||||
|
{
|
||||||
|
if (maxSize > 0 && size >= maxSize) return null;
|
||||||
|
|
||||||
|
children.push(child);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a child right in this FunkinGroup. Will also return said child for convenience.
|
||||||
|
* Can't create the child if `size` is at `maxSize`, instead returning null.
|
||||||
|
*
|
||||||
|
* @return The created child or null.
|
||||||
|
*/
|
||||||
|
public function make():Null<T>
|
||||||
|
{
|
||||||
|
if (maxSize > 0 && size >= maxSize) return null;
|
||||||
|
|
||||||
|
var newChild:T = cast new FlxSprite();
|
||||||
|
children.push(newChild);
|
||||||
|
return newChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child to this FunkinGroup at a given index. Will also return said child for convenience.
|
||||||
|
*
|
||||||
|
* @param child The child that the caller wants this FunkinGroup to add.
|
||||||
|
* @param index The position the caller wants the child to go in.
|
||||||
|
* @return The same child or null.
|
||||||
|
*/
|
||||||
|
public function insert(child:T, index:Int):Null<T>
|
||||||
|
{
|
||||||
|
if (size < index) return null;
|
||||||
|
children.insert(index, child);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves select children from another FunkinGroup into this one. Only works if both FunkinGroups contain the same type.
|
||||||
|
*
|
||||||
|
* @param grp The other group to take from.
|
||||||
|
* @param children The children to move.
|
||||||
|
*/
|
||||||
|
public function move(grp:FunkinGroup<T>, children:Array<T>):Void
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (grp.children.contains(child))
|
||||||
|
{
|
||||||
|
grp.remove(child);
|
||||||
|
add(child);
|
||||||
|
// update child's local position so the child stays where it was
|
||||||
|
child.localX = x - child.x;
|
||||||
|
child.localY = y - child.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function destroy():Void
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
child.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
children = null;
|
||||||
|
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a child from the group, also returns it for convenience.
|
||||||
|
*
|
||||||
|
* @param child The child to remove.
|
||||||
|
* @return The removed child.
|
||||||
|
*/
|
||||||
|
public function remove(child:T):Null<T>
|
||||||
|
{
|
||||||
|
var index = children.indexOf(child);
|
||||||
|
if (index != -1) children.splice(index, 1);
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a function to all children.
|
||||||
|
*
|
||||||
|
* @param func A function that modifies one child at a time.
|
||||||
|
*/
|
||||||
|
public function forEach(func:T->Void):Void
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child != null)
|
||||||
|
{
|
||||||
|
func(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the children of this FunkinGroup. Returns the sorted children
|
||||||
|
*
|
||||||
|
* @param func The sorting function to use - you can use one of the premade ones in
|
||||||
|
* `FlxSort` or write your own using `FlxSort.byValues()` as a "backend".
|
||||||
|
* @param setGroup Whether to actually sort the children of this group,
|
||||||
|
* so the caller can grab a sorted list without this group
|
||||||
|
* actually sorting the children.
|
||||||
|
* @param order A constant that defines the sort order.
|
||||||
|
* Possible values are `FlxSort.ASCENDING` (default) and `FlxSort.DESCENDING`.
|
||||||
|
* @return The sorted children list.
|
||||||
|
*/
|
||||||
|
public inline function sort(func:(Int, T, T) -> Int, setGroup:Bool = true, order = FlxSort.ASCENDING):Null<Array<T>>
|
||||||
|
{
|
||||||
|
if (setGroup)
|
||||||
|
{
|
||||||
|
children.sort(func.bind(order));
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fakeKids = children.copy();
|
||||||
|
fakeKids.sort(func.bind(order));
|
||||||
|
return fakeKids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the group, by redoing the render order of all children.
|
||||||
|
* It does this based on the `zIndex` of each child.
|
||||||
|
*/
|
||||||
|
public function refresh():Void
|
||||||
|
{
|
||||||
|
sort(SortUtil.byZIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the first alive child under this FunkinGroup. Returns null if it can't
|
||||||
|
* find squat.
|
||||||
|
*
|
||||||
|
* @return The alive child or null.
|
||||||
|
*/
|
||||||
|
public inline function getFirstAlive():Null<T>
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child.exists && child.alive) return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the first dead child under this FunkinGroup. Returns null if it can't
|
||||||
|
* find squat.
|
||||||
|
* getFirstAlive's evil twin.
|
||||||
|
*
|
||||||
|
* @return The dead child or null.
|
||||||
|
*/
|
||||||
|
public inline function getFirstDead():Null<T>
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (!child.alive) return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the amount of alive children in this FunkinGroup.
|
||||||
|
*
|
||||||
|
* @return The alive child number or null.
|
||||||
|
*/
|
||||||
|
public inline function countLiving():Int
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child.alive) i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the amount of dead children in this FunkinGroup.
|
||||||
|
*
|
||||||
|
* @return The dead child number or null.
|
||||||
|
*/
|
||||||
|
public inline function countDead():Int
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (!child.alive) i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first nonexistent child in the family and returns it.
|
||||||
|
* Good for recycling.
|
||||||
|
*
|
||||||
|
* @return The child or null.
|
||||||
|
*/
|
||||||
|
public inline function getFirstAvailable():Null<T>
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (!child.exists) return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of the first null child under this FunkinGroup.
|
||||||
|
* -1 means it failed
|
||||||
|
*
|
||||||
|
* @return The index.
|
||||||
|
*/
|
||||||
|
public inline function getFirstNull():Int
|
||||||
|
{
|
||||||
|
for (child in 0...size)
|
||||||
|
{
|
||||||
|
if (children[child] == null) return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first existing child in the family and returns it.
|
||||||
|
* Good for recycling.
|
||||||
|
*
|
||||||
|
* @return The child or null.
|
||||||
|
*/
|
||||||
|
public inline function getFirstExisting():Null<T>
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child.exists) return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a random child from this FunkinGroup.
|
||||||
|
* @param startIndex Optional offset off the front of the array.
|
||||||
|
* Default value is `0`, or the beginning of the array.
|
||||||
|
* @param length Optional restriction on the number of values you want to randomly select from.
|
||||||
|
* @return A child or null.
|
||||||
|
*/
|
||||||
|
public inline function getRandom(startIndex:Int = 0, length:Int = 0):Null<T>
|
||||||
|
{
|
||||||
|
if (size <= 0) return null;
|
||||||
|
|
||||||
|
if (startIndex < 0) startIndex = 0;
|
||||||
|
if (length <= 0) length = size;
|
||||||
|
|
||||||
|
return FlxG.random.getObject(children, startIndex, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brings a child back from the graveyard.
|
||||||
|
*
|
||||||
|
* @param child The child to revive.
|
||||||
|
*/
|
||||||
|
public function reviveChild(child:T):Void
|
||||||
|
{
|
||||||
|
if (child != null) child.revive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills all the children and then itself.
|
||||||
|
* Revive this group via `revive()`.
|
||||||
|
*/
|
||||||
|
override public function kill():Void
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child != null) child.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revives all the children and then itself.
|
||||||
|
*/
|
||||||
|
override public function revive():Void
|
||||||
|
{
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
if (child != null) child.revive();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.revive();
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function clone():FunkinGroup<T>
|
||||||
|
{
|
||||||
|
var group = new FunkinGroup<T>(x, y, maxSize);
|
||||||
|
|
||||||
|
for (child in children)
|
||||||
|
{
|
||||||
|
group.add(cast child.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// Unavailable functions that won't work with `FunkinGroup`.
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
* @return this group
|
||||||
|
*/
|
||||||
|
override public function makeGraphic(Width:Int, Height:Int, Color:Int = FlxColor.WHITE, Unique:Bool = false, ?Key:String):FlxSprite
|
||||||
|
{
|
||||||
|
#if FLX_DEBUG
|
||||||
|
throw "This function is not supported in FunkinGroup";
|
||||||
|
#end
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
* @return this group
|
||||||
|
*/
|
||||||
|
override public function loadGraphicFromSprite(Sprite:FlxSprite):FlxSprite
|
||||||
|
{
|
||||||
|
#if FLX_DEBUG
|
||||||
|
throw "This function is not supported in FunkinGroup";
|
||||||
|
#end
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
* @return this group
|
||||||
|
*/
|
||||||
|
override public function loadGraphic(Graphic:flixel.system.FlxAssets.FlxGraphicAsset, Animated:Bool = false, Width:Int = 0, Height:Int = 0,
|
||||||
|
Unique:Bool = false, ?Key:String):FlxSprite
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
* @return this group
|
||||||
|
*/
|
||||||
|
override public function loadRotatedGraphic(Graphic:flixel.system.FlxAssets.FlxGraphicAsset, Rotations:Int = 16, Frame:Int = -1, AntiAliasing:Bool = false,
|
||||||
|
AutoBuffer:Bool = false, ?Key:String):FlxSprite
|
||||||
|
{
|
||||||
|
#if FLX_DEBUG
|
||||||
|
throw "This function is not supported in FunkinGroup";
|
||||||
|
#end
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function set_pixels(Value:openfl.display.BitmapData):openfl.display.BitmapData
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function set_frame(Value:flixel.graphics.frames.FlxFrame):flixel.graphics.frames.FlxFrame
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get_pixels():openfl.display.BitmapData
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function to update the current animation frame.
|
||||||
|
*
|
||||||
|
* @param RunOnCpp Whether the frame should also be recalculated if we're on a non-flash target
|
||||||
|
*/
|
||||||
|
override inline function calcFrame(RunOnCpp:Bool = false):Void
|
||||||
|
{
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
*/
|
||||||
|
override inline function resetHelpers():Void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
*/
|
||||||
|
override public inline function stamp(Brush:FlxSprite, X:Int = 0, Y:Int = 0):Void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
override function set_frames(Frames:flixel.graphics.frames.FlxFramesCollection):flixel.graphics.frames.FlxFramesCollection
|
||||||
|
{
|
||||||
|
return Frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functionality isn't supported in `FunkinGroup`.
|
||||||
|
*/
|
||||||
|
override inline function updateColorTransform():Void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package funkin.group;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A script that can be tied to a FunkinGroup.
|
||||||
|
* Create a scripted class that extends FunkinGroup to use this.
|
||||||
|
*/
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFunkinGroup extends funkin.group.FunkinGroup<Dynamic> implements polymod.hscript.HScriptedClass {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A script that can be tied to a FunkinSpriteGroup.
|
||||||
|
* Create a scripted class that extends FunkinSpriteGroup to use this.
|
||||||
|
*/
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFunkinSpriteGroup extends funkin.group.FunkinGroup.FunkinSpriteGroup implements polymod.hscript.HScriptedClass {}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package funkin.input;
|
package funkin.input;
|
||||||
|
|
||||||
|
import funkin.util.InputUtil;
|
||||||
import flixel.input.gamepad.FlxGamepad;
|
import flixel.input.gamepad.FlxGamepad;
|
||||||
import flixel.util.FlxDirectionFlags;
|
import flixel.util.FlxDirectionFlags;
|
||||||
import flixel.input.FlxInput.FlxInputState;
|
import flixel.input.FlxInput.FlxInputState;
|
||||||
@@ -381,22 +382,22 @@ class Controls extends FlxActionSet
|
|||||||
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
||||||
{
|
{
|
||||||
if (action.inputs.length == 0) return 'N/A';
|
if (action.inputs.length == 0) return 'N/A';
|
||||||
var input = action.inputs[0];
|
var input = FunkinAction.lastDeviceUsed == KEYBOARD ? action.inputs[0] : action.inputs[1];
|
||||||
if (ignoreSurrounding == false)
|
if (ignoreSurrounding == false)
|
||||||
{
|
{
|
||||||
return switch (input.device)
|
return switch (FunkinAction.lastDeviceUsed)
|
||||||
{
|
{
|
||||||
case KEYBOARD: return '[${(input.inputID : FlxKey)}]';
|
case KEYBOARD: return '[${InputUtil.format(input.inputID, Keys).toUpperCase()}]';
|
||||||
case GAMEPAD: return '(${(input.inputID : FlxGamepadInputID)})';
|
case GAMEPAD: return '(${InputUtil.format(input.inputID, Gamepad(input.deviceID)).toUpperCase()})';
|
||||||
case device: throw 'unhandled device: $device';
|
case device: throw 'unhandled device: $device';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return switch (input.device)
|
return switch (FunkinAction.lastDeviceUsed)
|
||||||
{
|
{
|
||||||
case KEYBOARD: return '${(input.inputID : FlxKey)}';
|
case KEYBOARD: return InputUtil.format(input.inputID, Keys).toUpperCase();
|
||||||
case GAMEPAD: return '${(input.inputID : FlxGamepadInputID)}';
|
case GAMEPAD: return InputUtil.format(input.inputID, Gamepad(input.deviceID)).toUpperCase();
|
||||||
case device: throw 'unhandled device: $device';
|
case device: throw 'unhandled device: $device';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1180,7 +1181,7 @@ class FunkinAction extends FlxActionDigital
|
|||||||
public var nameReleased(default, null):Null<String>;
|
public var nameReleased(default, null):Null<String>;
|
||||||
|
|
||||||
var cache:Map<String,
|
var cache:Map<String,
|
||||||
{timestamp:Int, value:Bool}> = [];
|
{timestamp:Float, value:Bool}> = [];
|
||||||
|
|
||||||
public function new(?name:String = "", ?namePressed:String, ?nameReleased:String)
|
public function new(?name:String = "", ?namePressed:String, ?nameReleased:String)
|
||||||
{
|
{
|
||||||
@@ -1188,6 +1189,8 @@ class FunkinAction extends FlxActionDigital
|
|||||||
|
|
||||||
this.namePressed = namePressed;
|
this.namePressed = namePressed;
|
||||||
this.nameReleased = nameReleased;
|
this.nameReleased = nameReleased;
|
||||||
|
|
||||||
|
updateLastDeviceUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1203,6 +1206,7 @@ class FunkinAction extends FlxActionDigital
|
|||||||
*/
|
*/
|
||||||
public function checkPressed():Bool
|
public function checkPressed():Bool
|
||||||
{
|
{
|
||||||
|
if (checkFiltered(PRESSED)) updateLastDeviceUsed();
|
||||||
return checkFiltered(PRESSED);
|
return checkFiltered(PRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1211,6 +1215,7 @@ class FunkinAction extends FlxActionDigital
|
|||||||
*/
|
*/
|
||||||
public function checkJustPressed():Bool
|
public function checkJustPressed():Bool
|
||||||
{
|
{
|
||||||
|
if (checkFiltered(JUST_PRESSED)) updateLastDeviceUsed();
|
||||||
return checkFiltered(JUST_PRESSED);
|
return checkFiltered(JUST_PRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1227,6 +1232,7 @@ class FunkinAction extends FlxActionDigital
|
|||||||
*/
|
*/
|
||||||
public function checkJustReleased():Bool
|
public function checkJustReleased():Bool
|
||||||
{
|
{
|
||||||
|
if (checkFiltered(JUST_RELEASED)) updateLastDeviceUsed();
|
||||||
return checkFiltered(JUST_RELEASED);
|
return checkFiltered(JUST_RELEASED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1353,6 +1359,29 @@ class FunkinAction extends FlxActionDigital
|
|||||||
|
|
||||||
return triggered;
|
return triggered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static var lastDeviceUsed:FlxInputDevice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks which is the last device you have used and stores the value in `lastDeviceUsed`.
|
||||||
|
*/
|
||||||
|
public function updateLastDeviceUsed()
|
||||||
|
{
|
||||||
|
if (FlxG.keys.pressed.ANY)
|
||||||
|
{
|
||||||
|
lastDeviceUsed = FlxInputDevice.KEYBOARD;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FlxG.gamepads.lastActive != null)
|
||||||
|
{
|
||||||
|
lastDeviceUsed = FlxInputDevice.GAMEPAD;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default value (just in case everything's null somehow)
|
||||||
|
lastDeviceUsed = FlxInputDevice.KEYBOARD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+58
-354
@@ -156,384 +156,87 @@ class Cursor
|
|||||||
if (value != null && cursorMode != value)
|
if (value != null && cursorMode != value)
|
||||||
{
|
{
|
||||||
cursorMode = value;
|
cursorMode = value;
|
||||||
setCursorGraphic(cursorMode);
|
loadCursorGraphicSync(cursorMode);
|
||||||
}
|
}
|
||||||
return cursorMode;
|
return cursorMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous.
|
* Loads the cursor graphic synchronously.
|
||||||
|
*
|
||||||
|
* @param value The cursor mode to load. If null, the cursor is unloaded.
|
||||||
*/
|
*/
|
||||||
static function setCursorGraphic(?value:CursorMode = null):Void
|
static function loadCursorGraphicSync(?value:CursorMode = null):Void
|
||||||
{
|
{
|
||||||
if (value == null)
|
applyCursorParams(value);
|
||||||
{
|
|
||||||
FlxG.mouse.unload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case Default:
|
|
||||||
if (assetCursorDefault == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_DEFAULT_PARAMS.graphic);
|
|
||||||
assetCursorDefault = bitmapData;
|
|
||||||
applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Cross:
|
|
||||||
if (assetCursorCross == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_CROSS_PARAMS.graphic);
|
|
||||||
assetCursorCross = bitmapData;
|
|
||||||
applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Eraser:
|
|
||||||
if (assetCursorEraser == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_ERASER_PARAMS.graphic);
|
|
||||||
assetCursorEraser = bitmapData;
|
|
||||||
applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Grabbing:
|
|
||||||
if (assetCursorGrabbing == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_GRABBING_PARAMS.graphic);
|
|
||||||
assetCursorGrabbing = bitmapData;
|
|
||||||
applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Hourglass:
|
|
||||||
if (assetCursorHourglass == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_HOURGLASS_PARAMS.graphic);
|
|
||||||
assetCursorHourglass = bitmapData;
|
|
||||||
applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Pointer:
|
|
||||||
if (assetCursorPointer == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_POINTER_PARAMS.graphic);
|
|
||||||
assetCursorPointer = bitmapData;
|
|
||||||
applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Text:
|
|
||||||
if (assetCursorText == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_TEXT_PARAMS.graphic);
|
|
||||||
assetCursorText = bitmapData;
|
|
||||||
applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ZoomIn:
|
|
||||||
if (assetCursorZoomIn == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_ZOOM_IN_PARAMS.graphic);
|
|
||||||
assetCursorZoomIn = bitmapData;
|
|
||||||
applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ZoomOut:
|
|
||||||
if (assetCursorZoomOut == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_ZOOM_OUT_PARAMS.graphic);
|
|
||||||
assetCursorZoomOut = bitmapData;
|
|
||||||
applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Crosshair:
|
|
||||||
if (assetCursorCrosshair == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_CROSSHAIR_PARAMS.graphic);
|
|
||||||
assetCursorCrosshair = bitmapData;
|
|
||||||
applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Cell:
|
|
||||||
if (assetCursorCell == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_CELL_PARAMS.graphic);
|
|
||||||
assetCursorCell = bitmapData;
|
|
||||||
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Scroll:
|
|
||||||
if (assetCursorScroll == null)
|
|
||||||
{
|
|
||||||
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_SCROLL_PARAMS.graphic);
|
|
||||||
assetCursorScroll = bitmapData;
|
|
||||||
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
setCursorGraphic(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous.
|
* Loads the cursor graphic asynchronously.
|
||||||
|
*
|
||||||
|
* @param value The cursor mode to load. If null, the cursor is unloaded.
|
||||||
*/
|
*/
|
||||||
static function loadCursorGraphic(?value:CursorMode = null):Void
|
static function loadCursorGraphicAsync(?value:CursorMode = null):Void
|
||||||
{
|
{
|
||||||
if (value == null)
|
applyCursorParams(value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function applyCursorParams(mode:Null<CursorMode>, async:Bool = false)
|
||||||
|
{
|
||||||
|
if (mode == null)
|
||||||
{
|
{
|
||||||
FlxG.mouse.unload();
|
FlxG.mouse.unload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (value)
|
var data = switch (mode)
|
||||||
{
|
{
|
||||||
case Default:
|
case Default: { cache: assetCursorDefault, params: CURSOR_DEFAULT_PARAMS, set: (bmp) -> assetCursorDefault = bmp };
|
||||||
if (assetCursorDefault == null)
|
case Cross: { cache: assetCursorCross, params: CURSOR_CROSS_PARAMS, set: (bmp) -> assetCursorCross = bmp };
|
||||||
{
|
case Eraser: { cache: assetCursorEraser, params: CURSOR_ERASER_PARAMS, set: (bmp) -> assetCursorEraser = bmp };
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_DEFAULT_PARAMS.graphic);
|
case Grabbing: { cache: assetCursorGrabbing, params: CURSOR_GRABBING_PARAMS, set: (bmp) -> assetCursorGrabbing = bmp };
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
case Hourglass: { cache: assetCursorHourglass, params: CURSOR_HOURGLASS_PARAMS, set: (bmp) -> assetCursorHourglass = bmp };
|
||||||
{
|
case Pointer: { cache: assetCursorPointer, params: CURSOR_POINTER_PARAMS, set: (bmp) -> assetCursorPointer = bmp };
|
||||||
assetCursorDefault = bitmapData;
|
case Text: { cache: assetCursorText, params: CURSOR_TEXT_PARAMS, set: (bmp) -> assetCursorText = bmp };
|
||||||
applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
|
case TextVertical: { cache: assetCursorTextVertical, params: CURSOR_TEXT_VERTICAL_PARAMS, set: (bmp) -> assetCursorTextVertical = bmp };
|
||||||
});
|
case ZoomIn: { cache: assetCursorZoomIn, params: CURSOR_ZOOM_IN_PARAMS, set: (bmp) -> assetCursorZoomIn = bmp };
|
||||||
future.onError(onCursorError.bind(Default));
|
case ZoomOut: { cache: assetCursorZoomOut, params: CURSOR_ZOOM_OUT_PARAMS, set: (bmp) -> assetCursorZoomOut = bmp };
|
||||||
}
|
case Crosshair: { cache: assetCursorCrosshair, params: CURSOR_CROSSHAIR_PARAMS, set: (bmp) -> assetCursorCrosshair = bmp };
|
||||||
else
|
case Cell: { cache: assetCursorCell, params: CURSOR_CELL_PARAMS, set: (bmp) -> assetCursorCell = bmp };
|
||||||
{
|
case Scroll: { cache: assetCursorScroll, params: CURSOR_SCROLL_PARAMS, set: (bmp) -> assetCursorScroll = bmp };
|
||||||
applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
|
default: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Cross:
|
if (data == null)
|
||||||
if (assetCursorCross == null)
|
{
|
||||||
{
|
FlxG.mouse.unload();
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_CROSS_PARAMS.graphic);
|
return;
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
}
|
||||||
{
|
|
||||||
assetCursorCross = bitmapData;
|
|
||||||
applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Cross));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Eraser:
|
if (data.cache != null)
|
||||||
if (assetCursorEraser == null)
|
{
|
||||||
{
|
applyGraphic(data.cache, data.params);
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_ERASER_PARAMS.graphic);
|
return;
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
}
|
||||||
{
|
|
||||||
assetCursorEraser = bitmapData;
|
|
||||||
applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Eraser));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Grabbing:
|
if (async)
|
||||||
if (assetCursorGrabbing == null)
|
{
|
||||||
{
|
var future:Future<BitmapData> = Assets.loadBitmapData(data.params.graphic);
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_GRABBING_PARAMS.graphic);
|
future.onComplete((bmp:BitmapData) -> {
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
data.set(bmp);
|
||||||
{
|
applyGraphic(bmp, data.params);
|
||||||
assetCursorGrabbing = bitmapData;
|
});
|
||||||
applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
|
future.onError(onCursorError.bind(mode));
|
||||||
});
|
}
|
||||||
future.onError(onCursorError.bind(Grabbing));
|
else
|
||||||
}
|
{
|
||||||
else
|
var bitmapData:BitmapData = Assets.getBitmapData(data.params.graphic);
|
||||||
{
|
data.set(bitmapData);
|
||||||
applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
|
applyGraphic(bitmapData, data.params);
|
||||||
}
|
|
||||||
|
|
||||||
case Hourglass:
|
|
||||||
if (assetCursorHourglass == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_HOURGLASS_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorHourglass = bitmapData;
|
|
||||||
applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Hourglass));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Pointer:
|
|
||||||
if (assetCursorPointer == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_POINTER_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorPointer = bitmapData;
|
|
||||||
applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Pointer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Text:
|
|
||||||
if (assetCursorText == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_TEXT_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorText = bitmapData;
|
|
||||||
applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Text));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ZoomIn:
|
|
||||||
if (assetCursorZoomIn == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_ZOOM_IN_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorZoomIn = bitmapData;
|
|
||||||
applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(ZoomIn));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ZoomOut:
|
|
||||||
if (assetCursorZoomOut == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_ZOOM_OUT_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorZoomOut = bitmapData;
|
|
||||||
applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(ZoomOut));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Crosshair:
|
|
||||||
if (assetCursorCrosshair == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_CROSSHAIR_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorCrosshair = bitmapData;
|
|
||||||
applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Crosshair));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Cell:
|
|
||||||
if (assetCursorCell == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_CELL_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorCell = bitmapData;
|
|
||||||
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Cell));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Scroll:
|
|
||||||
if (assetCursorScroll == null)
|
|
||||||
{
|
|
||||||
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_SCROLL_PARAMS.graphic);
|
|
||||||
future.onComplete(function(bitmapData:BitmapData)
|
|
||||||
{
|
|
||||||
assetCursorScroll = bitmapData;
|
|
||||||
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
|
||||||
});
|
|
||||||
future.onError(onCursorError.bind(Scroll));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
loadCursorGraphic(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline function applyCursorParams(graphic:BitmapData, params:CursorParams):Void
|
private static inline function applyGraphic(graphic:BitmapData, params:CursorParams):Void
|
||||||
{
|
{
|
||||||
FlxG.mouse.load(graphic, params.scale, params.offsetX, params.offsetY);
|
FlxG.mouse.load(graphic, params.scale, params.offsetX, params.offsetY);
|
||||||
}
|
}
|
||||||
@@ -580,6 +283,7 @@ enum CursorMode
|
|||||||
Hourglass;
|
Hourglass;
|
||||||
Pointer;
|
Pointer;
|
||||||
Text;
|
Text;
|
||||||
|
TextVertical;
|
||||||
ZoomIn;
|
ZoomIn;
|
||||||
ZoomOut;
|
ZoomOut;
|
||||||
Crosshair;
|
Crosshair;
|
||||||
|
|||||||
@@ -133,11 +133,6 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert from int to Int64.
|
|
||||||
*/
|
|
||||||
static final NS_PER_MS:Int64 = Constants.NS_PER_MS;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a precise timestamp, measured in nanoseconds.
|
* Returns a precise timestamp, measured in nanoseconds.
|
||||||
* Timestamp is only useful for comparing against other timestamps.
|
* Timestamp is only useful for comparing against other timestamps.
|
||||||
@@ -151,11 +146,11 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||||||
// NOTE: This timestamp isn't that precise on standard HTML5 builds.
|
// NOTE: This timestamp isn't that precise on standard HTML5 builds.
|
||||||
// This is because of browser safeguards against timing attacks.
|
// This is because of browser safeguards against timing attacks.
|
||||||
// See https://web.dev/coop-coep to enable headers which allow for more precise timestamps.
|
// See https://web.dev/coop-coep to enable headers which allow for more precise timestamps.
|
||||||
return haxe.Int64.fromFloat(js.Browser.window.performance.now()) * NS_PER_MS;
|
return haxe.Int64.fromFloat(js.Browser.window.performance.now()) * Constants.NS_PER_MS;
|
||||||
#elseif cpp
|
#elseif lime_cffi
|
||||||
// NOTE: If the game hard crashes on this line, rebuild Lime!
|
// NOTE: If the game hard crashes on this line, rebuild Lime!
|
||||||
// `lime rebuild windows -clean`
|
// `lime rebuild windows -clean`
|
||||||
return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * NS_PER_MS;
|
return Int64.fromFloat(lime._internal.backend.native.NativeCFFI.lime_system_get_timer());
|
||||||
#else
|
#else
|
||||||
throw "Eric didn't implement precise timestamps on this platform!";
|
throw "Eric didn't implement precise timestamps on this platform!";
|
||||||
#end
|
#end
|
||||||
@@ -290,11 +285,6 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||||||
var key:FlxKey = convertKeyCode(keyCode);
|
var key:FlxKey = convertKeyCode(keyCode);
|
||||||
if (_keyList.indexOf(key) == -1) return;
|
if (_keyList.indexOf(key) == -1) return;
|
||||||
|
|
||||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
|
||||||
// This is because SDL3's timestamps are measured in nanoseconds, not milliseconds.
|
|
||||||
timestamp *= Constants.NS_PER_MS; // 18126000000 38367000000
|
|
||||||
// timestamp -= globalOffset * Constants.NS_PER_MS;
|
|
||||||
// trace(timestamp);
|
|
||||||
updateKeyStates(key, true);
|
updateKeyStates(key, true);
|
||||||
|
|
||||||
if (getInputByKey(key)?.justPressed ?? false)
|
if (getInputByKey(key)?.justPressed ?? false)
|
||||||
@@ -313,10 +303,6 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||||||
var key:FlxKey = convertKeyCode(keyCode);
|
var key:FlxKey = convertKeyCode(keyCode);
|
||||||
if (_keyList.indexOf(key) == -1) return;
|
if (_keyList.indexOf(key) == -1) return;
|
||||||
|
|
||||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
|
||||||
// This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds.
|
|
||||||
timestamp *= Constants.NS_PER_MS;
|
|
||||||
|
|
||||||
updateKeyStates(key, false);
|
updateKeyStates(key, false);
|
||||||
|
|
||||||
if (getInputByKey(key)?.justReleased ?? false)
|
if (getInputByKey(key)?.justReleased ?? false)
|
||||||
@@ -337,10 +323,6 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||||||
var buttonListEntry = _buttonList.get(gamepad.id);
|
var buttonListEntry = _buttonList.get(gamepad.id);
|
||||||
if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return;
|
if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return;
|
||||||
|
|
||||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
|
||||||
// This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds.
|
|
||||||
timestamp *= Constants.NS_PER_MS;
|
|
||||||
|
|
||||||
updateButtonStates(gamepad, buttonId, true);
|
updateButtonStates(gamepad, buttonId, true);
|
||||||
|
|
||||||
if (getInputByButton(gamepad, buttonId)?.justPressed ?? false)
|
if (getInputByButton(gamepad, buttonId)?.justPressed ?? false)
|
||||||
@@ -361,10 +343,6 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||||||
var buttonListEntry = _buttonList.get(gamepad.id);
|
var buttonListEntry = _buttonList.get(gamepad.id);
|
||||||
if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return;
|
if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return;
|
||||||
|
|
||||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
|
||||||
// This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds.
|
|
||||||
timestamp *= Constants.NS_PER_MS;
|
|
||||||
|
|
||||||
updateButtonStates(gamepad, buttonId, false);
|
updateButtonStates(gamepad, buttonId, false);
|
||||||
|
|
||||||
if (getInputByButton(gamepad, buttonId)?.justReleased ?? false)
|
if (getInputByButton(gamepad, buttonId)?.justReleased ?? false)
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ class AdMobUtil
|
|||||||
|
|
||||||
Admob.configureUnity(Admob.getTCFConsentForPurpose(0) == 1, StringTools.startsWith(Admob.getUSPrivacy(), '1Y'));
|
Admob.configureUnity(Admob.getTCFConsentForPurpose(0) == 1, StringTools.startsWith(Admob.getUSPrivacy(), '1Y'));
|
||||||
|
|
||||||
|
Admob.configurePangle(Admob.getTCFConsentForPurpose(0) == 1, StringTools.startsWith(Admob.getUSPrivacy(), '1Y'));
|
||||||
|
|
||||||
|
Admob.configureVungle(StringTools.startsWith(Admob.getUSPrivacy(), '1Y'));
|
||||||
|
|
||||||
Admob.init(#if TESTING_ADS true #else false #end);
|
Admob.init(#if TESTING_ADS true #else false #end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package funkin.mobile.util;
|
package funkin.mobile.util;
|
||||||
|
|
||||||
#if ios
|
#if ios
|
||||||
import funkin.external.apple.FNFCUtil as NativeFNFCUtil;
|
import funkin.external.apple.FNFCExtern;
|
||||||
#end
|
#end
|
||||||
#if android
|
#if android
|
||||||
import funkin.external.android.JNIUtil;
|
import funkin.external.android.JNIUtil;
|
||||||
@@ -13,7 +13,7 @@ import flixel.util.FlxSignal;
|
|||||||
/**
|
/**
|
||||||
* A class for handling the flow of loading FNFC song packs on mobile.
|
* A class for handling the flow of loading FNFC song packs on mobile.
|
||||||
*/
|
*/
|
||||||
class FNFCUtil
|
class FNFCProvider
|
||||||
{
|
{
|
||||||
public static var onFNFCOpen:FlxTypedSignal<String->Void>;
|
public static var onFNFCOpen:FlxTypedSignal<String->Void>;
|
||||||
|
|
||||||
@@ -22,10 +22,9 @@ class FNFCUtil
|
|||||||
onFNFCOpen = new FlxTypedSignal<String->Void>();
|
onFNFCOpen = new FlxTypedSignal<String->Void>();
|
||||||
|
|
||||||
#if ios
|
#if ios
|
||||||
FlxG.stage.window.onDropFile.add(function(_)
|
FlxG.stage.window.onDropFile.add(function(path:String, state:String, x:Float, y:Float):Void
|
||||||
{
|
{
|
||||||
final url = queryFNFC();
|
queryFNFC();
|
||||||
if (url != null) getFNFCFromURL(url);
|
|
||||||
});
|
});
|
||||||
#elseif android
|
#elseif android
|
||||||
CallbackUtil.onFNFCOpen.add(onFNFCOpen.dispatch);
|
CallbackUtil.onFNFCOpen.add(onFNFCOpen.dispatch);
|
||||||
@@ -52,7 +51,7 @@ class FNFCUtil
|
|||||||
private static function getFNFCFromURL(url:String):Void
|
private static function getFNFCFromURL(url:String):Void
|
||||||
{
|
{
|
||||||
var cURL:cpp.ConstCharStar = cast url;
|
var cURL:cpp.ConstCharStar = cast url;
|
||||||
NativeFNFCUtil.copyFNFCIntoCache(cURL, cpp.Callable.fromStaticFunction(fnfcCallback));
|
FNFCExtern.copyFNFCIntoCache(cURL, cpp.Callable.fromStaticFunction(fnfcCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
@:noCompletion
|
@:noCompletion
|
||||||
@@ -5,8 +5,9 @@ import funkin.external.apple.ScreenUtil as NativeScreenUtil;
|
|||||||
#elseif android
|
#elseif android
|
||||||
import funkin.external.android.ScreenUtil as NativeScreenUtil;
|
import funkin.external.android.ScreenUtil as NativeScreenUtil;
|
||||||
#end
|
#end
|
||||||
import lime.math.Rectangle;
|
|
||||||
import lime.system.System;
|
import lime.system.System;
|
||||||
|
import lime.app.Application;
|
||||||
|
import openfl.geom.Rectangle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Utility class to get mobile screen related informations.
|
* A Utility class to get mobile screen related informations.
|
||||||
@@ -22,6 +23,9 @@ class ScreenUtil
|
|||||||
{
|
{
|
||||||
final notchRect:Rectangle = new Rectangle();
|
final notchRect:Rectangle = new Rectangle();
|
||||||
|
|
||||||
|
notchRect.x = 0.0;
|
||||||
|
notchRect.y = 0.0;
|
||||||
|
|
||||||
#if android
|
#if android
|
||||||
final rectDimensions:Array<Array<Float>> = [[], [], [], []];
|
final rectDimensions:Array<Array<Float>> = [[], [], [], []];
|
||||||
|
|
||||||
@@ -59,32 +63,24 @@ class ScreenUtil
|
|||||||
var bottomInset:Float = -1;
|
var bottomInset:Float = -1;
|
||||||
var deviceWidth:Float = -1;
|
var deviceWidth:Float = -1;
|
||||||
var deviceHeight:Float = -1;
|
var deviceHeight:Float = -1;
|
||||||
var displayOrientation:DisplayOrientation = System.getDisplayOrientation(0);
|
|
||||||
|
|
||||||
NativeScreenUtil.getSafeAreaInsets(cpp.RawPointer.addressOf(topInset), cpp.RawPointer.addressOf(bottomInset), cpp.RawPointer.addressOf(leftInset),
|
NativeScreenUtil.getSafeAreaInsets(cpp.RawPointer.addressOf(topInset), cpp.RawPointer.addressOf(bottomInset), cpp.RawPointer.addressOf(leftInset),
|
||||||
cpp.RawPointer.addressOf(rightInset));
|
cpp.RawPointer.addressOf(rightInset));
|
||||||
|
|
||||||
NativeScreenUtil.getScreenSize(cpp.RawPointer.addressOf(deviceWidth), cpp.RawPointer.addressOf(deviceHeight));
|
NativeScreenUtil.getScreenSize(cpp.RawPointer.addressOf(deviceWidth), cpp.RawPointer.addressOf(deviceHeight));
|
||||||
|
|
||||||
notchRect.x = 0;
|
|
||||||
notchRect.y = 0.0;
|
|
||||||
|
|
||||||
// Calculate the rectangle dimensions for the notch
|
// Calculate the rectangle dimensions for the notch
|
||||||
// Note: iOS only spits out *insets* for "safe areas", so we can only get a broad position for the notch
|
// Note: iOS only spits out *insets* for "safe areas", so we can only get a broad position for the notch
|
||||||
// left + right insets are the same, so we can use either
|
|
||||||
// Note: *inset* is the distance from the edge of the screen where a safe area gets defined
|
// Note: *inset* is the distance from the edge of the screen where a safe area gets defined
|
||||||
// see: https://developer.apple.com/documentation/uikit/uiview/safeareainsets
|
// see: https://developer.apple.com/documentation/uikit/uiview/safeareainsets
|
||||||
switch (displayOrientation)
|
switch (System.getDisplayOrientation(Application.current.window.display))
|
||||||
{
|
{
|
||||||
case DISPLAY_ORIENTATION_LANDSCAPE: // landscape
|
case DISPLAY_ORIENTATION_LANDSCAPE: // landscape
|
||||||
notchRect.width = leftInset + rightInset;
|
notchRect.width = leftInset;
|
||||||
notchRect.height = bottomInset - topInset;
|
notchRect.height = deviceHeight;
|
||||||
notchRect.y = topInset;
|
|
||||||
case DISPLAY_ORIENTATION_LANDSCAPE_FLIPPED: // landscape
|
case DISPLAY_ORIENTATION_LANDSCAPE_FLIPPED: // landscape
|
||||||
notchRect.width = leftInset + rightInset;
|
notchRect.width = leftInset;
|
||||||
notchRect.height = bottomInset - topInset;
|
notchRect.height = deviceHeight;
|
||||||
notchRect.y = topInset;
|
notchRect.x = deviceWidth - rightInset;
|
||||||
notchRect.x = deviceWidth - notchRect.width; // move notchRect if we are flipped, notch is at the right of screen
|
|
||||||
case DISPLAY_ORIENTATION_PORTRAIT: // portrait
|
case DISPLAY_ORIENTATION_PORTRAIT: // portrait
|
||||||
notchRect.width = deviceWidth;
|
notchRect.width = deviceWidth;
|
||||||
notchRect.height = topInset;
|
notchRect.height = topInset;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package funkin.modding;
|
package funkin.modding;
|
||||||
|
|
||||||
import polymod.Polymod;
|
import polymod.Polymod.PolymodError;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
class PolymodErrorHandler
|
class PolymodErrorHandler
|
||||||
@@ -10,67 +10,206 @@ class PolymodErrorHandler
|
|||||||
// Perform an action based on the error code.
|
// Perform an action based on the error code.
|
||||||
switch (error.code)
|
switch (error.code)
|
||||||
{
|
{
|
||||||
case FRAMEWORK_INIT, FRAMEWORK_AUTODETECT, SCRIPT_PARSING:
|
//
|
||||||
// Unimportant.
|
// Mod Metadata Parsing Errors
|
||||||
return;
|
//
|
||||||
|
|
||||||
case MOD_LOAD_PREPARE, MOD_LOAD_DONE:
|
case MOD_MISSING_DIRECTORY:
|
||||||
logInfo('LOADING MOD - ${error.message}');
|
// A mod directory was included in the list of mods to load, but it isn't installed.
|
||||||
|
trace(' WARNING '.warning() + 'Tried to load a mod that was not installed: ${error.message}');
|
||||||
case MISSING_ICON:
|
|
||||||
logWarn('A mod is missing an icon. Please add one.');
|
|
||||||
|
|
||||||
case SCRIPT_PARSE_ERROR:
|
|
||||||
// A syntax error when parsing a script.
|
|
||||||
logError(error.message);
|
|
||||||
// Notify the user via popup.
|
// Notify the user via popup.
|
||||||
funkin.util.WindowUtil.showError('Polymod Script Parsing Error', error.message);
|
funkin.util.WindowUtil.showError('Mod Load Error', error.message);
|
||||||
|
|
||||||
|
case MOD_MISSING_ID:
|
||||||
|
// A mod ID was included in the list of mods to load, but it isn't installed.
|
||||||
|
trace(' WARNING '.warning() + ' Tried to load a mod that was not installed: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Load Error', error.message);
|
||||||
|
|
||||||
|
case MOD_MISSING_METADATA:
|
||||||
|
// A mod ID was included in the list of mods to load, but the mod folder doesn't have metadata.
|
||||||
|
trace(' ERROR '.error() + ' Tried to load a mod with no metadata: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Load Error', error.message);
|
||||||
|
|
||||||
|
case MOD_METADATA_PARSE_FAILED:
|
||||||
|
// Polymod tries to load the mod's metadata, but it could not be parsed.
|
||||||
|
trace(' ERROR '.error() + ' Failed to parse mod metadata: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Metadata Parse Error', error.message);
|
||||||
|
|
||||||
|
case MOD_VERSION_PARSE_FAILED:
|
||||||
|
// Polymod tries to load the mod's version, but it could not be parsed.
|
||||||
|
trace(' ERROR '.error() + ' Failed to parse mod version: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Version Parse Error', error.message);
|
||||||
|
|
||||||
|
case MOD_API_VERSION_PARSE_FAILED:
|
||||||
|
// Polymod tries to load the mod's API version, but it could not be parsed.
|
||||||
|
trace(' ERROR '.error() + ' Failed to parse mod API version: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod API Version Parse Error', error.message);
|
||||||
|
|
||||||
|
case MOD_MISSING_ICON:
|
||||||
|
// A mod is missing an icon.
|
||||||
|
trace(' WARNING '.warning() + ' A mod is missing an icon: ${error.message}');
|
||||||
|
|
||||||
|
//
|
||||||
|
// Mod Loading Errors
|
||||||
|
//
|
||||||
|
|
||||||
|
case MOD_API_VERSION_MISMATCH:
|
||||||
|
// Polymod tried to load a mod but failed because it has the wrong API version.
|
||||||
|
// This is an important issue that requires user attention so it can be resolved by the mod developer.
|
||||||
|
trace(' WARNING '.warning() + ' Failed to load mod - ${error.message}');
|
||||||
|
|
||||||
|
var regex:EReg = ~/Mod "([-_a-zA-Z0-9]+)" is not compatible with API version "(.*?)", got "(.*?)"/;
|
||||||
|
if (regex.match(error.message))
|
||||||
|
{
|
||||||
|
// Notify the user via formatted popup.
|
||||||
|
var modId:String = regex.matched(1);
|
||||||
|
// var apiVersion:String = regex.matched(2);
|
||||||
|
var modVersion:String = regex.matched(3);
|
||||||
|
|
||||||
|
var message:String = 'Installed mod "$modId" was built for modding version "v$modVersion". It is not compatible with game version ${Constants.GENERATED_BY}, and must be skipped.'
|
||||||
|
+ '\n\nPlease inform the mod developer that "$modId" must be updated for compatibility.';
|
||||||
|
|
||||||
|
funkin.util.WindowUtil.showError('Mod Outdated', message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Notify the user via standard popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Outdated', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MOD_LOAD_FAILED:
|
||||||
|
// Polymod failed to load a mod. A different error would have already been logged.
|
||||||
|
trace(' WARNING '.warning() + ' Failed to load mod - ${error.message}');
|
||||||
|
|
||||||
|
case MOD_LOAD_DONE:
|
||||||
|
trace(' INFO '.info() + ' Loaded mod - ${error.message}');
|
||||||
|
|
||||||
|
//
|
||||||
|
// Mod Dependency Errors
|
||||||
|
//
|
||||||
|
|
||||||
|
case MOD_OPTIONAL_DEPENDENCY_UNMET:
|
||||||
|
// Polymod looked at the list of available mods, and one of them was missing an optional dependency.
|
||||||
|
trace(' INFO '.info() + ' Installed mod is missing an optional dependency: ${error.message}');
|
||||||
|
|
||||||
|
case MOD_DEPENDENCY_UNMET:
|
||||||
|
switch (error.origin)
|
||||||
|
{
|
||||||
|
case SCAN:
|
||||||
|
// Polymod looked at the list of available mods, and one of them was missing a dependency.
|
||||||
|
// This is an important warning that should be shown to the user.
|
||||||
|
trace(' WARNING '.warning() + ' Installed mod is missing a dependency: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Dependency Error', error.message);
|
||||||
|
default:
|
||||||
|
// Polymod tried to load a mod, but failed because it has a dependency that wasn't met.
|
||||||
|
// This is a major error that should be shown to the user.
|
||||||
|
trace(' ERROR '.error() + ' Failed to load mod due to missing dependency: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Dependency Error', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MOD_DEPENDENCY_VERSION_MISMATCH:
|
||||||
|
switch (error.origin)
|
||||||
|
{
|
||||||
|
case SCAN:
|
||||||
|
// Polymod looked at the list of available mods, and one of them has a dependency on a different mod,
|
||||||
|
// but the mod has the wrong version.
|
||||||
|
// This is an important warning that should be shown to the user.
|
||||||
|
trace(' WARNING '.warning() + ' Installed mod has a mismatched dependency: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Dependency Error', error.message);
|
||||||
|
default:
|
||||||
|
// Polymod tried to load a mod, but failed because one of its dependencies was the wrong version.
|
||||||
|
// This is a major error that should be shown to the user.
|
||||||
|
trace(' ERROR '.error() + ' Failed to load mod due to mismatched dependency: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Dependency Error', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MOD_DEPENDENCY_CYCLICAL:
|
||||||
|
// Polymod tried to load a mod, but failed because one of its dependencies depends on that mod.
|
||||||
|
// This is a major error that should be shown to the user.
|
||||||
|
trace(' ERROR '.error() + ' Failed to load mod due to cyclical dependency: ${error.message}');
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Mod Dependency Error', error.message);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Script Parsing Errors
|
||||||
|
//
|
||||||
|
|
||||||
|
case SCRIPT_PARSE_FAILED:
|
||||||
|
// A syntax error when parsing a script.
|
||||||
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Script Parsing Error', error.message);
|
||||||
|
|
||||||
case SCRIPT_RUNTIME_EXCEPTION:
|
case SCRIPT_RUNTIME_EXCEPTION:
|
||||||
// A runtime error when running a script.
|
// A runtime error when running a script.
|
||||||
logError(error.message);
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
// Notify the user via popup.
|
// Notify the user via popup.
|
||||||
funkin.util.WindowUtil.showError('Polymod Script Exception', error.message);
|
funkin.util.WindowUtil.showError('Script Exception', error.message);
|
||||||
case SCRIPT_CLASS_MODULE_NOT_FOUND:
|
|
||||||
// A scripted class tried to reference an unknown class or module.
|
|
||||||
logError(error.message);
|
|
||||||
|
|
||||||
// Last word is the class name.
|
case SCRIPTED_CLASS_NOT_REGISTERED:
|
||||||
var msg:String = 'Import error in ${error.origin}';
|
// Polymod attempted to initialize a scripted class, but it wasn't registered.
|
||||||
msg += '\n${error.message}';
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Script Parsing Error', error.message);
|
||||||
|
|
||||||
|
case SCRIPTED_CLASS_ALREADY_REGISTERED:
|
||||||
|
// Polymod attempted to register a scripted class, but one with the same name and package already exists.
|
||||||
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
// Notify the user via popup.
|
// Notify the user via popup.
|
||||||
funkin.util.WindowUtil.showError('Polymod Script Import Error', msg);
|
funkin.util.WindowUtil.showError('Script Parsing Error', error.message);
|
||||||
case SCRIPT_CLASS_MODULE_BLACKLISTED:
|
|
||||||
// A scripted class tried to reference a blacklisted class or module.
|
case SCRIPTED_CLASS_REDUNDANT_IMPORT:
|
||||||
logError(error.message);
|
// A scripted class imported a module that's already imported.
|
||||||
|
trace(' WARNING '.warning() + ' ' + error.message);
|
||||||
|
|
||||||
|
case SCRIPTED_CLASS_UNRESOLVED_IMPORT:
|
||||||
|
// A scripted class tried to import a module that doesn't exist.
|
||||||
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
// Notify the user via popup.
|
// Notify the user via popup.
|
||||||
funkin.util.WindowUtil.showError('Polymod Script Blacklist Violation', error.message);
|
funkin.util.WindowUtil.showError('Script Import Error', error.message);
|
||||||
|
|
||||||
|
case SCRIPTED_CLASS_BLACKLISTED_MODULE:
|
||||||
|
// A scripted class tried to import a module that's blacklisted.
|
||||||
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Script Blacklist Violation', error.message);
|
||||||
|
|
||||||
|
case SCRIPTED_CLASS_BLACKLISTED_FIELD:
|
||||||
|
// A scripted class tried to access a field that's blacklisted.
|
||||||
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
|
// Notify the user via popup.
|
||||||
|
funkin.util.WindowUtil.showError('Script Blacklist Violation', error.message);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Other Errors
|
||||||
|
//
|
||||||
|
|
||||||
|
case FRAMEWORK_INIT, MOD_DEPENDENCY_CHECK_SKIPPED, SCRIPT_PARSE_START, SCRIPT_PARSE_DONE:
|
||||||
|
// Unimportant messages that we don't need to log.
|
||||||
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Log the message based on its severity.
|
// Log the message based on its severity.
|
||||||
switch (error.severity)
|
switch (error.severity)
|
||||||
{
|
{
|
||||||
case NOTICE:
|
|
||||||
logInfo(error.message);
|
|
||||||
case WARNING:
|
|
||||||
logWarn(error.message);
|
|
||||||
case ERROR:
|
case ERROR:
|
||||||
logError(error.message);
|
trace(' ERROR '.error() + ' ' + error.message);
|
||||||
|
case WARNING:
|
||||||
|
trace(' WARNING '.warning() + ' ' + error.message);
|
||||||
|
case INFO:
|
||||||
|
trace(' INFO '.info() + ' ' + error.message);
|
||||||
|
case DEBUG:
|
||||||
|
// trace(' DEBUG '.debug() + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function logInfo(message:String):Void
|
|
||||||
{
|
|
||||||
trace(' INFO '.bg_blue().bold() + ' ${message}');
|
|
||||||
}
|
|
||||||
|
|
||||||
static function logError(message:String):Void
|
|
||||||
{
|
|
||||||
trace(' ERROR '.error() + ' ${message}');
|
|
||||||
}
|
|
||||||
|
|
||||||
static function logWarn(message:String):Void
|
|
||||||
{
|
|
||||||
trace(' WARNING '.bg_yellow().bold() + ' ${message}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ class PolymodHandler
|
|||||||
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
||||||
* while preventing mods made for future versions from being installed.
|
* while preventing mods made for future versions from being installed.
|
||||||
*/
|
*/
|
||||||
public static final API_VERSION_RULE:String = ">=0.8.0 <0.9.0";
|
public static final API_VERSION_RULE:String = '>=0.8.0 <0.9.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where relative to the executable that mods are located.
|
* Where relative to the executable that mods are located.
|
||||||
*/
|
*/
|
||||||
static final MOD_FOLDER:String =
|
static final MOD_FOLDER:String =
|
||||||
#if (REDIRECT_ASSETS_FOLDER && macos)
|
#if (REDIRECT_ASSETS_FOLDER && mac)
|
||||||
'../../../../../../../example_mods'
|
'../../../../../../../example_mods'
|
||||||
#elseif REDIRECT_ASSETS_FOLDER
|
#elseif REDIRECT_ASSETS_FOLDER
|
||||||
'../../../../example_mods'
|
'../../../../example_mods'
|
||||||
@@ -61,7 +61,7 @@ class PolymodHandler
|
|||||||
#end;
|
#end;
|
||||||
|
|
||||||
static final CORE_FOLDER:Null<String> =
|
static final CORE_FOLDER:Null<String> =
|
||||||
#if (REDIRECT_ASSETS_FOLDER && macos)
|
#if (REDIRECT_ASSETS_FOLDER && mac)
|
||||||
'../../../../../../../assets'
|
'../../../../../../../assets'
|
||||||
#elseif REDIRECT_ASSETS_FOLDER
|
#elseif REDIRECT_ASSETS_FOLDER
|
||||||
'../../../../assets'
|
'../../../../assets'
|
||||||
@@ -69,6 +69,14 @@ class PolymodHandler
|
|||||||
null
|
null
|
||||||
#end;
|
#end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populated with the directories of mods once they're successfully loaded.
|
||||||
|
*/
|
||||||
|
public static var loadedModDirs:Array<String> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populated with the IDs of mods once they're successfully loaded.
|
||||||
|
*/
|
||||||
public static var loadedModIds:Array<String> = [];
|
public static var loadedModIds:Array<String> = [];
|
||||||
|
|
||||||
// Use SysZipFileSystem on native and MemoryZipFilesystem on web.
|
// Use SysZipFileSystem on native and MemoryZipFilesystem on web.
|
||||||
@@ -92,7 +100,7 @@ class PolymodHandler
|
|||||||
createModRoot();
|
createModRoot();
|
||||||
#end
|
#end
|
||||||
trace('Initializing Polymod (using all mods)...');
|
trace('Initializing Polymod (using all mods)...');
|
||||||
loadModsById(getAllModIds());
|
loadModsByDir(getAllModDirs());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +113,7 @@ class PolymodHandler
|
|||||||
createModRoot();
|
createModRoot();
|
||||||
#end
|
#end
|
||||||
trace('Initializing Polymod (using configured mods)...');
|
trace('Initializing Polymod (using configured mods)...');
|
||||||
loadModsById(Save.instance.enabledModIds.value);
|
loadModsByDir(Save.instance.enabledModDirs.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,22 +127,22 @@ class PolymodHandler
|
|||||||
#end
|
#end
|
||||||
// We still need to configure the debug print calls etc.
|
// We still need to configure the debug print calls etc.
|
||||||
trace('Initializing Polymod (using no mods)...');
|
trace('Initializing Polymod (using no mods)...');
|
||||||
loadModsById([]);
|
loadModsByDir([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all the mods with the given ids.
|
* Load all the mods with the directories they're in.
|
||||||
* @param ids The ORDERED list of mod ids to load.
|
* @param dirs The ORDERED list of mod ids to load.
|
||||||
*/
|
*/
|
||||||
public static function loadModsById(ids:Array<String>):Void
|
public static function loadModsByDir(dirs:Array<String>):Void
|
||||||
{
|
{
|
||||||
if (ids.length == 0)
|
if (dirs.length == 0)
|
||||||
{
|
{
|
||||||
trace('You attempted to load zero mods.');
|
trace('You attempted to load zero mods.');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Attempting to load ${ids.length} mods...');
|
trace('Attempting to load ${dirs.length} mods...');
|
||||||
}
|
}
|
||||||
|
|
||||||
buildImports();
|
buildImports();
|
||||||
@@ -145,7 +153,7 @@ class PolymodHandler
|
|||||||
// Root directory for all mods.
|
// Root directory for all mods.
|
||||||
modRoot: MOD_FOLDER,
|
modRoot: MOD_FOLDER,
|
||||||
// The directories for one or more mods to load.
|
// The directories for one or more mods to load.
|
||||||
dirs: ids,
|
dirs: dirs,
|
||||||
// Framework being used to load assets.
|
// Framework being used to load assets.
|
||||||
framework: OPENFL,
|
framework: OPENFL,
|
||||||
// The current version of our API.
|
// The current version of our API.
|
||||||
@@ -182,18 +190,20 @@ class PolymodHandler
|
|||||||
{
|
{
|
||||||
if (loadedModList.length == 0)
|
if (loadedModList.length == 0)
|
||||||
{
|
{
|
||||||
trace('Mod loading complete. We loaded no mods / ${ids.length} mods.');
|
trace('Mod loading complete. We loaded no mods / ${dirs.length} mods.');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Mod loading complete. We loaded ${loadedModList.length} / ${ids.length} mods.');
|
trace('Mod loading complete. We loaded ${loadedModList.length} / ${dirs.length} mods.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedModIds = [];
|
loadedModIds = [];
|
||||||
|
loadedModDirs = [];
|
||||||
for (mod in loadedModList)
|
for (mod in loadedModList)
|
||||||
{
|
{
|
||||||
trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]');
|
trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]');
|
||||||
|
loadedModDirs.push(mod.dirName);
|
||||||
loadedModIds.push(mod.id);
|
loadedModIds.push(mod.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,25 +257,13 @@ class PolymodHandler
|
|||||||
static function buildImports():Void
|
static function buildImports():Void
|
||||||
{
|
{
|
||||||
// Add default imports for common classes.
|
// Add default imports for common classes.
|
||||||
static final DEFAULT_IMPORTS:Array<Class<Dynamic>> = [
|
static final DEFAULT_IMPORTS:Array<Class<Dynamic>> = [funkin.Assets, funkin.Paths, funkin.Preferences, funkin.util.Constants, flixel.FlxG];
|
||||||
funkin.Assets,
|
|
||||||
funkin.Paths,
|
|
||||||
funkin.Preferences,
|
|
||||||
funkin.util.Constants,
|
|
||||||
flixel.FlxG
|
|
||||||
];
|
|
||||||
|
|
||||||
for (cls in DEFAULT_IMPORTS)
|
for (cls in DEFAULT_IMPORTS)
|
||||||
{
|
{
|
||||||
Polymod.addDefaultImport(cls);
|
Polymod.addDefaultImport(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add import aliases for certain classes.
|
|
||||||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
|
||||||
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
|
||||||
|
|
||||||
Polymod.addImportAlias('funkin.data.event.SongEventSchema', funkin.data.event.SongEventSchema.SongEventSchemaRaw);
|
|
||||||
|
|
||||||
// `lime.utils.Assets` literally just has a private `resolveClass` function for some reason? so we replace it with our own.
|
// `lime.utils.Assets` literally just has a private `resolveClass` function for some reason? so we replace it with our own.
|
||||||
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||||
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||||
@@ -419,6 +417,11 @@ class PolymodHandler
|
|||||||
// Disallow direct manipulation of save data.
|
// Disallow direct manipulation of save data.
|
||||||
Polymod.blacklistStaticFields(flixel.FlxG, ['save']);
|
Polymod.blacklistStaticFields(flixel.FlxG, ['save']);
|
||||||
|
|
||||||
|
// `haxe.Unserializer`
|
||||||
|
// Just to be double-sure, lets blacklist some fields of the Unserializer to make it harder to use if you DO get one.
|
||||||
|
Polymod.blacklistStaticFields(haxe.Unserializer, ['run']);
|
||||||
|
Polymod.blacklistInstanceFields(haxe.Unserializer, ['unserialize']);
|
||||||
|
|
||||||
// `funkin.save.Save`
|
// `funkin.save.Save`
|
||||||
// Direct access to save data is important for scripts (like checking unlocks),
|
// Direct access to save data is important for scripts (like checking unlocks),
|
||||||
// but we don't want scripts to be able to perform operations like writing scores.
|
// but we don't want scripts to be able to perform operations like writing scores.
|
||||||
@@ -427,6 +430,12 @@ class PolymodHandler
|
|||||||
'clearData', // No score manipulation please
|
'clearData', // No score manipulation please
|
||||||
'setLevelScore', 'setSongScore', 'applySongRank']);
|
'setLevelScore', 'setSongScore', 'applySongRank']);
|
||||||
|
|
||||||
|
// `openfl.filesystem.FileStream`, `openfl.net.Socket`, `openfl.utils.ByteArray.ByteArrayData`
|
||||||
|
// Returns `Unseralizer.run` if encoded in HXSF format, though it does have to be seralized correctly for the exploit to work.
|
||||||
|
#if !html5 Polymod.blacklistInstanceFields(openfl.filesystem.FileStream, ['readObject']); #end
|
||||||
|
Polymod.blacklistInstanceFields(openfl.net.Socket, ['readObject']);
|
||||||
|
Polymod.blacklistInstanceFields(openfl.utils.ByteArray.ByteArrayData, ['readObject']);
|
||||||
|
|
||||||
// `funkin.api.*`
|
// `funkin.api.*`
|
||||||
// Contains functions which may allow for cheating and such.
|
// Contains functions which may allow for cheating and such.
|
||||||
for (cls in ClassMacro.listClassesInPackage('funkin.api'))
|
for (cls in ClassMacro.listClassesInPackage('funkin.api'))
|
||||||
@@ -455,15 +464,6 @@ class PolymodHandler
|
|||||||
Polymod.blacklistImport(className);
|
Polymod.blacklistImport(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `funkin.api.newgrounds.*`
|
|
||||||
// Contains functions which allow for cheating medals and leaderboards.
|
|
||||||
for (cls in ClassMacro.listClassesInPackage('funkin.api.newgrounds'))
|
|
||||||
{
|
|
||||||
if (cls == null) continue;
|
|
||||||
var className:String = Type.getClassName(cls);
|
|
||||||
Polymod.blacklistImport(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `io.newgrounds.*`
|
// `io.newgrounds.*`
|
||||||
// Contains functions which allow for cheating medals and leaderboards.
|
// Contains functions which allow for cheating medals and leaderboards.
|
||||||
for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))
|
for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))
|
||||||
@@ -520,11 +520,6 @@ class PolymodHandler
|
|||||||
var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault();
|
var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault();
|
||||||
// Ensure TXT files have merge support.
|
// Ensure TXT files have merge support.
|
||||||
output.addType('txt', TextFileFormat.LINES);
|
output.addType('txt', TextFileFormat.LINES);
|
||||||
// Ensure script files have merge support.
|
|
||||||
output.addType('hscript', TextFileFormat.PLAINTEXT);
|
|
||||||
output.addType('hxs', TextFileFormat.PLAINTEXT);
|
|
||||||
output.addType('hxc', TextFileFormat.PLAINTEXT);
|
|
||||||
output.addType('hx', TextFileFormat.PLAINTEXT);
|
|
||||||
|
|
||||||
// You can specify the format of a specific file, with file extension.
|
// You can specify the format of a specific file, with file extension.
|
||||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||||
@@ -569,18 +564,28 @@ class PolymodHandler
|
|||||||
return modIds;
|
return modIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of ALL mod directory names, including disabled mods.
|
||||||
|
* @return An array of mod direcotry names
|
||||||
|
*/
|
||||||
|
public static function getAllModDirs():Array<String>
|
||||||
|
{
|
||||||
|
var modDirs:Array<String> = [for (i in getAllMods()) i.dirName];
|
||||||
|
return modDirs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a list of metadata for all enabled mods.
|
* Retrieve a list of metadata for all enabled mods.
|
||||||
* @return An array of mod metadata
|
* @return An array of mod metadata
|
||||||
*/
|
*/
|
||||||
public static function getEnabledMods():Array<ModMetadata>
|
public static function getEnabledMods():Array<ModMetadata>
|
||||||
{
|
{
|
||||||
var modIds:Array<String> = Save.instance.enabledModIds.value;
|
var modDirs:Array<String> = Save.instance.enabledModDirs.value;
|
||||||
var modMetadata:Array<ModMetadata> = getAllMods();
|
var modMetadata:Array<ModMetadata> = getAllMods();
|
||||||
var enabledMods:Array<ModMetadata> = [];
|
var enabledMods:Array<ModMetadata> = [];
|
||||||
for (item in modMetadata)
|
for (item in modMetadata)
|
||||||
{
|
{
|
||||||
if (modIds.indexOf(item.id) != -1)
|
if (modDirs.indexOf(item.dirName) != -1)
|
||||||
{
|
{
|
||||||
enabledMods.push(item);
|
enabledMods.push(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class HitNoteScriptEvent extends NoteScriptEvent
|
|||||||
/**
|
/**
|
||||||
* The score the player received for hitting the note.
|
* The score the player received for hitting the note.
|
||||||
*/
|
*/
|
||||||
public var score:Int;
|
public var score:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the hit causes a combo break.
|
* If the hit causes a combo break.
|
||||||
@@ -158,7 +158,7 @@ class HitNoteScriptEvent extends NoteScriptEvent
|
|||||||
*/
|
*/
|
||||||
public var doesNotesplash:Bool = false;
|
public var doesNotesplash:Bool = false;
|
||||||
|
|
||||||
public function new(note:NoteSprite, healthChange:Float, score:Int, judgement:String, isComboBreak:Bool, comboCount:Int = 0, hitDiff:Float = 0,
|
public function new(note:NoteSprite, healthChange:Float, score:Float, judgement:String, isComboBreak:Bool, comboCount:Int = 0, hitDiff:Float = 0,
|
||||||
doesNotesplash:Bool = false):Void
|
doesNotesplash:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(NOTE_HIT, note, healthChange, comboCount, true);
|
super(NOTE_HIT, note, healthChange, comboCount, true);
|
||||||
@@ -200,7 +200,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent
|
|||||||
/**
|
/**
|
||||||
* How much score should be lost when this ghost note is pressed.
|
* How much score should be lost when this ghost note is pressed.
|
||||||
*/
|
*/
|
||||||
public var scoreChange(default, default):Int;
|
public var scoreChange(default, default):Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to play the record scratch sound.
|
* Whether to play the record scratch sound.
|
||||||
@@ -212,7 +212,7 @@ class GhostMissNoteScriptEvent extends ScriptEvent
|
|||||||
*/
|
*/
|
||||||
public var playAnim(default, default):Bool;
|
public var playAnim(default, default):Bool;
|
||||||
|
|
||||||
public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
|
public function new(dir:NoteDirection, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Float):Void
|
||||||
{
|
{
|
||||||
super(NOTE_GHOST_MISS, true);
|
super(NOTE_GHOST_MISS, true);
|
||||||
this.dir = dir;
|
this.dir = dir;
|
||||||
@@ -239,7 +239,7 @@ class HoldNoteScriptEvent extends NoteScriptEvent
|
|||||||
/**
|
/**
|
||||||
* The score the player received for hitting the note.
|
* The score the player received for hitting the note.
|
||||||
*/
|
*/
|
||||||
public var score:Int;
|
public var score:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the hit causes a combo break.
|
* If the hit causes a combo break.
|
||||||
@@ -257,7 +257,7 @@ class HoldNoteScriptEvent extends NoteScriptEvent
|
|||||||
*/
|
*/
|
||||||
public var doesNotesplash:Bool = false;
|
public var doesNotesplash:Bool = false;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, holdNote:SustainTrail, healthChange:Float, score:Int, isComboBreak:Bool, comboCount:Int = 0,
|
public function new(type:ScriptEventType, holdNote:SustainTrail, healthChange:Float, score:Float, isComboBreak:Bool, comboCount:Int = 0,
|
||||||
cancelable:Bool = false):Void
|
cancelable:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(type, null, healthChange, comboCount, true);
|
super(type, null, healthChange, comboCount, true);
|
||||||
|
|||||||
@@ -605,6 +605,9 @@ class PauseSubState extends MusicBeatSubState
|
|||||||
// ===============
|
// ===============
|
||||||
var fastOffset:Bool = false;
|
var fastOffset:Bool = false;
|
||||||
var lastOffsetPress:Float = 0;
|
var lastOffsetPress:Float = 0;
|
||||||
|
#if !mobile
|
||||||
|
var offset:Float = Preferences.globalOffset ?? 0;
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process user inputs every frame.
|
* Process user inputs every frame.
|
||||||
@@ -680,7 +683,6 @@ class PauseSubState extends MusicBeatSubState
|
|||||||
function handleModifyingOffsets():Bool
|
function handleModifyingOffsets():Bool
|
||||||
{
|
{
|
||||||
#if !mobile
|
#if !mobile
|
||||||
var offset:Int = Preferences.globalOffset ?? 0;
|
|
||||||
if (FlxG.keys.pressed.SHIFT && (controls.UI_UP || controls.UI_DOWN))
|
if (FlxG.keys.pressed.SHIFT && (controls.UI_UP || controls.UI_DOWN))
|
||||||
{
|
{
|
||||||
lastOffsetPress += FlxG.elapsed;
|
lastOffsetPress += FlxG.elapsed;
|
||||||
@@ -697,20 +699,20 @@ class PauseSubState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
offset += (controls.UI_UP_P || controls.UI_UP) ? 1 : -1;
|
offset += (controls.UI_UP_P || controls.UI_UP) ? 1 : -1;
|
||||||
|
|
||||||
offsetText.text = 'Global Offset: ${offset}ms';
|
offsetText.text = 'Global Offset: ${Std.int(offset)}ms';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
offset += (controls.UI_UP_P || controls.UI_UP) ? 1 : -1;
|
offset += ((controls.UI_UP_P || controls.UI_UP) ? 1 : -1) * (FlxG.elapsed * 30);
|
||||||
|
|
||||||
offsetText.text = 'Global Offset: ${offset}ms';
|
offsetText.text = 'Global Offset: ${Std.int(offset)}ms';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset > 1500) offset = 1500;
|
if (offset > 1500) offset = 1500;
|
||||||
if (offset < -1500) offset = -1500;
|
if (offset < -1500) offset = -1500;
|
||||||
|
|
||||||
Preferences.globalOffset = offset;
|
Preferences.globalOffset = Std.int(offset);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import funkin.play.components.HealthIcon;
|
|||||||
import funkin.play.components.PopUpStuff;
|
import funkin.play.components.PopUpStuff;
|
||||||
import funkin.play.components.Subtitles;
|
import funkin.play.components.Subtitles;
|
||||||
import funkin.play.cutscene.dialogue.Conversation;
|
import funkin.play.cutscene.dialogue.Conversation;
|
||||||
import funkin.play.cutscene.VanillaCutscenes;
|
|
||||||
import funkin.play.cutscene.VideoCutscene;
|
import funkin.play.cutscene.VideoCutscene;
|
||||||
import funkin.play.notes.NoteDirection;
|
import funkin.play.notes.NoteDirection;
|
||||||
import funkin.play.notes.notekind.NoteKindManager;
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
@@ -239,9 +238,10 @@ class PlayState extends MusicBeatSubState
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The player's current score.
|
* The player's current score.
|
||||||
* TODO: Move this to its own class.
|
* This needs to be a float because you gain partial points as you hold a hold note,
|
||||||
|
* possibly less than one point each update depending on your framerate.
|
||||||
*/
|
*/
|
||||||
public var songScore:Int = 0;
|
public var songScore:Float = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start at this point in the song once the countdown is done.
|
* Start at this point in the song once the countdown is done.
|
||||||
@@ -626,6 +626,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
*/
|
*/
|
||||||
public var camPause:FunkinCamera;
|
public var camPause:FunkinCamera;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The camera which contains, and controls visibility of, the fade out to the results screen.
|
||||||
|
*/
|
||||||
|
public var camTransition:FunkinCamera;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The combo popups. Includes the real-time combo counter and the rating.
|
* The combo popups. Includes the real-time combo counter and the rating.
|
||||||
*/
|
*/
|
||||||
@@ -767,8 +772,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
previousCameraFollowPoint = params.cameraFollowPoint;
|
previousCameraFollowPoint = params.cameraFollowPoint;
|
||||||
mirrorSongData = params.mirrored ?? false;
|
mirrorSongData = params.mirrored ?? false;
|
||||||
|
|
||||||
trace("Params: " + mirrorSongData + ", " + isBotPlayMode);
|
|
||||||
|
|
||||||
// Basic object initialization
|
// Basic object initialization
|
||||||
|
|
||||||
// TODO: Add something to toggle this on!
|
// TODO: Add something to toggle this on!
|
||||||
@@ -794,6 +797,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
camCutouts = new FunkinCamera('playStateCamCutouts');
|
camCutouts = new FunkinCamera('playStateCamCutouts');
|
||||||
camSubtitles = new FunkinCamera('playStateCamSubtitles');
|
camSubtitles = new FunkinCamera('playStateCamSubtitles');
|
||||||
camPause = new FunkinCamera('playStateCamPause');
|
camPause = new FunkinCamera('playStateCamPause');
|
||||||
|
camTransition = new FunkinCamera('playStateCamTransition');
|
||||||
|
|
||||||
var currentChart = currentSong.getDifficulty(currentDifficulty, currentVariation);
|
var currentChart = currentSong.getDifficulty(currentDifficulty, currentVariation);
|
||||||
var noteStyleId:Null<String> = currentChart?.noteStyle;
|
var noteStyleId:Null<String> = currentChart?.noteStyle;
|
||||||
@@ -942,19 +946,10 @@ class PlayState extends MusicBeatSubState
|
|||||||
// This gets set back to false when the chart actually starts.
|
// This gets set back to false when the chart actually starts.
|
||||||
startingSong = true;
|
startingSong = true;
|
||||||
|
|
||||||
// TODO: We hardcoded the transition into Winter Horrorland. Do this with a ScriptedSong instead.
|
// Call a script event to start the countdown.
|
||||||
if ((currentSong.id ?? '').toLowerCase() == 'winter-horrorland')
|
// Songs with cutscenes should call event.cancel().
|
||||||
{
|
// As long as they call `PlayState.instance.startCountdown()` later, the countdown will start.
|
||||||
// VanillaCutscenes will call startCountdown later.
|
startCountdown();
|
||||||
VanillaCutscenes.playHorrorStartCutscene();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Call a script event to start the countdown.
|
|
||||||
// Songs with cutscenes should call event.cancel().
|
|
||||||
// As long as they call `PlayState.instance.startCountdown()` later, the countdown will start.
|
|
||||||
startCountdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the pause button.
|
// Create the pause button.
|
||||||
#if mobile
|
#if mobile
|
||||||
@@ -1119,7 +1114,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
cameraZoomRate = Constants.DEFAULT_ZOOM_RATE;
|
cameraZoomRate = Constants.DEFAULT_ZOOM_RATE;
|
||||||
|
|
||||||
health = Constants.HEALTH_STARTING;
|
health = Constants.HEALTH_STARTING;
|
||||||
songScore = 0;
|
songScore = 0.0;
|
||||||
Highscore.tallies.combo = 0;
|
Highscore.tallies.combo = 0;
|
||||||
|
|
||||||
// so the song doesn't start too early :D
|
// so the song doesn't start too early :D
|
||||||
@@ -1242,8 +1237,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
FlxG.watch.addQuick('health', health);
|
FlxG.watch.addQuick('health', health);
|
||||||
FlxG.watch.addQuick('cameraBopIntensity', cameraBopIntensity);
|
FlxG.watch.addQuick('cameraBopIntensity', cameraBopIntensity);
|
||||||
|
|
||||||
// TODO: Add a song event for Handle GF dance speed.
|
|
||||||
|
|
||||||
// Handle player death.
|
// Handle player death.
|
||||||
if (!isInCutscene && !disableKeys)
|
if (!isInCutscene && !disableKeys)
|
||||||
{
|
{
|
||||||
@@ -1359,17 +1352,25 @@ class PlayState extends MusicBeatSubState
|
|||||||
*/
|
*/
|
||||||
function pause(mode:PauseMode = Standard, lostFocus:Bool = false):Void
|
function pause(mode:PauseMode = Standard, lostFocus:Bool = false):Void
|
||||||
{
|
{
|
||||||
if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying) return;
|
if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying || isSongEnd) return;
|
||||||
|
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case Conversation:
|
case Conversation:
|
||||||
preparePauseUI();
|
preparePauseUI();
|
||||||
openPauseSubState(Conversation, camPause, lostFocus, () -> currentConversation?.pauseMusic());
|
|
||||||
|
final event = new PauseScriptEvent(false);
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
if (!event.eventCanceled) openPauseSubState(Conversation, camPause, lostFocus, () -> currentConversation?.pauseMusic());
|
||||||
|
|
||||||
case Cutscene:
|
case Cutscene:
|
||||||
preparePauseUI();
|
preparePauseUI();
|
||||||
openPauseSubState(Cutscene, camPause, lostFocus, () -> VideoCutscene.pauseVideo());
|
|
||||||
|
final event = new PauseScriptEvent(false);
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
if (!event.eventCanceled) openPauseSubState(Cutscene, camPause, lostFocus, () -> VideoCutscene.pauseVideo());
|
||||||
|
|
||||||
default: // also known as standard
|
default: // also known as standard
|
||||||
if (!isInCountdown || isInCutscene) return;
|
if (!isInCountdown || isInCutscene) return;
|
||||||
@@ -1445,7 +1446,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
|
|
||||||
vwooshTimer.cancel();
|
vwooshTimer.cancel();
|
||||||
|
|
||||||
songScore = 0;
|
songScore = 0.0;
|
||||||
updateScoreText();
|
updateScoreText();
|
||||||
|
|
||||||
health = Constants.HEALTH_STARTING;
|
health = Constants.HEALTH_STARTING;
|
||||||
@@ -1501,7 +1502,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
dispatchEvent(eventEvent);
|
dispatchEvent(eventEvent);
|
||||||
|
|
||||||
// Calling event.cancelEvent() skips the event. Neat!
|
// Calling event.cancelEvent() skips the event. Neat!
|
||||||
if (!eventEvent.eventCanceled)
|
if (!eventEvent.eventCanceled && !shouldSubstatePause)
|
||||||
{
|
{
|
||||||
SongEventRegistry.handleEvent(event);
|
SongEventRegistry.handleEvent(event);
|
||||||
}
|
}
|
||||||
@@ -1512,15 +1513,17 @@ class PlayState extends MusicBeatSubState
|
|||||||
|
|
||||||
public override function dispatchEvent(event:ScriptEvent):Void
|
public override function dispatchEvent(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
// ORDER: Module, Song, Note, Stage, Conversation, Character
|
// ORDER: Module, Song, Events, Notes, Stage, Conversation, Characters
|
||||||
// Modules should get the first chance to cancel the event.
|
// Modules should get the first chance to cancel the event.
|
||||||
|
|
||||||
// super.dispatchEvent(event) dispatches event to module scripts.
|
// super.dispatchEvent(event) dispatches event to module scripts.
|
||||||
super.dispatchEvent(event);
|
super.dispatchEvent(event);
|
||||||
|
|
||||||
// Dispatch event to song script.
|
// Dispatch event to song script.
|
||||||
ScriptEventDispatcher.callEvent(currentSong, event);
|
ScriptEventDispatcher.callEvent(currentSong, event);
|
||||||
|
|
||||||
|
// Dispatch event to event notes
|
||||||
|
if (songEvents != null && songEvents.length > 0) SongEventRegistry.callEvent(event);
|
||||||
|
|
||||||
// Dispatch event to note kind scripts
|
// Dispatch event to note kind scripts
|
||||||
NoteKindManager.callEvent(event);
|
NoteKindManager.callEvent(event);
|
||||||
|
|
||||||
@@ -1844,8 +1847,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
iconP2?.onStepHit(Std.int(Conductor.instance.currentStep));
|
iconP2?.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||||
|
|
||||||
// Only bop camera if zoom level is below 135%
|
// Only bop camera if zoom level is below 135%
|
||||||
|
// This is an arbitrary number chosen so that the camera doesn't move insanely far in when the bop speed is fast.
|
||||||
|
final MAX_RELATIVE_CAM_ZOOM:Float = 1.35;
|
||||||
|
|
||||||
if (Preferences.zoomCamera
|
if (Preferences.zoomCamera
|
||||||
&& FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
|
&& camHUD.zoom < (MAX_RELATIVE_CAM_ZOOM * defaultHUDCameraZoom)
|
||||||
&& cameraZoomRate > 0
|
&& cameraZoomRate > 0
|
||||||
&& (Conductor.instance.currentStep + cameraZoomRateOffset * Constants.STEPS_PER_BEAT) % (cameraZoomRate * Constants.STEPS_PER_BEAT) == 0)
|
&& (Conductor.instance.currentStep + cameraZoomRateOffset * Constants.STEPS_PER_BEAT) % (cameraZoomRate * Constants.STEPS_PER_BEAT) == 0)
|
||||||
{
|
{
|
||||||
@@ -1961,12 +1967,14 @@ class PlayState extends MusicBeatSubState
|
|||||||
camCutouts.bgColor.alpha = 0; // Show the game scene behind the camera.
|
camCutouts.bgColor.alpha = 0; // Show the game scene behind the camera.
|
||||||
if (Preferences.subtitles) camSubtitles.bgColor.alpha = 0; // Show the game scene behind the camera.
|
if (Preferences.subtitles) camSubtitles.bgColor.alpha = 0; // Show the game scene behind the camera.
|
||||||
camPause.bgColor.alpha = 0; // Show the game scene behind the camera.
|
camPause.bgColor.alpha = 0; // Show the game scene behind the camera.
|
||||||
|
camTransition.bgColor.alpha = 0;
|
||||||
|
|
||||||
FlxG.cameras.reset(camGame);
|
FlxG.cameras.reset(camGame);
|
||||||
FlxG.cameras.add(camHUD, false);
|
FlxG.cameras.add(camHUD, false);
|
||||||
FlxG.cameras.add(camCutscene, false);
|
FlxG.cameras.add(camCutscene, false);
|
||||||
FlxG.cameras.add(camCutouts, false);
|
FlxG.cameras.add(camCutouts, false);
|
||||||
if (Preferences.subtitles) FlxG.cameras.add(camSubtitles, false);
|
if (Preferences.subtitles) FlxG.cameras.add(camSubtitles, false);
|
||||||
|
FlxG.cameras.add(camTransition, false);
|
||||||
FlxG.cameras.add(camPause, false);
|
FlxG.cameras.add(camPause, false);
|
||||||
|
|
||||||
// Configure camera follow point.
|
// Configure camera follow point.
|
||||||
@@ -2621,7 +2629,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
|
|
||||||
if (vocals != null)
|
if (vocals != null)
|
||||||
{
|
{
|
||||||
trace('Playing vocals...');
|
|
||||||
add(vocals);
|
add(vocals);
|
||||||
|
|
||||||
vocals.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
vocals.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||||
@@ -2701,9 +2708,9 @@ class PlayState extends MusicBeatSubState
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Add an option for this maybe?
|
final SHOW_DECIMALS:Bool = false;
|
||||||
var commaSeparated:Bool = true;
|
final COMMA_SEPARATED:Bool = true;
|
||||||
scoreText.text = 'Score: ${FlxStringUtil.formatMoney(songScore, false, commaSeparated)}';
|
scoreText.text = 'Score: ${FlxStringUtil.formatMoney(songScore, SHOW_DECIMALS, COMMA_SEPARATED)}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2762,6 +2769,16 @@ class PlayState extends MusicBeatSubState
|
|||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
if (event.eventCanceled) continue;
|
if (event.eventCanceled) continue;
|
||||||
|
|
||||||
|
if (vocals != null)
|
||||||
|
{
|
||||||
|
if (vocals.legacyVoiceSystem)
|
||||||
|
{
|
||||||
|
if (vocals.legacyVoiceUsesPlayer) vocals.playerVolume = playerVocalsVolume;
|
||||||
|
else
|
||||||
|
vocals.opponentVolume = opponentVocalsVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Command the opponent to hit the note on time.
|
// Command the opponent to hit the note on time.
|
||||||
// NOTE: This is what handles the strumline and cleaning up the note itself!
|
// NOTE: This is what handles the strumline and cleaning up the note itself!
|
||||||
opponentStrumline.hitNote(note);
|
opponentStrumline.hitNote(note);
|
||||||
@@ -2869,7 +2886,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
if (!isBotPlayMode && holdNote.scoreable)
|
if (!isBotPlayMode && holdNote.scoreable)
|
||||||
{
|
{
|
||||||
health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed;
|
health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed;
|
||||||
songScore += Std.int(Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed);
|
songScore += Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the player keeps singing while the note is held by the bot.
|
// Make sure the player keeps singing while the note is held by the bot.
|
||||||
@@ -2902,7 +2919,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
// If the base note of the hold was missed, don't penalize them more on top of that.
|
// 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 healthChangeMax = Constants.HEALTH_HOLD_DROP_PENALTY_MAX - (holdNote.hitNote ? -Constants.HEALTH_MISS_PENALTY : 0);
|
||||||
var healthChange = healthChangeUncapped.clamp(healthChangeMax, 0);
|
var healthChange = healthChangeUncapped.clamp(healthChangeMax, 0);
|
||||||
var scoreChange = Std.int(Constants.SCORE_HOLD_DROP_PENALTY_PER_SECOND * remainingLengthSec);
|
var scoreChange:Float = Constants.SCORE_HOLD_DROP_PENALTY_PER_SECOND * remainingLengthSec;
|
||||||
|
|
||||||
var event:HoldNoteScriptEvent = new HoldNoteScriptEvent(NOTE_HOLD_DROP, holdNote, healthChange, scoreChange, true, Highscore.tallies.combo);
|
var event:HoldNoteScriptEvent = new HoldNoteScriptEvent(NOTE_HOLD_DROP, holdNote, healthChange, scoreChange, true, Highscore.tallies.combo);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
@@ -2916,7 +2933,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
// Play the miss sound.
|
// Play the miss sound.
|
||||||
if (event.playSound)
|
if (event.playSound)
|
||||||
{
|
{
|
||||||
if (vocals != null) vocals.playerVolume = 0;
|
if (vocals != null)
|
||||||
|
{
|
||||||
|
if (vocals.legacyVoiceSystem && !vocals.legacyVoiceUsesPlayer) vocals.opponentVolume = 0;
|
||||||
|
vocals.playerVolume = 0;
|
||||||
|
}
|
||||||
FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6));
|
FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3018,9 +3039,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
if (targetNote == null) continue;
|
if (targetNote == null) continue;
|
||||||
|
|
||||||
// Judge and hit the note.
|
// Judge and hit the note.
|
||||||
// trace('Hit note! ${targetNote.noteData}');
|
|
||||||
goodNoteHit(targetNote, input);
|
goodNoteHit(targetNote, input);
|
||||||
// trace('Score: ${songScore}');
|
|
||||||
|
|
||||||
notesInDirection.remove(targetNote);
|
notesInDirection.remove(targetNote);
|
||||||
|
|
||||||
@@ -3094,7 +3113,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
playerStrumline.hitNote(note, !event.isComboBreak);
|
playerStrumline.hitNote(note, !event.isComboBreak);
|
||||||
if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection());
|
if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection());
|
||||||
if (note.isHoldNote && note.holdNoteSprite != null) playerStrumline.playNoteHoldCover(note.holdNoteSprite);
|
if (note.isHoldNote && note.holdNoteSprite != null) playerStrumline.playNoteHoldCover(note.holdNoteSprite);
|
||||||
if (vocals != null) vocals.playerVolume = playerVocalsVolume;
|
if (vocals != null)
|
||||||
|
{
|
||||||
|
if (vocals.legacyVoiceSystem && !vocals.legacyVoiceUsesPlayer) vocals.opponentVolume = opponentVocalsVolume;
|
||||||
|
vocals.playerVolume = playerVocalsVolume;
|
||||||
|
}
|
||||||
|
|
||||||
// Display the combo meter and add the calculation to the score.
|
// Display the combo meter and add the calculation to the score.
|
||||||
if (note.scoreable)
|
if (note.scoreable)
|
||||||
@@ -3147,7 +3170,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
var event:GhostMissNoteScriptEvent = new GhostMissNoteScriptEvent(direction, // Direction missed in.
|
var event:GhostMissNoteScriptEvent = new GhostMissNoteScriptEvent(direction, // Direction missed in.
|
||||||
hasPossibleNotes, // Whether there was a note you could have hit.
|
hasPossibleNotes, // Whether there was a note you could have hit.
|
||||||
Constants.HEALTH_GHOST_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).
|
Constants.SCORE_GHOST_MISS_PENALTY // Amount of score to add (negative).
|
||||||
);
|
);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
@@ -3266,7 +3289,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
/**
|
/**
|
||||||
* Handles applying health, score, and ratings.
|
* Handles applying health, score, and ratings.
|
||||||
*/
|
*/
|
||||||
function applyScore(score:Int, daRating:String, healthChange:Float, isComboBreak:Bool)
|
function applyScore(score:Float, daRating:String, healthChange:Float, isComboBreak:Bool)
|
||||||
{
|
{
|
||||||
switch (daRating)
|
switch (daRating)
|
||||||
{
|
{
|
||||||
@@ -3423,7 +3446,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
||||||
var data = {
|
var data = {
|
||||||
score: songScore,
|
score: Std.int(songScore),
|
||||||
tallies: {
|
tallies: {
|
||||||
sick: Highscore.tallies.sick,
|
sick: Highscore.tallies.sick,
|
||||||
good: Highscore.tallies.good,
|
good: Highscore.tallies.good,
|
||||||
@@ -3441,7 +3464,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
|
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
|
||||||
|
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
Leaderboards.submitSongScore(currentSong.id, suffixedDifficulty, songScore);
|
Leaderboards.submitSongScore(currentSong.id, suffixedDifficulty, Std.int(songScore));
|
||||||
#end
|
#end
|
||||||
|
|
||||||
if (!isPracticeMode && !isBotPlayMode)
|
if (!isPracticeMode && !isBotPlayMode)
|
||||||
@@ -3472,7 +3495,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
|
|
||||||
// Determine the score rank for this song we just finished.
|
// Determine the score rank for this song we just finished.
|
||||||
var scoreRank:Null<ScoringRank> = Scoring.calculateRank({
|
var scoreRank:Null<ScoringRank> = Scoring.calculateRank({
|
||||||
score: songScore,
|
score: Std.int(songScore),
|
||||||
tallies: {
|
tallies: {
|
||||||
sick: Highscore.tallies.sick,
|
sick: Highscore.tallies.sick,
|
||||||
good: Highscore.tallies.good,
|
good: Highscore.tallies.good,
|
||||||
@@ -3507,7 +3530,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
isNewHighscore = false;
|
isNewHighscore = false;
|
||||||
|
|
||||||
PlayStatePlaylist.campaignScore += songScore;
|
PlayStatePlaylist.campaignScore += Std.int(songScore);
|
||||||
|
|
||||||
// Pop the next song ID from the list.
|
// Pop the next song ID from the list.
|
||||||
// Returns null if the list is empty.
|
// Returns null if the list is empty.
|
||||||
@@ -3631,7 +3654,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
if (isSubState)
|
if (isSubState)
|
||||||
{
|
{
|
||||||
if (isPlaytestResults)
|
if (isPlaytestResults && !isBotPlayMode)
|
||||||
{
|
{
|
||||||
moveToResultsScreen(false, prevScoreData);
|
moveToResultsScreen(false, prevScoreData);
|
||||||
}
|
}
|
||||||
@@ -3773,13 +3796,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
FlxG.camera.targetOffset.x += 20;
|
FlxG.camera.targetOffset.x += 20;
|
||||||
|
|
||||||
// Replace zoom animation with a fade out for now.
|
// Replace zoom animation with a fade out for now.
|
||||||
FlxG.camera.fade(FlxColor.BLACK, 0.6);
|
FlxTween.tween(camHUD, {alpha: 0}, 0.6);
|
||||||
|
|
||||||
FlxTween.tween(camHUD, {alpha: 0}, 0.6, {
|
camTransition.fade(FlxColor.BLACK, 0.6, false, function()
|
||||||
onComplete: function(_)
|
{
|
||||||
{
|
moveToResultsScreen(isNewHighscore, prevScoreData);
|
||||||
moveToResultsScreen(isNewHighscore, prevScoreData);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Zoom in on Girlfriend (or BF if no GF)
|
// Zoom in on Girlfriend (or BF if no GF)
|
||||||
@@ -3833,7 +3854,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||||
prevScoreData: prevScoreData,
|
prevScoreData: prevScoreData,
|
||||||
scoreData: {
|
scoreData: {
|
||||||
score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
|
score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : Std.int(songScore),
|
||||||
tallies: {
|
tallies: {
|
||||||
sick: talliesToUse.sick,
|
sick: talliesToUse.sick,
|
||||||
good: talliesToUse.good,
|
good: talliesToUse.good,
|
||||||
@@ -3914,9 +3935,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
@:nullSafety(Off)
|
@:nullSafety(Off)
|
||||||
FlxG.camera.target = null;
|
FlxG.camera.target = null;
|
||||||
|
|
||||||
|
var adjustedDuration:Float = duration / playbackRate;
|
||||||
|
|
||||||
// Follow tween! Caching it so we can cancel/pause it later if needed.
|
// Follow tween! Caching it so we can cancel/pause it later if needed.
|
||||||
var followPos:FlxPoint = cameraFollowPoint.getPosition() - FlxPoint.weak(FlxG.camera.width * 0.5, FlxG.camera.height * 0.5);
|
var followPos:FlxPoint = cameraFollowPoint.getPosition() - FlxPoint.weak(FlxG.camera.width * 0.5, FlxG.camera.height * 0.5);
|
||||||
cameraFollowTween = FlxTween.tween(FlxG.camera.scroll, {x: followPos.x, y: followPos.y}, duration, {
|
cameraFollowTween = FlxTween.tween(FlxG.camera.scroll, {x: followPos.x, y: followPos.y}, adjustedDuration, {
|
||||||
ease: ease,
|
ease: ease,
|
||||||
onComplete: function(_)
|
onComplete: function(_)
|
||||||
{
|
{
|
||||||
@@ -3954,7 +3977,8 @@ class PlayState extends MusicBeatSubState
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Zoom tween! Caching it so we can cancel/pause it later if needed.
|
// Zoom tween! Caching it so we can cancel/pause it later if needed.
|
||||||
cameraZoomTween = FlxTween.tween(this, {currentCameraZoom: targetZoom}, duration, {ease: ease});
|
var adjustedDuration:Float = duration / playbackRate;
|
||||||
|
cameraZoomTween = FlxTween.tween(this, {currentCameraZoom: targetZoom}, adjustedDuration, {ease: ease});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3980,7 +4004,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
/**
|
/**
|
||||||
* The magical function that shall tween the scroll speed.
|
* The magical function that shall tween the scroll speed.
|
||||||
*/
|
*/
|
||||||
public function tweenScrollSpeed(?speed:Float, ?duration:Float, ?ease:Null<Float->Float>, strumlines:Array<String>):Void
|
public function tweenScrollSpeed(?speed:Float, duration:Float = 0, ?ease:Null<Float->Float>, strumlines:Array<String>):Void
|
||||||
{
|
{
|
||||||
// Cancel the current tween if it's active.
|
// Cancel the current tween if it's active.
|
||||||
cancelScrollSpeedTweens();
|
cancelScrollSpeedTweens();
|
||||||
@@ -4007,9 +4031,11 @@ class PlayState extends MusicBeatSubState
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var adjustedDuration:Float = duration / playbackRate;
|
||||||
|
|
||||||
scrollSpeedTweens.push(FlxTween.tween(strum, {
|
scrollSpeedTweens.push(FlxTween.tween(strum, {
|
||||||
'scrollSpeed': value
|
'scrollSpeed': value
|
||||||
}, duration, {ease: ease}));
|
}, adjustedDuration, {ease: ease}));
|
||||||
}
|
}
|
||||||
// make sure charts dont break if the charter is dumb and stupid
|
// make sure charts dont break if the charter is dumb and stupid
|
||||||
prevScrollTargets.push([value, i]);
|
prevScrollTargets.push([value, i]);
|
||||||
|
|||||||
@@ -941,7 +941,7 @@ class ResultState extends MusicBeatSubState
|
|||||||
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
|
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
|
||||||
var isPersonalBest = rank > Scoring.calculateRank(params?.prevScoreData);
|
var isPersonalBest = rank > Scoring.calculateRank(params?.prevScoreData);
|
||||||
|
|
||||||
if (isScoreValid && isPersonalBest)
|
if ((isScoreValid && isPersonalBest) || params.forceRankSlam)
|
||||||
{
|
{
|
||||||
trace('THE RANK IS Higher.....');
|
trace('THE RANK IS Higher.....');
|
||||||
|
|
||||||
@@ -1158,4 +1158,9 @@ typedef ResultsStateParams =
|
|||||||
* The previous score data, used for rank comparision.
|
* The previous score data, used for rank comparision.
|
||||||
*/
|
*/
|
||||||
var ?prevScoreData:SaveScoreData;
|
var ?prevScoreData:SaveScoreData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces to do the rank slamming animation in freeplay for debug purposes
|
||||||
|
*/
|
||||||
|
var ?forceRankSlam:Bool;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -253,6 +253,9 @@ class BaseCharacter extends Bopper
|
|||||||
// Set the x and y to be their original values.
|
// Set the x and y to be their original values.
|
||||||
this.resetPosition();
|
this.resetPosition();
|
||||||
|
|
||||||
|
// Resets danceEvery value to the original one.
|
||||||
|
this.danceEvery = _data.danceEvery;
|
||||||
|
|
||||||
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||||
// Make sure we are playing the idle animation
|
// Make sure we are playing the idle animation
|
||||||
// ...then update the hitbox so that this.width and this.height are correct.
|
// ...then update the hitbox so that this.width and this.height are correct.
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
|||||||
rating.velocity.y -= FlxG.random.int(140, 175);
|
rating.velocity.y -= FlxG.random.int(140, 175);
|
||||||
rating.velocity.x -= FlxG.random.int(0, 10);
|
rating.velocity.x -= FlxG.random.int(0, 10);
|
||||||
|
|
||||||
|
rating.graphic.destroyOnNoUse = false;
|
||||||
|
|
||||||
add(rating);
|
add(rating);
|
||||||
|
|
||||||
var fadeEase = noteStyle.isJudgementSpritePixel(daRating) ? EaseUtil.stepped(2) : null;
|
var fadeEase = noteStyle.isJudgementSpritePixel(daRating) ? EaseUtil.stepped(2) : null;
|
||||||
@@ -101,6 +103,8 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
|||||||
numScore.velocity.y -= FlxG.random.int(130, 150);
|
numScore.velocity.y -= FlxG.random.int(130, 150);
|
||||||
numScore.velocity.x = FlxG.random.float(-5, 5);
|
numScore.velocity.x = FlxG.random.float(-5, 5);
|
||||||
|
|
||||||
|
numScore.graphic.destroyOnNoUse = false;
|
||||||
|
|
||||||
add(numScore);
|
add(numScore);
|
||||||
|
|
||||||
var fadeEase = noteStyle.isComboNumSpritePixel(digit) ? EaseUtil.stepped(2) : null;
|
var fadeEase = noteStyle.isComboNumSpritePixel(digit) ? EaseUtil.stepped(2) : null;
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
package funkin.play.cutscene;
|
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
|
||||||
import flixel.tweens.FlxEase;
|
|
||||||
import flixel.tweens.FlxTween;
|
|
||||||
import flixel.util.FlxColor;
|
|
||||||
import funkin.audio.FunkinSound;
|
|
||||||
import flixel.util.FlxTimer;
|
|
||||||
import funkin.util.HapticUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static methods for playing cutscenes in the PlayState.
|
|
||||||
* TODO: Un-hardcode this shit!!!!!1!
|
|
||||||
*/
|
|
||||||
class VanillaCutscenes
|
|
||||||
{
|
|
||||||
static var blackScreen:FlxSprite;
|
|
||||||
|
|
||||||
static final TWEEN_DURATION:Float = 2.0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays the cutscene that appears at the start of Winter Horrorland.
|
|
||||||
* TODO: Move this to `winter-horrorland.hxc`
|
|
||||||
*/
|
|
||||||
public static function playHorrorStartCutscene():Void
|
|
||||||
{
|
|
||||||
PlayState.instance.isInCutscene = true;
|
|
||||||
PlayState.instance.camHUD.visible = false;
|
|
||||||
|
|
||||||
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
|
||||||
blackScreen.scrollFactor.set(0, 0);
|
|
||||||
blackScreen.zIndex = 1000000;
|
|
||||||
PlayState.instance.add(blackScreen);
|
|
||||||
|
|
||||||
new FlxTimer().start(0.1, function(_)
|
|
||||||
{
|
|
||||||
trace('Playing horrorland cutscene...');
|
|
||||||
PlayState.instance.remove(blackScreen);
|
|
||||||
|
|
||||||
// Force set the camera position and zoom.
|
|
||||||
PlayState.instance.cameraFollowPoint.setPosition(400, -2050);
|
|
||||||
PlayState.instance.resetCamera();
|
|
||||||
FlxG.camera.zoom = 2.5;
|
|
||||||
|
|
||||||
// Play the Sound effect.
|
|
||||||
HapticUtil.vibrate(0.1, 0.5, 1, 1);
|
|
||||||
FunkinSound.playOnce(Paths.sound('Lights_Turn_On'), function()
|
|
||||||
{
|
|
||||||
// Fade in the HUD.
|
|
||||||
trace('SFX done...');
|
|
||||||
PlayState.instance.camHUD.visible = true;
|
|
||||||
PlayState.instance.camHUD.alpha = 0.0; // Use alpha rather than visible to let us fade it in.
|
|
||||||
FlxTween.tween(PlayState.instance.camHUD, {alpha: 1.0}, TWEEN_DURATION, {ease: FlxEase.quadInOut});
|
|
||||||
|
|
||||||
// Start the countdown.
|
|
||||||
trace('Zoom out done...');
|
|
||||||
trace('Begin countdown (ends cutscene)');
|
|
||||||
PlayState.instance.startCountdown();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -70,6 +70,7 @@ class VideoCutscene
|
|||||||
{
|
{
|
||||||
if (PlayState.instance == null) return;
|
if (PlayState.instance == null) return;
|
||||||
|
|
||||||
|
#if FEATURE_VIDEO_PLAYBACK
|
||||||
if (!openfl.Assets.exists(filePath))
|
if (!openfl.Assets.exists(filePath))
|
||||||
{
|
{
|
||||||
// Display a popup.
|
// Display a popup.
|
||||||
@@ -78,8 +79,7 @@ class VideoCutscene
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
var rawFilePath = Paths.stripLibrary(filePath);
|
|
||||||
|
|
||||||
// Trigger the cutscene. Don't play the song in the background.
|
// Trigger the cutscene. Don't play the song in the background.
|
||||||
PlayState.instance.isInCutscene = true;
|
PlayState.instance.isInCutscene = true;
|
||||||
@@ -100,13 +100,18 @@ class VideoCutscene
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if NO_FEATURE_VIDEO_PLAYBACK
|
||||||
|
trace(" WARNING ".bold().bg_yellow() + " Video playback is not enabled. Calling video end callback instead.");
|
||||||
|
finishVideo();
|
||||||
|
#else
|
||||||
#if html5
|
#if html5
|
||||||
playVideoHTML5(rawFilePath);
|
playVideoHTML5(Paths.stripLibrary(filePath));
|
||||||
#elseif hxvlc
|
#elseif hxvlc
|
||||||
playVideoNative(filePath);
|
playVideoNative(filePath);
|
||||||
#else
|
#else
|
||||||
throw "No video support for this platform!";
|
throw "No video support for this platform!";
|
||||||
#end
|
#end
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isPlaying():Bool
|
public static function isPlaying():Bool
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import funkin.data.song.SongData.SongEventData;
|
|||||||
// Data from the event schema
|
// Data from the event schema
|
||||||
import funkin.data.event.SongEventSchema;
|
import funkin.data.event.SongEventSchema;
|
||||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||||
|
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handles song events which change the camera focus.
|
* This class handles song events which change the camera focus.
|
||||||
|
|||||||
@@ -139,16 +139,22 @@ class ScrollSpeedEvent extends SongEvent
|
|||||||
type: SongEventFieldType.ENUM,
|
type: SongEventFieldType.ENUM,
|
||||||
keys: ['In' => 'In', 'Out' => 'Out', 'In/Out' => 'InOut']
|
keys: ['In' => 'In', 'Out' => 'Out', 'In/Out' => 'InOut']
|
||||||
}, {
|
}, {
|
||||||
name: 'strumline',
|
name: 'advanced',
|
||||||
title: 'Target Strumline',
|
title: 'Advanced',
|
||||||
defaultValue: DEFAULT_STRUMLINE,
|
type: SongEventFieldType.FRAME,
|
||||||
type: SongEventFieldType.ENUM,
|
collapsible: true,
|
||||||
keys: ['Both' => 'both', 'Player' => 'player', 'Opponent' => 'opponent']
|
children: [{
|
||||||
}, {
|
name: 'strumline',
|
||||||
name: 'absolute',
|
title: 'Target Strumline',
|
||||||
title: 'Absolute',
|
defaultValue: DEFAULT_STRUMLINE,
|
||||||
defaultValue: DEFAULT_ABSOLUTE,
|
type: SongEventFieldType.ENUM,
|
||||||
type: SongEventFieldType.BOOL,
|
keys: ['Both' => 'both', 'Player' => 'player', 'Opponent' => 'opponent']
|
||||||
|
}, {
|
||||||
|
name: 'absolute',
|
||||||
|
title: 'Absolute',
|
||||||
|
defaultValue: DEFAULT_ABSOLUTE,
|
||||||
|
type: SongEventFieldType.BOOL,
|
||||||
|
}]
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,20 +109,26 @@ class SetHealthIconSongEvent extends SongEvent
|
|||||||
defaultValue: DEFAULT_FLIPX,
|
defaultValue: DEFAULT_FLIPX,
|
||||||
type: SongEventFieldType.BOOL,
|
type: SongEventFieldType.BOOL,
|
||||||
}, {
|
}, {
|
||||||
name: 'isPixel',
|
name: 'advanced',
|
||||||
title: 'Is Pixel?',
|
title: 'Advanced',
|
||||||
defaultValue: DEFAULT_ISPIXEL,
|
type: SongEventFieldType.FRAME,
|
||||||
type: SongEventFieldType.BOOL,
|
collapsible: true,
|
||||||
}, {
|
children: [{
|
||||||
name: 'offsetX',
|
name: 'isPixel',
|
||||||
title: 'X Offset',
|
title: 'Is Pixel?',
|
||||||
defaultValue: DEFAULT_X_OFFSET,
|
defaultValue: DEFAULT_ISPIXEL,
|
||||||
type: SongEventFieldType.FLOAT,
|
type: SongEventFieldType.BOOL,
|
||||||
}, {
|
}, {
|
||||||
name: 'offsetY',
|
name: 'offsetX',
|
||||||
title: 'Y Offset',
|
title: 'X Offset',
|
||||||
defaultValue: DEFAULT_Y_OFFSET,
|
defaultValue: DEFAULT_X_OFFSET,
|
||||||
type: SongEventFieldType.FLOAT,
|
type: SongEventFieldType.FLOAT,
|
||||||
|
}, {
|
||||||
|
name: 'offsetY',
|
||||||
|
title: 'Y Offset',
|
||||||
|
defaultValue: DEFAULT_Y_OFFSET,
|
||||||
|
type: SongEventFieldType.FLOAT,
|
||||||
|
}]
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package funkin.play.event;
|
||||||
|
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import funkin.play.character.BaseCharacter;
|
||||||
|
import funkin.play.stage.Bopper;
|
||||||
|
// Data from the chart
|
||||||
|
import funkin.data.song.SongData.SongEventData;
|
||||||
|
// Data from the event schema
|
||||||
|
import funkin.data.event.SongEventSchema;
|
||||||
|
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles song events which changes dance speed of specific character or stage prop.
|
||||||
|
*/
|
||||||
|
class SetTargetBopSpeedSongEvent extends SongEvent
|
||||||
|
{
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('SetTargetBopSpeed');
|
||||||
|
}
|
||||||
|
|
||||||
|
static final DEFAULT_TARGET:String = 'boyfriend';
|
||||||
|
|
||||||
|
public override function handleEvent(data:SongEventData):Void
|
||||||
|
{
|
||||||
|
// Does nothing if there is no PlayState camera or stage.
|
||||||
|
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
|
||||||
|
|
||||||
|
var targetName:Null<String> = data.getString('target');
|
||||||
|
if (targetName == null) targetName = DEFAULT_TARGET;
|
||||||
|
|
||||||
|
var rate = data.getFloat('rate');
|
||||||
|
if (rate == null) rate = Constants.DEFAULT_PROP_RATE;
|
||||||
|
|
||||||
|
var target:FlxSprite = null;
|
||||||
|
|
||||||
|
switch (targetName)
|
||||||
|
{
|
||||||
|
case 'boyfriend' | 'bf' | 'player':
|
||||||
|
trace('Set dance rate to $rate on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'dad' | 'opponent':
|
||||||
|
trace('Set dance rate to $rate on dad.');
|
||||||
|
target = PlayState.instance.currentStage.getDad();
|
||||||
|
case 'girlfriend' | 'gf':
|
||||||
|
trace('Set dance rate to $rate on girlfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getGirlfriend();
|
||||||
|
default:
|
||||||
|
target = PlayState.instance.currentStage.getNamedProp(targetName);
|
||||||
|
if (target == null) trace('Unknown target to set dance rate: $targetName');
|
||||||
|
else
|
||||||
|
trace('Set dance rate to $targetName from stage.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
if (Std.isOfType(target, BaseCharacter))
|
||||||
|
{
|
||||||
|
var targetChar:BaseCharacter = cast target;
|
||||||
|
targetChar.danceEvery = rate;
|
||||||
|
}
|
||||||
|
else if (Std.isOfType(target, Bopper))
|
||||||
|
{
|
||||||
|
var targetProp:Bopper = cast target;
|
||||||
|
targetProp.danceEvery = rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Unknown SetTargetBopSpeed target: $targetName');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function getTitle():String
|
||||||
|
{
|
||||||
|
return "Set Target Bop Speed";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* "target": STRING, // Name of character or prop to point to.
|
||||||
|
* "anim": STRING, // Name of animation to play.
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @return SongEventSchema
|
||||||
|
*/
|
||||||
|
public override function getEventSchema():SongEventSchema
|
||||||
|
{
|
||||||
|
return new SongEventSchema([{
|
||||||
|
name: 'target',
|
||||||
|
title: 'Target',
|
||||||
|
type: SongEventFieldType.STRING,
|
||||||
|
defaultValue: DEFAULT_TARGET,
|
||||||
|
}, {
|
||||||
|
name: 'rate',
|
||||||
|
title: 'Rate',
|
||||||
|
defaultValue: Constants.DEFAULT_PROP_RATE,
|
||||||
|
min: 0,
|
||||||
|
step: 0.25,
|
||||||
|
type: SongEventFieldType.FLOAT,
|
||||||
|
units: 'beats/dance'
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package funkin.play.event;
|
|||||||
|
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.data.event.SongEventSchema;
|
import funkin.data.event.SongEventSchema;
|
||||||
|
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameters used to initialize a song event.
|
* Parameters used to initialize a song event.
|
||||||
@@ -22,7 +24,7 @@ typedef SongEventParams =
|
|||||||
* It is used by the ScriptedSongEvent class to handle user-defined events,
|
* It is used by the ScriptedSongEvent class to handle user-defined events,
|
||||||
* and also used by other classes in this package to provide default behavior for built-in events.
|
* and also used by other classes in this package to provide default behavior for built-in events.
|
||||||
*/
|
*/
|
||||||
class SongEvent
|
class SongEvent implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* These variables are used in two different events (and may be in more), and in order not to create unnecessary variables, we store them here
|
* These variables are used in two different events (and may be in more), and in order not to create unnecessary variables, we store them here
|
||||||
@@ -99,4 +101,92 @@ class SongEvent
|
|||||||
{
|
{
|
||||||
return 'SongEvent(${this.id})';
|
return 'SongEvent(${this.id})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onScriptEvent(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onCreate(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onDestroy(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onUpdate(event:UpdateScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onStepHit(event:SongTimeScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onBeatHit(event:SongTimeScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPause(event:PauseScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onResume(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSongStart(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSongEnd(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onGameOver(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onNoteIncoming(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onNoteMiss(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSongEvent(event:SongEventScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onCountdownStart(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onCountdownStep(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onCountdownEnd(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSongLoaded(event:SongLoadScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSongRetry(event:SongRetryEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,49 @@ class SongEventHelper
|
|||||||
return easeBitmapMap.get(key);
|
return easeBitmapMap.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function getEaseRange(func:Dynamic, samples:Int):{min:Float, max:Float}
|
||||||
|
{
|
||||||
|
if (func == null || samples <= 0) return {min: 0.0, max: 1.0};
|
||||||
|
|
||||||
|
var min:Float = 0.0;
|
||||||
|
var max:Float = 1.0;
|
||||||
|
var hasValue:Bool = false;
|
||||||
|
|
||||||
|
for (i in 0...samples)
|
||||||
|
{
|
||||||
|
var t:Float = if (samples > 1) (i / (samples - 1)) else 0.0;
|
||||||
|
var raw = func(t);
|
||||||
|
if (Math.isNaN(raw)) continue;
|
||||||
|
|
||||||
|
if (!hasValue)
|
||||||
|
{
|
||||||
|
min = raw;
|
||||||
|
max = raw;
|
||||||
|
hasValue = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (raw < min) min = raw;
|
||||||
|
if (raw > max) max = raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasValue) return {min: 0.0, max: 1.0};
|
||||||
|
return {min: min, max: max};
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getEaseY(raw:Float, size:Int, range:{min:Float, max:Float}):Int
|
||||||
|
{
|
||||||
|
var v:Float = raw;
|
||||||
|
var span = range.max - range.min;
|
||||||
|
|
||||||
|
if (span > 0)
|
||||||
|
v = (raw - range.min) / span;
|
||||||
|
|
||||||
|
v = if (v < 0) 0 else if (v > 1) 1 else v;
|
||||||
|
return Std.int((1 - v) * (size - 1));
|
||||||
|
}
|
||||||
|
|
||||||
static function createBitmapFromFunc(func:Dynamic, key:String, thickness:Int = 2):BitmapData
|
static function createBitmapFromFunc(func:Dynamic, key:String, thickness:Int = 2):BitmapData
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -62,6 +105,14 @@ class SongEventHelper
|
|||||||
if (key.toLowerCase() == "instant") return bd;
|
if (key.toLowerCase() == "instant") return bd;
|
||||||
if (thickness < 1) thickness = 1;
|
if (thickness < 1) thickness = 1;
|
||||||
var half = Std.int(thickness / 2);
|
var half = Std.int(thickness / 2);
|
||||||
|
var range = getEaseRange(func, size);
|
||||||
|
|
||||||
|
// Draw horizontal lines at y=1 and y=0 for reference.
|
||||||
|
var y1 = getEaseY(1, size, range);
|
||||||
|
var y0 = getEaseY(0, size, range);
|
||||||
|
bd.fillRect(new openfl.geom.Rectangle(0, y1 - half, size, thickness), 0xFF404040);
|
||||||
|
bd.fillRect(new openfl.geom.Rectangle(0, y0 - half, size, thickness), 0xFF404040);
|
||||||
|
|
||||||
var lastY:Int = -1;
|
var lastY:Int = -1;
|
||||||
for (i in 0...size)
|
for (i in 0...size)
|
||||||
{
|
{
|
||||||
@@ -69,21 +120,20 @@ class SongEventHelper
|
|||||||
var raw = func(t);
|
var raw = func(t);
|
||||||
if (!Math.isNaN(raw))
|
if (!Math.isNaN(raw))
|
||||||
{
|
{
|
||||||
var v:Float = raw;
|
var y:Int = getEaseY(raw, size, range);
|
||||||
if (v < 0) v = 0;
|
|
||||||
if (v > 1) v = 1;
|
|
||||||
var y:Int = Std.int((1 - v) * (size - 1));
|
|
||||||
if (lastY == -1)
|
if (lastY == -1)
|
||||||
{
|
{
|
||||||
for (yy in (y - half)...(y + half + 1))
|
for (xx in (i - half)...(i + half + 1))
|
||||||
if (yy >= 0 && yy < size) bd.setPixel32(i, yy, 0xFFFFFFFF);
|
if (xx >= 0 && xx < size) for (yy in (y - half)...(y + half + 1))
|
||||||
|
if (yy >= 0 && yy < size) bd.setPixel32(xx, yy, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var a = Std.int(Math.min(y, lastY));
|
var a = Std.int(Math.min(y, lastY));
|
||||||
var b = Std.int(Math.max(y, lastY));
|
var b = Std.int(Math.max(y, lastY));
|
||||||
for (yy in a - half...b + half + 1)
|
for (xx in (i - half)...(i + half + 1))
|
||||||
if (yy >= 0 && yy < size) bd.setPixel32(i, yy, 0xFFFFFFFF);
|
if (xx >= 0 && xx < size) for (yy in a - half...b + half + 1)
|
||||||
|
if (yy >= 0 && yy < size) bd.setPixel32(xx, yy, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
lastY = y;
|
lastY = y;
|
||||||
}
|
}
|
||||||
@@ -122,6 +172,7 @@ class SongEventHelper
|
|||||||
if (baseBd == null) return null;
|
if (baseBd == null) return null;
|
||||||
var easeFunc:Dynamic = resolveEaseFuncForKey(key);
|
var easeFunc:Dynamic = resolveEaseFuncForKey(key);
|
||||||
var sizeH:Int = baseBd.height;
|
var sizeH:Int = baseBd.height;
|
||||||
|
var range = getEaseRange(easeFunc, sizeH);
|
||||||
var sprites:Array<FlxSprite> = [];
|
var sprites:Array<FlxSprite> = [];
|
||||||
for (f in 0...frameCount)
|
for (f in 0...frameCount)
|
||||||
{
|
{
|
||||||
@@ -136,10 +187,7 @@ class SongEventHelper
|
|||||||
raw = FlxEase.linear(t);
|
raw = FlxEase.linear(t);
|
||||||
}
|
}
|
||||||
if (Math.isNaN(raw)) raw = 0.0;
|
if (Math.isNaN(raw)) raw = 0.0;
|
||||||
var v:Float = raw;
|
var y:Int = getEaseY(raw, sizeH, range);
|
||||||
if (v < 0) v = 0;
|
|
||||||
if (v > 1) v = 1;
|
|
||||||
var y:Int = Std.int((1 - v) * (sizeH - 1));
|
|
||||||
var bd:BitmapData = new BitmapData(dotWidth, sizeH, false, 0xFF202223);
|
var bd:BitmapData = new BitmapData(dotWidth, sizeH, false, 0xFF202223);
|
||||||
var centerX:Int = Std.int(dotWidth / 2);
|
var centerX:Int = Std.int(dotWidth / 2);
|
||||||
for (dx in -dotRadius...dotRadius + 1)
|
for (dx in -dotRadius...dotRadius + 1)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package funkin.play.event;
|
package funkin.play.event;
|
||||||
|
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import funkin.ui.FullScreenScaleMode;
|
||||||
// Data from the chart
|
// Data from the chart
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
// Data from the event schema
|
// Data from the event schema
|
||||||
@@ -27,9 +29,10 @@ class ZoomCameraSongEvent extends SongEvent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static final DEFAULT_ZOOM:Float = 1.0;
|
public static final DEFAULT_ZOOM:Float = 1.0;
|
||||||
static final DEFAULT_DURATION:Float = 4.0;
|
public static final DEFAULT_WIDESCREEN_SCALE:Float = 0.0;
|
||||||
static final DEFAULT_MODE:String = 'direct';
|
public static final DEFAULT_DURATION:Float = 4.0;
|
||||||
|
public static final DEFAULT_MODE:String = 'direct';
|
||||||
|
|
||||||
public override function handleEvent(data:SongEventData):Void
|
public override function handleEvent(data:SongEventData):Void
|
||||||
{
|
{
|
||||||
@@ -41,6 +44,11 @@ class ZoomCameraSongEvent extends SongEvent
|
|||||||
|
|
||||||
var zoom:Float = data.getFloat('zoom') ?? DEFAULT_ZOOM;
|
var zoom:Float = data.getFloat('zoom') ?? DEFAULT_ZOOM;
|
||||||
|
|
||||||
|
var widescreenScaleX:Float = data.getFloat('widescreenScaleX') ?? DEFAULT_WIDESCREEN_SCALE;
|
||||||
|
var widescreenScaleY:Float = data.getFloat('widescreenScaleY') ?? DEFAULT_WIDESCREEN_SCALE;
|
||||||
|
|
||||||
|
var scaledZoom:Float = zoom + (zoom * calculateScale(FullScreenScaleMode.wideScale, FlxPoint.get(widescreenScaleX, widescreenScaleY)));
|
||||||
|
|
||||||
var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION;
|
var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION;
|
||||||
|
|
||||||
var mode:String = data.getString('mode') ?? DEFAULT_MODE;
|
var mode:String = data.getString('mode') ?? DEFAULT_MODE;
|
||||||
@@ -55,7 +63,7 @@ class ZoomCameraSongEvent extends SongEvent
|
|||||||
switch (ease)
|
switch (ease)
|
||||||
{
|
{
|
||||||
case 'INSTANT':
|
case 'INSTANT':
|
||||||
PlayState.instance.tweenCameraZoom(zoom, 0, isDirectMode);
|
PlayState.instance.tweenCameraZoom(scaledZoom, 0, isDirectMode);
|
||||||
default:
|
default:
|
||||||
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
|
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
|
||||||
var easeFunctionName = '$ease$easeDir';
|
var easeFunctionName = '$ease$easeDir';
|
||||||
@@ -66,10 +74,15 @@ class ZoomCameraSongEvent extends SongEvent
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayState.instance.tweenCameraZoom(zoom, durSeconds, isDirectMode, easeFunction);
|
PlayState.instance.tweenCameraZoom(scaledZoom, durSeconds, isDirectMode, easeFunction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateScale(wideScale:FlxPoint, scale:FlxPoint)
|
||||||
|
{
|
||||||
|
return (wideScale.x - 1) * scale.x + (wideScale.y - 1) * scale.y;
|
||||||
|
}
|
||||||
|
|
||||||
public override function getTitle():String
|
public override function getTitle():String
|
||||||
{
|
{
|
||||||
return 'Zoom Camera';
|
return 'Zoom Camera';
|
||||||
@@ -104,12 +117,6 @@ class ZoomCameraSongEvent extends SongEvent
|
|||||||
step: 0.5,
|
step: 0.5,
|
||||||
type: SongEventFieldType.FLOAT,
|
type: SongEventFieldType.FLOAT,
|
||||||
units: 'steps'
|
units: 'steps'
|
||||||
}, {
|
|
||||||
name: 'mode',
|
|
||||||
title: 'Mode',
|
|
||||||
defaultValue: DEFAULT_MODE,
|
|
||||||
type: SongEventFieldType.ENUM,
|
|
||||||
keys: ['Stage zoom' => 'stage', 'Absolute zoom' => 'direct']
|
|
||||||
}, {
|
}, {
|
||||||
name: 'ease',
|
name: 'ease',
|
||||||
title: 'Easing Type',
|
title: 'Easing Type',
|
||||||
@@ -122,6 +129,34 @@ class ZoomCameraSongEvent extends SongEvent
|
|||||||
defaultValue: SongEvent.DEFAULT_EASE_DIR,
|
defaultValue: SongEvent.DEFAULT_EASE_DIR,
|
||||||
type: SongEventFieldType.ENUM,
|
type: SongEventFieldType.ENUM,
|
||||||
keys: ['In' => 'In', 'Out' => 'Out', 'In/Out' => 'InOut']
|
keys: ['In' => 'In', 'Out' => 'Out', 'In/Out' => 'InOut']
|
||||||
|
}, {
|
||||||
|
name: 'advanced',
|
||||||
|
title: 'Advanced',
|
||||||
|
type: SongEventFieldType.FRAME,
|
||||||
|
collapsible: true,
|
||||||
|
children: [{
|
||||||
|
name: 'mode',
|
||||||
|
title: 'Mode',
|
||||||
|
defaultValue: DEFAULT_MODE,
|
||||||
|
type: SongEventFieldType.ENUM,
|
||||||
|
keys: ['Stage zoom' => 'stage', 'Absolute zoom' => 'direct']
|
||||||
|
}, {
|
||||||
|
name: 'widescreenScaleX',
|
||||||
|
title: 'Widescreen Scale X',
|
||||||
|
defaultValue: DEFAULT_WIDESCREEN_SCALE,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
type: SongEventFieldType.FLOAT,
|
||||||
|
units: 'x'
|
||||||
|
}, {
|
||||||
|
name: 'widescreenScaleY',
|
||||||
|
title: 'Widescreen Scale Y',
|
||||||
|
defaultValue: DEFAULT_WIDESCREEN_SCALE,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
type: SongEventFieldType.FLOAT,
|
||||||
|
units: 'x'
|
||||||
|
}]
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -972,7 +972,8 @@ class Strumline extends FlxSpriteGroup
|
|||||||
note.holdNoteSprite.hitNote = true;
|
note.holdNoteSprite.hitNote = true;
|
||||||
note.holdNoteSprite.missedNote = false;
|
note.holdNoteSprite.missedNote = false;
|
||||||
|
|
||||||
note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition;
|
note.holdNoteSprite.sustainLength = Math.min(note.holdNoteSprite.fullSustainLength,
|
||||||
|
(note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FEATURE_GHOST_TAPPING
|
#if FEATURE_GHOST_TAPPING
|
||||||
@@ -1094,6 +1095,8 @@ class Strumline extends FlxSpriteGroup
|
|||||||
splash.y = this.y;
|
splash.y = this.y;
|
||||||
splash.y -= INITIAL_OFFSET;
|
splash.y -= INITIAL_OFFSET;
|
||||||
splash.y += noteStyle.getSplashOffsets()[1] * splash.scale.y;
|
splash.y += noteStyle.getSplashOffsets()[1] * splash.scale.y;
|
||||||
|
|
||||||
|
splash.graphic.destroyOnNoUse = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1171,6 +1174,8 @@ class Strumline extends FlxSpriteGroup
|
|||||||
noteSprite.x -= NUDGE;
|
noteSprite.x -= NUDGE;
|
||||||
noteSprite.y = -9999;
|
noteSprite.y = -9999;
|
||||||
|
|
||||||
|
noteSprite.graphic.destroyOnNoUse = false;
|
||||||
|
|
||||||
if (noteKind != null) noteSprite.scoreable = noteKind.scoreable;
|
if (noteKind != null) noteSprite.scoreable = noteKind.scoreable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1212,6 +1217,8 @@ class Strumline extends FlxSpriteGroup
|
|||||||
holdNoteSprite.x -= holdNoteSprite.width / 2;
|
holdNoteSprite.x -= holdNoteSprite.width / 2;
|
||||||
holdNoteSprite.y = -9999;
|
holdNoteSprite.y = -9999;
|
||||||
|
|
||||||
|
holdNoteSprite.graphic.destroyOnNoUse = false;
|
||||||
|
|
||||||
if (noteKind != null) holdNoteSprite.scoreable = noteKind.scoreable;
|
if (noteKind != null) holdNoteSprite.scoreable = noteKind.scoreable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,14 +120,14 @@ class SustainTrail extends FlxSprite
|
|||||||
{
|
{
|
||||||
super(0, 0);
|
super(0, 0);
|
||||||
|
|
||||||
|
setupHoldNoteGraphic(noteStyle);
|
||||||
|
noteStyleOffsets = noteStyle.getHoldNoteOffsets();
|
||||||
|
|
||||||
// BASIC SETUP
|
// BASIC SETUP
|
||||||
this.sustainLength = sustainLength;
|
this.sustainLength = sustainLength;
|
||||||
this.fullSustainLength = sustainLength;
|
this.fullSustainLength = sustainLength;
|
||||||
this.noteDirection = noteDirection;
|
this.noteDirection = noteDirection;
|
||||||
|
|
||||||
setupHoldNoteGraphic(noteStyle);
|
|
||||||
noteStyleOffsets = noteStyle.getHoldNoteOffsets();
|
|
||||||
|
|
||||||
setIndices(TRIANGLE_VERTEX_INDICES);
|
setIndices(TRIANGLE_VERTEX_INDICES);
|
||||||
|
|
||||||
this.active = true; // This NEEDS to be true for the note to be drawn!
|
this.active = true; // This NEEDS to be true for the note to be drawn!
|
||||||
@@ -262,7 +262,6 @@ class SustainTrail extends FlxSprite
|
|||||||
{
|
{
|
||||||
if (s < 0.0) s = 0.0;
|
if (s < 0.0) s = 0.0;
|
||||||
|
|
||||||
if (sustainLength == s) return s;
|
|
||||||
this.sustainLength = s;
|
this.sustainLength = s;
|
||||||
triggerRedraw();
|
triggerRedraw();
|
||||||
return this.sustainLength;
|
return this.sustainLength;
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ class NoAnimNoteKind extends NoteKind
|
|||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super("noanim", "Disables singing animations", null, [], DISABLE_ANIMATIONS, null);
|
super('noanim', 'No Animation', null, [], DISABLE_ANIMATIONS, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package funkin.play.notes.notekind;
|
package funkin.play.notes.notekind;
|
||||||
|
|
||||||
import funkin.modding.events.ScriptEvent.HitNoteScriptEvent;
|
|
||||||
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,7 +9,7 @@ class NonScoreableNoteKind extends NoteKind
|
|||||||
{
|
{
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super("non_scoreable", "Non-scoreable");
|
super('non_scoreable', 'Non-scoreable');
|
||||||
scoreable = false;
|
scoreable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ class NoteKindManager
|
|||||||
return noteKinds.get(noteKind);
|
return noteKinds.get(noteKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of known valid note kinds.
|
||||||
|
* @return A list of note kinds
|
||||||
|
*/
|
||||||
|
public static function listNoteKinds():Array<String>
|
||||||
|
{
|
||||||
|
return noteKinds.keyValues();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize custom behavior for note kinds.
|
* Initialize custom behavior for note kinds.
|
||||||
*/
|
*/
|
||||||
@@ -61,7 +70,7 @@ class NoteKindManager
|
|||||||
if (noteKindClsName == 'funkin.play.notes.notekind.NoteKind'
|
if (noteKindClsName == 'funkin.play.notes.notekind.NoteKind'
|
||||||
|| noteKindClsName == 'funkin.play.notes.notekind.ScriptedNoteKind') continue;
|
|| noteKindClsName == 'funkin.play.notes.notekind.ScriptedNoteKind') continue;
|
||||||
|
|
||||||
var kind:NoteKind = Type.createInstance(noteKindCls, ["UNKNOWN"]);
|
var kind:NoteKind = Type.createInstance(noteKindCls, ['UNKNOWN']);
|
||||||
|
|
||||||
if (kind != null)
|
if (kind != null)
|
||||||
{
|
{
|
||||||
@@ -88,12 +97,9 @@ class NoteKindManager
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var script:NoteKind = ScriptedNoteKind.scriptInit(scriptedClass, "unknown");
|
var script:NoteKind = ScriptedNoteKind.scriptInit(scriptedClass, 'unknown');
|
||||||
trace(' Initialized scripted note kind: ${script.noteKind}');
|
trace(' Initialized scripted note kind: ${script.noteKind}');
|
||||||
noteKinds.set(script.noteKind, script);
|
noteKinds.set(script.noteKind, script);
|
||||||
#if FEATURE_CHART_EDITOR
|
|
||||||
ChartEditorDropdowns.NOTE_KINDS.set(script.noteKind, script.description);
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -176,15 +176,15 @@ class Scoring
|
|||||||
|
|
||||||
return switch (absTiming)
|
return switch (absTiming)
|
||||||
{
|
{
|
||||||
// case(_ < PBOT1_KILLER_THRESHOLD) => true:
|
// case(_ <= PBOT1_KILLER_THRESHOLD) => true:
|
||||||
// 'killer';
|
// 'killer';
|
||||||
case(_ < PBOT1_SICK_THRESHOLD) => true:
|
case(_ <= PBOT1_SICK_THRESHOLD) => true:
|
||||||
'sick';
|
'sick';
|
||||||
case(_ < PBOT1_GOOD_THRESHOLD) => true:
|
case(_ <= PBOT1_GOOD_THRESHOLD) => true:
|
||||||
'good';
|
'good';
|
||||||
case(_ < PBOT1_BAD_THRESHOLD) => true:
|
case(_ <= PBOT1_BAD_THRESHOLD) => true:
|
||||||
'bad';
|
'bad';
|
||||||
case(_ < PBOT1_SHIT_THRESHOLD) => true:
|
case(_ <= PBOT1_SHIT_THRESHOLD) => true:
|
||||||
'shit';
|
'shit';
|
||||||
default:
|
default:
|
||||||
FlxG.log.warn('Missed note: Bad timing ($absTiming < $PBOT1_SHIT_THRESHOLD)');
|
FlxG.log.warn('Missed note: Bad timing ($absTiming < $PBOT1_SHIT_THRESHOLD)');
|
||||||
@@ -272,13 +272,13 @@ class Scoring
|
|||||||
|
|
||||||
return switch (absTiming)
|
return switch (absTiming)
|
||||||
{
|
{
|
||||||
case(_ < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD) => true:
|
case(_ <= LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD) => true:
|
||||||
'sick';
|
'sick';
|
||||||
case(_ < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD) => true:
|
case(_ <= LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD) => true:
|
||||||
'good';
|
'good';
|
||||||
case(_ < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD) => true:
|
case(_ <= LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD) => true:
|
||||||
'bad';
|
'bad';
|
||||||
case(_ < LEGACY_HIT_WINDOW * LEGACY_SHIT_THRESHOLD) => true:
|
case(_ <= LEGACY_HIT_WINDOW * LEGACY_SHIT_THRESHOLD) => true:
|
||||||
'shit';
|
'shit';
|
||||||
default:
|
default:
|
||||||
FlxG.log.warn('Missed note: Bad timing ($absTiming < $LEGACY_SHIT_THRESHOLD)');
|
FlxG.log.warn('Missed note: Bad timing ($absTiming < $LEGACY_SHIT_THRESHOLD)');
|
||||||
@@ -344,19 +344,19 @@ class Scoring
|
|||||||
static function judgeNoteWEEK7(msTiming:Float):String
|
static function judgeNoteWEEK7(msTiming:Float):String
|
||||||
{
|
{
|
||||||
var absTiming = Math.abs(msTiming);
|
var absTiming = Math.abs(msTiming);
|
||||||
if (absTiming < WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD)
|
if (absTiming <= WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD)
|
||||||
{
|
{
|
||||||
return 'sick';
|
return 'sick';
|
||||||
}
|
}
|
||||||
else if (absTiming < WEEK7_HIT_WINDOW * WEEK7_GOOD_THRESHOLD)
|
else if (absTiming <= WEEK7_HIT_WINDOW * WEEK7_GOOD_THRESHOLD)
|
||||||
{
|
{
|
||||||
return 'good';
|
return 'good';
|
||||||
}
|
}
|
||||||
else if (absTiming < WEEK7_HIT_WINDOW * WEEK7_BAD_THRESHOLD)
|
else if (absTiming <= WEEK7_HIT_WINDOW * WEEK7_BAD_THRESHOLD)
|
||||||
{
|
{
|
||||||
return 'bad';
|
return 'bad';
|
||||||
}
|
}
|
||||||
else if (absTiming < WEEK7_HIT_WINDOW)
|
else if (absTiming <= WEEK7_HIT_WINDOW)
|
||||||
{
|
{
|
||||||
return 'shit';
|
return 'shit';
|
||||||
}
|
}
|
||||||
@@ -529,7 +529,7 @@ enum abstract ScoringRank(String)
|
|||||||
case SHIT:
|
case SHIT:
|
||||||
return 2 / 24;
|
return 2 / 24;
|
||||||
default:
|
default:
|
||||||
return 3.5;
|
return 2 / 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +549,7 @@ enum abstract ScoringRank(String)
|
|||||||
case SHIT:
|
case SHIT:
|
||||||
return 95 / 24;
|
return 95 / 24;
|
||||||
default:
|
default:
|
||||||
return 3.5;
|
return 95 / 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,7 +569,7 @@ enum abstract ScoringRank(String)
|
|||||||
case SHIT:
|
case SHIT:
|
||||||
return 186 / 24;
|
return 186 / 24;
|
||||||
default:
|
default:
|
||||||
return 3.5;
|
return 186 / 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ enum abstract ScoringRank(String)
|
|||||||
case SHIT:
|
case SHIT:
|
||||||
return 207 / 24;
|
return 207 / 24;
|
||||||
default:
|
default:
|
||||||
return 3.5;
|
return 207 / 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,7 +631,7 @@ enum abstract ScoringRank(String)
|
|||||||
case SHIT:
|
case SHIT:
|
||||||
return 'resultScreen/rankText/rankScrollLOSS';
|
return 'resultScreen/rankText/rankScrollLOSS';
|
||||||
default:
|
default:
|
||||||
return 'resultScreen/rankText/rankScrollGOOD';
|
return 'resultScreen/rankText/rankScrollLOSS';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,7 +652,7 @@ enum abstract ScoringRank(String)
|
|||||||
case SHIT:
|
case SHIT:
|
||||||
return 'resultScreen/rankText/rankTextLOSS';
|
return 'resultScreen/rankText/rankTextLOSS';
|
||||||
default:
|
default:
|
||||||
return 'resultScreen/rankText/rankTextGOOD';
|
return 'resultScreen/rankText/rankTextLOSS';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,6 +672,8 @@ enum abstract ScoringRank(String)
|
|||||||
0xFFFF58B4;
|
0xFFFF58B4;
|
||||||
case PERFECT_GOLD:
|
case PERFECT_GOLD:
|
||||||
0xFFFFB619;
|
0xFFFFB619;
|
||||||
|
default:
|
||||||
|
0xFF6044FF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1018,11 +1018,25 @@ class SongDifficulty
|
|||||||
result.addOpponentVoice(FunkinSound.load(opponentVoice, 1.0, false, false, false, false, null, null, true));
|
result.addOpponentVoice(FunkinSound.load(opponentVoice, 1.0, false, false, false, false, null, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes the sounds don't set their important value to true, so we have to do this manually.
|
if (result.members.length == 0)
|
||||||
result.forEach(function(snd:FunkinSound)
|
|
||||||
{
|
{
|
||||||
snd.important = true;
|
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||||
});
|
// Try to use `Voices.ogg` if no other voices are found.
|
||||||
|
var legacyPath = Paths.voices(this.song.id, '$suffix');
|
||||||
|
if (Assets.exists(legacyPath))
|
||||||
|
{
|
||||||
|
result.addPlayerVoice(FunkinSound.load(legacyPath, 1.0, false, false, false, false, null, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.members.length == 1) // It's legacy'ing somewhere and i can prove it
|
||||||
|
{
|
||||||
|
result.legacyVoiceSystem = true;
|
||||||
|
result.legacyVoiceUsesPlayer = result.getPlayerVoice(0) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sometimes the sounds don't set their important value to true, so we have to do this manually.
|
||||||
|
result.forEach((snd:FunkinSound) -> snd.important = true);
|
||||||
|
|
||||||
result.playerVoicesOffset = offsets.getVocalOffset(characters.player, instId);
|
result.playerVoicesOffset = offsets.getVocalOffset(characters.player, instId);
|
||||||
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent, instId);
|
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent, instId);
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||||||
prop.x = dataProp.position[0];
|
prop.x = dataProp.position[0];
|
||||||
prop.y = dataProp.position[1];
|
prop.y = dataProp.position[1];
|
||||||
prop.zIndex = dataProp.zIndex;
|
prop.zIndex = dataProp.zIndex;
|
||||||
|
|
||||||
|
// Reset danceEvery value.
|
||||||
|
if (Std.isOfType(prop, Bopper))
|
||||||
|
{
|
||||||
|
cast(prop, Bopper).danceEvery = dataProp.danceEvery;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ class Save implements ConsoleClass
|
|||||||
/// MODS
|
/// MODS
|
||||||
///
|
///
|
||||||
@:saveProperty(data.mods.enabledMods)
|
@:saveProperty(data.mods.enabledMods)
|
||||||
public var enabledModIds:SaveProperty<Array<String>>;
|
public var enabledModDirs:SaveProperty<Array<String>>;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// CHART EDITOR OPTIONS
|
/// CHART EDITOR OPTIONS
|
||||||
@@ -1223,7 +1223,7 @@ typedef SaveDataOptions =
|
|||||||
var audioVisualOffset:Int;
|
var audioVisualOffset:Int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we want the framerate to be unlocked on HTML5.
|
* If we want the framerate to be unlocked.
|
||||||
* @default `false`
|
* @default `false`
|
||||||
*/
|
*/
|
||||||
var unlockedFramerate:Bool;
|
var unlockedFramerate:Bool;
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ class SaveProperty<T>
|
|||||||
|
|
||||||
if (_autoFlush) Save.system.flush();
|
if (_autoFlush) Save.system.flush();
|
||||||
|
|
||||||
trace('[Save Property]: Changing value of $oldValue TO $newValue');
|
|
||||||
|
|
||||||
onChange.dispatch(newValue);
|
onChange.dispatch(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if mobile
|
#if mobile
|
||||||
private function updateDeviceNotch(notch:lime.math.Rectangle):Void
|
private function updateDeviceNotch(notch:openfl.geom.Rectangle):Void
|
||||||
{
|
{
|
||||||
notchPosition.set(enabled ? notch.x : 0, enabled ? notch.y : 0);
|
notchPosition.set(enabled ? notch.x : 0, enabled ? notch.y : 0);
|
||||||
notchSize.set(enabled ? notch.width : 0, enabled ? notch.height : 0);
|
notchSize.set(enabled ? notch.width : 0, enabled ? notch.height : 0);
|
||||||
@@ -403,11 +403,6 @@ class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode
|
|||||||
gameNotchPosition *= scale;
|
gameNotchPosition *= scale;
|
||||||
gameNotchSize *= scale;
|
gameNotchSize *= scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ios
|
|
||||||
gameNotchPosition /= 2;
|
|
||||||
gameNotchSize /= 2;
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package funkin.ui;
|
package funkin.ui;
|
||||||
|
|
||||||
import funkin.graphics.FlxFilteredSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The icon that gets used for Freeplay capsules and char select
|
* 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
|
* NOT to be confused with the HealthIcon class, which is for the in-game icons
|
||||||
*/
|
*/
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
class PixelatedIcon extends FlxFilteredSprite
|
class PixelatedIcon extends FunkinSprite
|
||||||
{
|
{
|
||||||
public var char:String;
|
public var char:String;
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||||||
public function setCharacter(char:String):Void
|
public function setCharacter(char:String):Void
|
||||||
{
|
{
|
||||||
if (this.char == char) return;
|
if (this.char == char) return;
|
||||||
this.char = char;
|
|
||||||
|
|
||||||
var charPath:String = "freeplay/icons/";
|
var charPath:String = "freeplay/icons/";
|
||||||
|
|
||||||
@@ -52,10 +51,9 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
this.visible = true;
|
||||||
this.visible = true;
|
this.char = char; // if we went past this its safe to assume the icon exists so we can assign it
|
||||||
}
|
|
||||||
|
|
||||||
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class IntroSubState extends MusicBeatSubState
|
|||||||
playVideoNative(LIGHTS_VIDEO_PATH);
|
playVideoNative(LIGHTS_VIDEO_PATH);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if NO_FEATURE_VIDEO_PLAYBACK
|
||||||
|
onLightsEnd();
|
||||||
|
#end
|
||||||
|
|
||||||
// // Im TOO lazy to even care, so uh, yep
|
// // Im TOO lazy to even care, so uh, yep
|
||||||
// FlxG.camera.zoom = 0.66666666666666666666666666666667;
|
// FlxG.camera.zoom = 0.66666666666666666666666666666667;
|
||||||
// vid.x = -(FlxG.width - (FlxG.width * FlxG.camera.zoom));
|
// vid.x = -(FlxG.width - (FlxG.width * FlxG.camera.zoom));
|
||||||
|
|||||||
@@ -41,29 +41,37 @@ class Nametag extends FlxSprite
|
|||||||
|
|
||||||
public function switchChar(str:String, playMosaicSequence:Bool = true):Void
|
public function switchChar(str:String, playMosaicSequence:Bool = true):Void
|
||||||
{
|
{
|
||||||
if (playMosaicSequence) shaderEffect();
|
var path:String = (str == "bf") ? "boyfriend" : "bf";
|
||||||
|
if (str != "bf") path = str;
|
||||||
|
|
||||||
new FlxTimer().start(4 / 30, _ ->
|
loadGraphic(Paths.image("charSelect/" + path + "Nametag"));
|
||||||
|
updateHitbox();
|
||||||
|
scale.set(0.77, 0.77);
|
||||||
|
updatePosition();
|
||||||
|
|
||||||
|
// Reset shader to ensure the nametag doesn't get stuck
|
||||||
|
if (playMosaicSequence)
|
||||||
{
|
{
|
||||||
var path:String = str;
|
mosaicShader.setBlockSize(1, 1);
|
||||||
switch str
|
shaderEffect();
|
||||||
|
|
||||||
|
// Delay the shader effect by a bit to prevent lag.
|
||||||
|
new FlxTimer().start(2 / 30, _ ->
|
||||||
{
|
{
|
||||||
case "bf":
|
shaderEffect(true);
|
||||||
path = "boyfriend";
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
loadGraphic(Paths.image('charSelect/' + path + "Nametag"));
|
{
|
||||||
updateHitbox();
|
mosaicShader.setBlockSize(1, 1);
|
||||||
scale.x = scale.y = 0.77;
|
}
|
||||||
|
|
||||||
updatePosition();
|
|
||||||
|
|
||||||
if (playMosaicSequence) shaderEffect(true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shaderEffect(fadeOut:Bool = false):Void
|
function shaderEffect(fadeOut:Bool = false):Void
|
||||||
{
|
{
|
||||||
|
// Skip the shader effect if the width is too small.
|
||||||
|
if (width <= 1) return;
|
||||||
|
|
||||||
if (currentMosaicSequence != null)
|
if (currentMosaicSequence != null)
|
||||||
{
|
{
|
||||||
// Forcibly reset the shader to prevent overlapping blur sequences
|
// Forcibly reset the shader to prevent overlapping blur sequences
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ import openfl.display.Shape;
|
|||||||
import openfl.display.Sprite;
|
import openfl.display.Sprite;
|
||||||
import openfl.text.TextField;
|
import openfl.text.TextField;
|
||||||
import openfl.text.TextFormat;
|
import openfl.text.TextFormat;
|
||||||
|
import openfl.Lib;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A debug overlay showing useful info.
|
* A debug overlay showing useful info.
|
||||||
*/
|
*/
|
||||||
#if cpp
|
|
||||||
@:access(lime._internal.backend.native.NativeCFFI)
|
|
||||||
#end
|
|
||||||
class FunkinDebugDisplay extends Sprite
|
class FunkinDebugDisplay extends Sprite
|
||||||
{
|
{
|
||||||
static final UPDATE_DELAY:Int = 100;
|
static final UPDATE_DELAY:Int = 100;
|
||||||
@@ -31,18 +29,16 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
*/
|
*/
|
||||||
public var backgroundOpacity(default, set):Float = 0.5;
|
public var backgroundOpacity(default, set):Float = 0.5;
|
||||||
|
|
||||||
var currentFPS:Int;
|
|
||||||
var deltaTimeout:Float;
|
var deltaTimeout:Float;
|
||||||
var times:Array<Float>;
|
var times:Array<Float>;
|
||||||
var color:Int;
|
var color:Int;
|
||||||
|
|
||||||
#if !html5
|
var fps:Int;
|
||||||
|
var fpsPeak:Int;
|
||||||
var gcMem:Float;
|
var gcMem:Float;
|
||||||
var gcMemPeak:Float;
|
var gcMemPeak:Float;
|
||||||
|
|
||||||
var taskMem:Float;
|
var taskMem:Float;
|
||||||
var taskMemPeak:Float;
|
var taskMemPeak:Float;
|
||||||
#end
|
|
||||||
|
|
||||||
var background:Shape;
|
var background:Shape;
|
||||||
|
|
||||||
@@ -58,16 +54,18 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
|
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.currentFPS = 0;
|
|
||||||
this.deltaTimeout = 0.0;
|
this.deltaTimeout = 0.0;
|
||||||
#if !html5
|
this.times = [];
|
||||||
|
this.color = color;
|
||||||
|
|
||||||
|
this.fps = 0;
|
||||||
|
this.fpsPeak = 0;
|
||||||
this.gcMem = 0.0;
|
this.gcMem = 0.0;
|
||||||
this.gcMemPeak = 0.0;
|
this.gcMemPeak = 0.0;
|
||||||
this.taskMem = 0.0;
|
this.taskMem = 0.0;
|
||||||
this.taskMemPeak = 0.0;
|
this.taskMemPeak = 0.0;
|
||||||
#end
|
|
||||||
this.times = [];
|
|
||||||
this.color = color;
|
|
||||||
this.backgroundOpacity = 0;
|
this.backgroundOpacity = 0;
|
||||||
this.isAdvanced = false;
|
this.isAdvanced = false;
|
||||||
}
|
}
|
||||||
@@ -76,13 +74,23 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
{
|
{
|
||||||
removeChildren(0, numChildren);
|
removeChildren(0, numChildren);
|
||||||
|
|
||||||
final BG_WIDTH_MULTIPLIER:Float = #if html5 advanced ? 1 : 0.3 #else 1 #end;
|
var BG_WIDTH_MULTIPLIER:Float = advanced ? 1 : 0.3;
|
||||||
|
|
||||||
#if html5
|
if (MemoryUtil.supportsGCMem() || MemoryUtil.supportsTaskMem())
|
||||||
final BG_HEIGHT_MULTIPLIER:Float = advanced ? 0.45 : 0.15;
|
{
|
||||||
#else
|
BG_WIDTH_MULTIPLIER = 1;
|
||||||
final BG_HEIGHT_MULTIPLIER:Float = advanced ? 1 : (MemoryUtil.supportsTaskMem()) ? 0.3 : 0.2;
|
}
|
||||||
#end
|
|
||||||
|
var BG_HEIGHT_MULTIPLIER:Float = advanced ? 0.45 : 0.15;
|
||||||
|
|
||||||
|
if (MemoryUtil.supportsGCMem() && MemoryUtil.supportsTaskMem())
|
||||||
|
{
|
||||||
|
BG_HEIGHT_MULTIPLIER = advanced ? 1 : 0.3;
|
||||||
|
}
|
||||||
|
else if (MemoryUtil.supportsGCMem() || MemoryUtil.supportsTaskMem())
|
||||||
|
{
|
||||||
|
BG_HEIGHT_MULTIPLIER = advanced ? 0.7 : 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
background = new Shape();
|
background = new Shape();
|
||||||
background.graphics.beginFill(0x3d3f41, 1);
|
background.graphics.beginFill(0x3d3f41, 1);
|
||||||
@@ -118,10 +126,12 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
fpsGraph.minValue = 0;
|
fpsGraph.minValue = 0;
|
||||||
addChild(fpsGraph);
|
addChild(fpsGraph);
|
||||||
|
|
||||||
#if !html5
|
if (MemoryUtil.supportsGCMem())
|
||||||
gcMemGraph = new FunkinStatsGraph(OTHERS_OFFSET, Math.floor(OTHERS_OFFSET + (fpsGraph.y + fpsGraph.axisHeight) + 22), graphsWidth, graphsHeight, color);
|
{
|
||||||
gcMemGraph.minValue = 0;
|
gcMemGraph = new FunkinStatsGraph(OTHERS_OFFSET, Math.floor(OTHERS_OFFSET + (fpsGraph.y + fpsGraph.axisHeight) + 22), graphsWidth, graphsHeight, color);
|
||||||
addChild(gcMemGraph);
|
gcMemGraph.minValue = 0;
|
||||||
|
addChild(gcMemGraph);
|
||||||
|
}
|
||||||
|
|
||||||
if (MemoryUtil.supportsTaskMem())
|
if (MemoryUtil.supportsTaskMem())
|
||||||
{
|
{
|
||||||
@@ -130,7 +140,6 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
taskMemGraph.minValue = 0;
|
taskMemGraph.minValue = 0;
|
||||||
addChild(taskMemGraph);
|
addChild(taskMemGraph);
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSimpleElements():Void
|
function createSimpleElements():Void
|
||||||
@@ -147,15 +156,9 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
addChild(infoDisplay);
|
addChild(infoDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function __enterFrame(deltaTime:Int):Void
|
override function __enterFrame(deltaTime:Float):Void
|
||||||
{
|
{
|
||||||
#if cpp
|
final currentTime:Float = Lib.getTimer();
|
||||||
final currentTime:Float = lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks();
|
|
||||||
#elseif html5
|
|
||||||
final currentTime:Float = js.Browser.window.performance.now();
|
|
||||||
#else
|
|
||||||
final currentTime:Float = haxe.Timer.stamp() * 1000;
|
|
||||||
#end
|
|
||||||
|
|
||||||
times.push(currentTime);
|
times.push(currentTime);
|
||||||
|
|
||||||
@@ -170,12 +173,16 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFPS = times.length;
|
fps = times.length;
|
||||||
|
|
||||||
#if !html5
|
if (fps > fpsPeak) fpsPeak = fps;
|
||||||
gcMem = MemoryUtil.getGCMemory();
|
|
||||||
|
|
||||||
if (gcMem > gcMemPeak) gcMemPeak = gcMem;
|
if (MemoryUtil.supportsGCMem())
|
||||||
|
{
|
||||||
|
gcMem = MemoryUtil.getGCMemory();
|
||||||
|
|
||||||
|
if (gcMem > gcMemPeak) gcMemPeak = gcMem;
|
||||||
|
}
|
||||||
|
|
||||||
if (MemoryUtil.supportsTaskMem())
|
if (MemoryUtil.supportsTaskMem())
|
||||||
{
|
{
|
||||||
@@ -183,7 +190,6 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
|
|
||||||
if (taskMem > taskMemPeak) taskMemPeak = taskMem;
|
if (taskMem > taskMemPeak) taskMemPeak = taskMem;
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
|
|
||||||
if (isAdvanced)
|
if (isAdvanced)
|
||||||
{
|
{
|
||||||
@@ -200,25 +206,24 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
function updateAdvancedDisplay():Void
|
function updateAdvancedDisplay():Void
|
||||||
{
|
{
|
||||||
updateFPSGraph();
|
updateFPSGraph();
|
||||||
#if !html5
|
|
||||||
updateGcMemGraph();
|
updateGcMemGraph();
|
||||||
updateTaskMemGraph();
|
updateTaskMemGraph();
|
||||||
#end
|
|
||||||
|
|
||||||
final info:Array<String> = [];
|
final info:Array<String> = [];
|
||||||
info.push('FPS: $currentFPS');
|
info.push('FPS: $fps');
|
||||||
info.push('AVG FPS: ${Math.floor(fpsGraph.average())}');
|
info.push('AVG FPS: ${Math.floor(fpsGraph.average())}');
|
||||||
info.push('1% LOW FPS: ${Math.floor(fpsGraph.lowest())}');
|
info.push('1% LOW FPS: ${Math.floor(fpsGraph.lowest())}');
|
||||||
fpsGraph.textDisplay.text = info.join('\n');
|
fpsGraph.textDisplay.text = info.join('\n');
|
||||||
|
|
||||||
#if !html5
|
if (gcMemGraph != null)
|
||||||
gcMemGraph.textDisplay.text = 'GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}';
|
{
|
||||||
|
gcMemGraph.textDisplay.text = 'GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}';
|
||||||
|
}
|
||||||
|
|
||||||
if (taskMemGraph != null)
|
if (taskMemGraph != null)
|
||||||
{
|
{
|
||||||
taskMemGraph.textDisplay.text = 'TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}';
|
taskMemGraph.textDisplay.text = 'TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}';
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSimpleDisplay():Void
|
function updateSimpleDisplay():Void
|
||||||
@@ -227,33 +232,38 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
{
|
{
|
||||||
final info:Array<String> = [];
|
final info:Array<String> = [];
|
||||||
|
|
||||||
info.push('FPS: $currentFPS');
|
info.push('FPS: $fps');
|
||||||
|
|
||||||
#if !html5
|
if (MemoryUtil.supportsGCMem())
|
||||||
info.push('GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}');
|
{
|
||||||
|
info.push('GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}');
|
||||||
|
}
|
||||||
|
|
||||||
if (MemoryUtil.supportsTaskMem())
|
if (MemoryUtil.supportsTaskMem())
|
||||||
|
{
|
||||||
info.push('TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}');
|
info.push('TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}');
|
||||||
#end
|
}
|
||||||
|
|
||||||
infoDisplay.text = info.join('\n');
|
infoDisplay.text = info.join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFPSGraph(?currentFPS:Int = 0):Void
|
function updateFPSGraph():Void
|
||||||
{
|
{
|
||||||
fpsGraph.maxValue = FlxG.drawFramerate;
|
fpsGraph.maxValue = fpsPeak;
|
||||||
fpsGraph.update(times.length);
|
fpsGraph.update(fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !html5
|
function updateGcMemGraph():Void
|
||||||
function updateGcMemGraph(?currentFPS:Int = 0):Void
|
|
||||||
{
|
{
|
||||||
gcMemGraph.maxValue = gcMemPeak;
|
if (gcMemGraph != null)
|
||||||
gcMemGraph.update(gcMem);
|
{
|
||||||
|
gcMemGraph.maxValue = gcMemPeak;
|
||||||
|
gcMemGraph.update(gcMem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTaskMemGraph(?currentFPS:Int = 0):Void
|
function updateTaskMemGraph():Void
|
||||||
{
|
{
|
||||||
if (taskMemGraph != null)
|
if (taskMemGraph != null)
|
||||||
{
|
{
|
||||||
@@ -261,7 +271,6 @@ class FunkinDebugDisplay extends Sprite
|
|||||||
taskMemGraph.update(taskMem);
|
taskMemGraph.update(taskMem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
|
|
||||||
function set_isAdvanced(value:Bool):Bool
|
function set_isAdvanced(value:Bool):Bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,10 +93,7 @@ class DebugBoundingState extends FlxState
|
|||||||
var viewDropdown:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
|
var viewDropdown:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
|
||||||
viewDropdown.onChange = function(e:UIEvent)
|
viewDropdown.onChange = function(e:UIEvent)
|
||||||
{
|
{
|
||||||
trace(e.type);
|
|
||||||
curView = cast e?.data?.curView;
|
curView = cast e?.data?.curView;
|
||||||
trace(e.data);
|
|
||||||
// trace(e.data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
|
offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
|
||||||
@@ -451,7 +448,8 @@ class DebugBoundingState extends FlxState
|
|||||||
// Plays the idle animation
|
// Plays the idle animation
|
||||||
if (FlxG.keys.justPressed.SPACE)
|
if (FlxG.keys.justPressed.SPACE)
|
||||||
{
|
{
|
||||||
offsetAnimationDropdown.value = {id: 'idle', text: 'idle'};
|
if (swagChar?.hasAnimation('danceLeft')) offsetAnimationDropdown.value = {id: 'danceLeft', text: 'danceLeft'};
|
||||||
|
else offsetAnimationDropdown.value = {id: 'idle', text: 'idle'};
|
||||||
|
|
||||||
playCharacterAnimation(currentAnimationName, true);
|
playCharacterAnimation(currentAnimationName, true);
|
||||||
}
|
}
|
||||||
@@ -568,21 +566,18 @@ class DebugBoundingState extends FlxState
|
|||||||
for (i in swagChar.animationOffsets.keys())
|
for (i in swagChar.animationOffsets.keys())
|
||||||
{
|
{
|
||||||
characterAnimNames.push(i);
|
characterAnimNames.push(i);
|
||||||
trace(i);
|
|
||||||
trace(swagChar.animationOffsets[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetAnimationDropdown.dataSource.clear();
|
offsetAnimationDropdown.dataSource.clear();
|
||||||
|
|
||||||
for (charAnim in characterAnimNames)
|
for (charAnim in characterAnimNames)
|
||||||
{
|
{
|
||||||
trace('Adding ${charAnim} to HaxeUI dropdown');
|
|
||||||
offsetAnimationDropdown.dataSource.add({id: charAnim, text: charAnim});
|
offsetAnimationDropdown.dataSource.add({id: charAnim, text: charAnim});
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetAnimationDropdown.selectedIndex = 0;
|
offsetAnimationDropdown.selectedIndex = 0;
|
||||||
|
|
||||||
trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
|
trace('Added ${offsetAnimationDropdown.dataSource.size} animations to HaxeUI dropdown');
|
||||||
|
|
||||||
offsetAnimationDropdown.onChange = function(event:UIEvent)
|
offsetAnimationDropdown.onChange = function(event:UIEvent)
|
||||||
{
|
{
|
||||||
@@ -607,7 +602,6 @@ class DebugBoundingState extends FlxState
|
|||||||
// var animName = characterAnimNames[Std.parseInt(str)];
|
// var animName = characterAnimNames[Std.parseInt(str)];
|
||||||
var animName = str;
|
var animName = str;
|
||||||
swagChar.playAnimation(animName, true); // trace();
|
swagChar.playAnimation(animName, true); // trace();
|
||||||
trace(swagChar.animationOffsets.get(animName));
|
|
||||||
|
|
||||||
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||||
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
|||||||
import funkin.ui.debug.charting.commands.CopyItemsCommand;
|
import funkin.ui.debug.charting.commands.CopyItemsCommand;
|
||||||
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
||||||
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
||||||
|
import funkin.ui.debug.charting.commands.DeselectAllItemsBetweenTimeCommand;
|
||||||
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
|
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
|
||||||
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
|
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
|
||||||
import funkin.ui.debug.charting.commands.FlipNotesCommand;
|
import funkin.ui.debug.charting.commands.FlipNotesCommand;
|
||||||
@@ -72,6 +73,7 @@ import funkin.ui.debug.charting.commands.RemoveItemsCommand;
|
|||||||
import funkin.ui.debug.charting.commands.RemoveNotesCommand;
|
import funkin.ui.debug.charting.commands.RemoveNotesCommand;
|
||||||
import funkin.ui.debug.charting.commands.RemoveStackedNotesCommand;
|
import funkin.ui.debug.charting.commands.RemoveStackedNotesCommand;
|
||||||
import funkin.ui.debug.charting.commands.SelectAllItemsCommand;
|
import funkin.ui.debug.charting.commands.SelectAllItemsCommand;
|
||||||
|
import funkin.ui.debug.charting.commands.SelectAllItemsBetweenTimeCommand;
|
||||||
import funkin.ui.debug.charting.commands.SelectItemsCommand;
|
import funkin.ui.debug.charting.commands.SelectItemsCommand;
|
||||||
import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
|
import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
|
||||||
import funkin.ui.debug.charting.commands.SwitchDifficultyCommand;
|
import funkin.ui.debug.charting.commands.SwitchDifficultyCommand;
|
||||||
@@ -292,6 +294,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
{
|
{
|
||||||
this.songLengthInMs = value;
|
this.songLengthInMs = value;
|
||||||
|
|
||||||
|
resetPreviewTimes();
|
||||||
updateGridHeight();
|
updateGridHeight();
|
||||||
|
|
||||||
return this.songLengthInMs;
|
return this.songLengthInMs;
|
||||||
@@ -789,6 +792,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
return hitsoundVolumePlayer + hitsoundVolumeOpponent > 0;
|
return hitsoundVolumePlayer + hitsoundVolumeOpponent > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stretchySound1:Null<FunkinSound> = null;
|
||||||
|
var stretchySound2:Null<FunkinSound> = null;
|
||||||
|
|
||||||
// Auto-save
|
// Auto-save
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1144,6 +1150,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
*/
|
*/
|
||||||
var commandHistoryDirty:Bool = true;
|
var commandHistoryDirty:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the selection has changed and the edit buttons need to be updated.
|
||||||
|
*/
|
||||||
|
var editButtonsDirty:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the clipboard has changed and the paste buttons need to be updated.
|
||||||
|
*/
|
||||||
|
var clipboardDirty:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the clipboard is valid and contains a json of notes and events.
|
||||||
|
*/
|
||||||
|
var clipboardValid:Bool = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, we are currently in the process of quitting the chart editor.
|
* If true, we are currently in the process of quitting the chart editor.
|
||||||
* Skip any update functions as most of them will call a crash.
|
* Skip any update functions as most of them will call a crash.
|
||||||
@@ -1413,6 +1435,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
function set_currentSongMetadata(value:SongMetadata):SongMetadata
|
function set_currentSongMetadata(value:SongMetadata):SongMetadata
|
||||||
{
|
{
|
||||||
songMetadata.set(selectedVariation, value);
|
songMetadata.set(selectedVariation, value);
|
||||||
|
|
||||||
|
resetPreviewTimes();
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1558,28 +1583,28 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
return currentSongMetadata.playData.noteStyle = value;
|
return currentSongMetadata.playData.noteStyle = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSongFreeplayPreviewStart(get, set):Int;
|
var currentSongFreeplayPreviewStart(get, set):Float;
|
||||||
|
|
||||||
function get_currentSongFreeplayPreviewStart():Int
|
function get_currentSongFreeplayPreviewStart():Float
|
||||||
{
|
{
|
||||||
return currentSongMetadata.playData.previewStart;
|
return currentSongMetadata.playData.previewStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_currentSongFreeplayPreviewStart(value:Int):Int
|
function set_currentSongFreeplayPreviewStart(value:Float):Float
|
||||||
{
|
{
|
||||||
return currentSongMetadata.playData.previewStart = value;
|
return currentSongMetadata.playData.previewStart = value * (value < 1 ? songLengthInMs : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSongFreeplayPreviewEnd(get, set):Int;
|
var currentSongFreeplayPreviewEnd(get, set):Float;
|
||||||
|
|
||||||
function get_currentSongFreeplayPreviewEnd():Int
|
function get_currentSongFreeplayPreviewEnd():Float
|
||||||
{
|
{
|
||||||
return currentSongMetadata.playData.previewEnd;
|
return currentSongMetadata.playData.previewEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_currentSongFreeplayPreviewEnd(value:Int):Int
|
function set_currentSongFreeplayPreviewEnd(value:Float):Float
|
||||||
{
|
{
|
||||||
return currentSongMetadata.playData.previewEnd = value;
|
return currentSongMetadata.playData.previewEnd = value * (value < 1 ? songLengthInMs : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSongStage(get, set):String;
|
var currentSongStage(get, set):String;
|
||||||
@@ -1959,14 +1984,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
var menubarItemSelectRegion:MenuItem;
|
var menubarItemSelectRegion:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Select Before Cursor` menu item.
|
* The `Edit -> Select Before Playhead` menu item.
|
||||||
*/
|
*/
|
||||||
var menubarItemSelectBeforeCursor:MenuItem;
|
var menubarItemSelectBeforePlayhead:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Select After Cursor` menu item.
|
* The `Edit -> Select After Playhead` menu item.
|
||||||
*/
|
*/
|
||||||
var menubarItemSelectAfterCursor:MenuItem;
|
var menubarItemSelectAfterPlayhead:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Decrease Note Snap Precision` menu item.
|
* The `Edit -> Decrease Note Snap Precision` menu item.
|
||||||
@@ -2525,6 +2550,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
this.welcomeMusic.looped = true;
|
this.welcomeMusic.looped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function resetPreviewTimes() {
|
||||||
|
currentSongFreeplayPreviewStart = (currentSongMetadata?.playData?.previewStart ?? Constants.DEFAULT_PREVIEW_START_TIME);
|
||||||
|
currentSongFreeplayPreviewEnd = (currentSongMetadata?.playData?.previewEnd ?? Constants.DEFAULT_PREVIEW_END_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
public function loadPreferences():Void
|
public function loadPreferences():Void
|
||||||
{
|
{
|
||||||
var save:Save = Save.instance;
|
var save:Save = Save.instance;
|
||||||
@@ -2555,7 +2585,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
menubarItemVolumeInstrumental.value = Std.int(save.chartEditorInstVolume.value * 100);
|
menubarItemVolumeInstrumental.value = Std.int(save.chartEditorInstVolume.value * 100);
|
||||||
menubarItemVolumeVocalsPlayer.value = Std.int(save.chartEditorPlayerVoiceVolume.value * 100);
|
menubarItemVolumeVocalsPlayer.value = Std.int(save.chartEditorPlayerVoiceVolume.value * 100);
|
||||||
menubarItemVolumeVocalsOpponent.value = Std.int(save.chartEditorOpponentVoiceVolume.value * 100);
|
menubarItemVolumeVocalsOpponent.value = Std.int(save.chartEditorOpponentVoiceVolume.value * 100);
|
||||||
menubarItemPlaybackSpeed.value = Std.int(save.chartEditorPlaybackSpeed.value * 100.0);
|
menubarItemPlaybackSpeed.value = Math.round(save.chartEditorPlaybackSpeed.value * 100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function writePreferences(hasBackup:Bool):Void
|
public function writePreferences(hasBackup:Bool):Void
|
||||||
@@ -3027,7 +3057,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
{
|
{
|
||||||
if (!isCursorOverHaxeUI && !isPlaytesting)
|
if (!isCursorOverHaxeUI && !isPlaytesting)
|
||||||
{
|
{
|
||||||
this.openCharacterDropdown(CharacterType.DAD, true);
|
if (pressingControl())
|
||||||
|
{
|
||||||
|
this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.openCharacterDropdown(CharacterType.DAD, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3035,7 +3072,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
{
|
{
|
||||||
if (!isCursorOverHaxeUI && !isPlaytesting)
|
if (!isCursorOverHaxeUI && !isPlaytesting)
|
||||||
{
|
{
|
||||||
this.openCharacterDropdown(CharacterType.BF, true);
|
if (pressingControl())
|
||||||
|
{
|
||||||
|
this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.openCharacterDropdown(CharacterType.BF, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3287,6 +3331,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
|
|
||||||
menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand());
|
menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand());
|
||||||
|
|
||||||
|
menubarItemSelectBeforePlayhead.onClick = _ -> performCommand(new SelectAllItemsBetweenTimeCommand(scrollPositionInMs + playheadPositionInMs, true, true, true));
|
||||||
|
|
||||||
|
menubarItemSelectAfterPlayhead.onClick = _ -> performCommand(new SelectAllItemsBetweenTimeCommand(scrollPositionInMs + playheadPositionInMs, false, true, true));
|
||||||
|
|
||||||
menubarItemPlaytestFull.onClick = _ -> testSongInPlayState(false);
|
menubarItemPlaytestFull.onClick = _ -> testSongInPlayState(false);
|
||||||
menubarItemPlaytestMinimal.onClick = _ -> testSongInPlayState(true);
|
menubarItemPlaytestMinimal.onClick = _ -> testSongInPlayState(true);
|
||||||
|
|
||||||
@@ -3442,7 +3490,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||||
menubarLabelVolumeInstrumental.text = 'Instrumental - ${Std.int(event.value)}%';
|
menubarLabelVolumeInstrumental.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
previousAudioVolumes[3] = menubarLabelVolumeInstrumental.value;
|
previousAudioVolumes[3] = menubarItemVolumeInstrumental.value;
|
||||||
|
|
||||||
menubarItemVolumeVocalsPlayer.onChange = event ->
|
menubarItemVolumeVocalsPlayer.onChange = event ->
|
||||||
{
|
{
|
||||||
@@ -3450,7 +3498,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
audioVocalTrackGroup.playerVolume = volume;
|
audioVocalTrackGroup.playerVolume = volume;
|
||||||
menubarLabelVolumeVocalsPlayer.text = 'Player - ${Std.int(event.value)}%';
|
menubarLabelVolumeVocalsPlayer.text = 'Player - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
previousAudioVolumes[4] = menubarLabelVolumeVocalsPlayer.value;
|
previousAudioVolumes[4] = menubarItemVolumeVocalsPlayer.value;
|
||||||
|
|
||||||
menubarItemVolumeVocalsOpponent.onChange = event ->
|
menubarItemVolumeVocalsOpponent.onChange = event ->
|
||||||
{
|
{
|
||||||
@@ -3458,7 +3506,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
audioVocalTrackGroup.opponentVolume = volume;
|
audioVocalTrackGroup.opponentVolume = volume;
|
||||||
menubarLabelVolumeVocalsOpponent.text = 'Enemy - ${Std.int(event.value)}%';
|
menubarLabelVolumeVocalsOpponent.text = 'Enemy - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
previousAudioVolumes[5] = menubarLabelVolumeVocalsOpponent.value;
|
previousAudioVolumes[5] = menubarItemVolumeVocalsOpponent.value;
|
||||||
|
|
||||||
menubarItemPlaybackSpeed.onChange = event ->
|
menubarItemPlaybackSpeed.onChange = event ->
|
||||||
{
|
{
|
||||||
@@ -3497,6 +3545,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
|
|
||||||
function copySelection():Void
|
function copySelection():Void
|
||||||
{
|
{
|
||||||
|
clipboardDirty = true;
|
||||||
|
clipboardValid = true;
|
||||||
// Doesn't use a command because it's not undoable.
|
// Doesn't use a command because it's not undoable.
|
||||||
|
|
||||||
// Calculate a single time offset for all the notes and events.
|
// Calculate a single time offset for all the notes and events.
|
||||||
@@ -3593,7 +3643,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
displayAutosavePopup = false;
|
displayAutosavePopup = false;
|
||||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||||
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [{
|
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [{
|
||||||
text: "Take Me There",
|
text: "Open In Folder",
|
||||||
callback: openBackupsFolder,
|
callback: openBackupsFolder,
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
@@ -4058,7 +4108,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
eventSprite.parentState = this;
|
eventSprite.parentState = this;
|
||||||
// trace('Creating new Event... (${renderedEvents.members.length})');
|
// trace('Creating new Event... (${renderedEvents.members.length})');
|
||||||
|
|
||||||
if (eventData?.value != null && (eventData?.value?.ease != null && eventData?.value?.easeDir == null))
|
if (eventData?.value != null && (eventData.getString("ease") != null && eventData.getInt("easeDir") == null))
|
||||||
{
|
{
|
||||||
eventData.value = migrateEventEaseDirectionFields(eventData.value);
|
eventData.value = migrateEventEaseDirectionFields(eventData.value);
|
||||||
}
|
}
|
||||||
@@ -4505,7 +4555,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HOME = Scroll to Top
|
// HOME = Scroll to Top
|
||||||
if (FlxG.keys.justPressed.HOME)
|
if (!FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.HOME)
|
||||||
{
|
{
|
||||||
// Scroll amount is the difference between the current position and the top.
|
// Scroll amount is the difference between the current position and the top.
|
||||||
scrollAmount = 0 - this.scrollPositionInPixels;
|
scrollAmount = 0 - this.scrollPositionInPixels;
|
||||||
@@ -4521,7 +4571,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
}
|
}
|
||||||
|
|
||||||
// END = Scroll to Bottom
|
// END = Scroll to Bottom
|
||||||
if (FlxG.keys.justPressed.END)
|
if (!FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.END)
|
||||||
{
|
{
|
||||||
// Scroll amount is the difference between the current position and the bottom.
|
// Scroll amount is the difference between the current position and the bottom.
|
||||||
scrollAmount = this.songLengthInPixels - this.scrollPositionInPixels;
|
scrollAmount = this.songLengthInPixels - this.scrollPositionInPixels;
|
||||||
@@ -5169,8 +5219,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
{
|
{
|
||||||
if (dragLengthCurrent != dragLengthSteps)
|
if (dragLengthCurrent != dragLengthSteps)
|
||||||
{
|
{
|
||||||
stretchySounds = !stretchySounds;
|
this.playStretchySound();
|
||||||
this.playSound(Paths.sound('chartingSounds/stretch' + (stretchySounds ? '1' : '2') + '_UI'));
|
|
||||||
|
|
||||||
dragLengthCurrent = dragLengthSteps;
|
dragLengthCurrent = dragLengthSteps;
|
||||||
}
|
}
|
||||||
@@ -5847,8 +5896,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
|
|
||||||
if (playheadDragLengthCurrent[column] != targetNoteLengthStepsInt)
|
if (playheadDragLengthCurrent[column] != targetNoteLengthStepsInt)
|
||||||
{
|
{
|
||||||
stretchySounds = !stretchySounds;
|
this.playStretchySound();
|
||||||
this.playSound(Paths.sound('chartingSounds/stretch' + (stretchySounds ? '1' : '2') + '_UI'));
|
|
||||||
playheadDragLengthCurrent[column] = targetNoteLengthStepsInt;
|
playheadDragLengthCurrent[column] = targetNoteLengthStepsInt;
|
||||||
}
|
}
|
||||||
ghostHold.visible = true;
|
ghostHold.visible = true;
|
||||||
@@ -6213,6 +6261,26 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
// Deselect all items.
|
// Deselect all items.
|
||||||
performCommand(new DeselectAllItemsCommand());
|
performCommand(new DeselectAllItemsCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SHIFT + Home = Select all above playhead
|
||||||
|
if (FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.HOME)
|
||||||
|
{
|
||||||
|
// CTRL + SHIFT + Home = Inverse - deselect all above playhead
|
||||||
|
if (FlxG.keys.pressed.CONTROL)
|
||||||
|
performCommand(new DeselectAllItemsBetweenTimeCommand(scrollPositionInMs + playheadPositionInMs, true, true, true));
|
||||||
|
else
|
||||||
|
performCommand(new SelectAllItemsBetweenTimeCommand(scrollPositionInMs + playheadPositionInMs, true, true, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHIFT + End = Select all below playhead
|
||||||
|
if (FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.END)
|
||||||
|
{
|
||||||
|
// CTRL + SHIFT + Home = Inverse - deselect all below playhead
|
||||||
|
if (FlxG.keys.pressed.CONTROL)
|
||||||
|
performCommand(new DeselectAllItemsBetweenTimeCommand(scrollPositionInMs + playheadPositionInMs, false, true, true));
|
||||||
|
else
|
||||||
|
performCommand(new SelectAllItemsBetweenTimeCommand(scrollPositionInMs + playheadPositionInMs, false, true, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6479,6 +6547,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
|
|
||||||
LoadingState.loadPlayState(targetStateParams, false, true, function(targetState)
|
LoadingState.loadPlayState(targetStateParams, false, true, function(targetState)
|
||||||
{
|
{
|
||||||
|
if (playbarHeadDragging) playbarHeadDragging = false;
|
||||||
// Apply volume settings.
|
// Apply volume settings.
|
||||||
if (playtestAudioSettings)
|
if (playtestAudioSettings)
|
||||||
{
|
{
|
||||||
@@ -6924,7 +6993,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
{
|
{
|
||||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||||
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [{
|
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [{
|
||||||
text: "Take Me There",
|
text: "Open In Folder",
|
||||||
callback: openBackupsFolder,
|
callback: openBackupsFolder,
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
@@ -7066,6 +7135,50 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
menubarItemRedo.text = 'Redo ${redoHistory[redoHistory.length - 1].toString()}';
|
menubarItemRedo.text = 'Redo ${redoHistory[redoHistory.length - 1].toString()}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (clipboardDirty)
|
||||||
|
{
|
||||||
|
clipboardDirty = false;
|
||||||
|
|
||||||
|
if (funkin.util.ClipboardUtil.getClipboard() == null || !clipboardValid)
|
||||||
|
{
|
||||||
|
menubarItemPaste.disabled = true;
|
||||||
|
menubarItemPasteUnsnapped.disabled = true;
|
||||||
|
clipboardValid = false;
|
||||||
|
}
|
||||||
|
else if (clipboardValid)
|
||||||
|
{
|
||||||
|
menubarItemPaste.disabled = false;
|
||||||
|
menubarItemPasteUnsnapped.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editButtonsDirty)
|
||||||
|
{
|
||||||
|
editButtonsDirty = false;
|
||||||
|
|
||||||
|
if (currentEventSelection.length > 0 || currentNoteSelection.length > 0)
|
||||||
|
{
|
||||||
|
menubarItemCopy.disabled = false;
|
||||||
|
menubarItemCut.disabled = false;
|
||||||
|
menubarItemDelete.disabled = false;
|
||||||
|
menubarItemSelectNone.disabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
menubarItemCopy.disabled = true;
|
||||||
|
menubarItemCut.disabled = true;
|
||||||
|
menubarItemDelete.disabled = true;
|
||||||
|
menubarItemSelectNone.disabled = true;
|
||||||
|
}
|
||||||
|
if (currentNoteSelection.length > 0)
|
||||||
|
{
|
||||||
|
menubarItemFlipNotes.disabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
menubarItemFlipNotes.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class AddEventsCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
@@ -52,10 +53,12 @@ class AddEventsCommand implements ChartEditorCommand
|
|||||||
|
|
||||||
state.currentNoteSelection = [];
|
state.currentNoteSelection = [];
|
||||||
state.currentEventSelection = [];
|
state.currentEventSelection = [];
|
||||||
|
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class AddNotesCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
@@ -56,6 +57,7 @@ class AddNotesCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class CopyItemsCommand implements ChartEditorCommand
|
|||||||
});
|
});
|
||||||
|
|
||||||
performVisuals(state);
|
performVisuals(state);
|
||||||
|
state.clipboardDirty = true;
|
||||||
|
state.clipboardValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function performVisuals(state:ChartEditorState):Void
|
function performVisuals(state:ChartEditorState):Void
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ class CutItemsCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
state.clipboardDirty = true;
|
||||||
|
state.clipboardValid = true;
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ class CutItemsCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package funkin.ui.debug.charting.commands;
|
||||||
|
|
||||||
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
|
import funkin.data.song.SongData.SongEventData;
|
||||||
|
import funkin.data.song.SongDataUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command that deselects all selected notes and/or events above or past the time given in the chart editor.
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||||
|
class DeselectAllItemsBetweenTimeCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
var time:Float;
|
||||||
|
var above:Bool;
|
||||||
|
|
||||||
|
var notes:Array<SongNoteData>;
|
||||||
|
var events:Array<SongEventData>;
|
||||||
|
|
||||||
|
var shouldDeselectNotes:Bool;
|
||||||
|
var shouldDeselectEvents:Bool;
|
||||||
|
|
||||||
|
public function new(time:Float, above:Bool, shouldDeselectNotes:Bool, shouldDeselectEvents:Bool)
|
||||||
|
{
|
||||||
|
this.time = time;
|
||||||
|
this.above = above;
|
||||||
|
|
||||||
|
this.notes = [];
|
||||||
|
this.events = [];
|
||||||
|
|
||||||
|
this.shouldDeselectNotes = shouldDeselectNotes;
|
||||||
|
this.shouldDeselectEvents = shouldDeselectEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
if (above)
|
||||||
|
{
|
||||||
|
if (shouldDeselectNotes)
|
||||||
|
{
|
||||||
|
for (i in 0...state.currentSongChartNoteData.length)
|
||||||
|
{
|
||||||
|
if (state.currentSongChartNoteData[i].time < time)
|
||||||
|
notes.push(state.currentSongChartNoteData[i]);
|
||||||
|
else
|
||||||
|
// We've reached the end of the notes above this time,
|
||||||
|
// there's no reason to waste our time running this loop to completion
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldDeselectEvents)
|
||||||
|
{
|
||||||
|
for (i in 0...state.currentSongChartEventData.length)
|
||||||
|
{
|
||||||
|
if (state.currentSongChartEventData[i].time < time)
|
||||||
|
events.push(state.currentSongChartEventData[i]);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Deselecting below the time given
|
||||||
|
{
|
||||||
|
if (shouldDeselectNotes)
|
||||||
|
{
|
||||||
|
for (i in 0...state.currentSongChartNoteData.length)
|
||||||
|
{
|
||||||
|
// Backwards for loop (kinda). Neat!
|
||||||
|
if (state.currentSongChartNoteData[state.currentSongChartNoteData.length - i - 1].time > time)
|
||||||
|
notes.push(state.currentSongChartNoteData[state.currentSongChartNoteData.length - i - 1]);
|
||||||
|
else
|
||||||
|
// We've reached the end of the notes below this time,
|
||||||
|
// there's no reason to waste our time running this loop to completion
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldDeselectEvents)
|
||||||
|
{
|
||||||
|
for (i in 0...state.currentSongChartEventData.length)
|
||||||
|
{
|
||||||
|
if (state.currentSongChartEventData[state.currentSongChartEventData.length - i - 1].time > time)
|
||||||
|
events.push(state.currentSongChartEventData[state.currentSongChartEventData.length- i - 1]);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||||
|
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in this.notes)
|
||||||
|
{
|
||||||
|
state.currentNoteSelection.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (event in this.events)
|
||||||
|
{
|
||||||
|
state.currentEventSelection.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||||
|
{
|
||||||
|
// This command is undoable. Add to the history if we actually performed an action.
|
||||||
|
return (notes.length > 0 || events.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var isPlural = (notes.length + events.length) > 1;
|
||||||
|
var notesOnly = (notes.length > 0 && events.length == 0);
|
||||||
|
var eventsOnly = (notes.length == 0 && events.length > 0);
|
||||||
|
|
||||||
|
if (notesOnly)
|
||||||
|
{
|
||||||
|
return 'Deselect ${notes.length} ${isPlural ? 'Notes' : 'Note'}';
|
||||||
|
}
|
||||||
|
else if (eventsOnly)
|
||||||
|
{
|
||||||
|
return 'Deselect ${events.length} ${isPlural ? 'Events' : 'Event'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Deselect ${notes.length + events.length} Items';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ class DeselectAllItemsCommand implements ChartEditorCommand
|
|||||||
state.currentEventSelection = [];
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
@@ -35,6 +36,7 @@ class DeselectAllItemsCommand implements ChartEditorCommand
|
|||||||
state.currentEventSelection = previousEventSelection;
|
state.currentEventSelection = previousEventSelection;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldAddToHistory(state:ChartEditorState):Bool
|
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class DeselectItemsCommand implements ChartEditorCommand
|
|||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
@@ -44,6 +45,7 @@ class DeselectItemsCommand implements ChartEditorCommand
|
|||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldAddToHistory(state:ChartEditorState):Bool
|
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class InvertSelectedItemsCommand implements ChartEditorCommand
|
|||||||
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
|
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
@@ -37,6 +38,7 @@ class InvertSelectedItemsCommand implements ChartEditorCommand
|
|||||||
state.currentEventSelection = previousEventSelection;
|
state.currentEventSelection = previousEventSelection;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldAddToHistory(state:ChartEditorState):Bool
|
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||||
|
|||||||
@@ -15,12 +15,16 @@ class MoveEventsCommand implements ChartEditorCommand
|
|||||||
var events:Array<SongEventData>;
|
var events:Array<SongEventData>;
|
||||||
var movedEvents:Array<SongEventData>;
|
var movedEvents:Array<SongEventData>;
|
||||||
var offset:Float;
|
var offset:Float;
|
||||||
|
var setPos:Bool;
|
||||||
|
|
||||||
public function new(events:Array<SongEventData>, offset:Float)
|
public function new(events:Array<SongEventData>, offset:Float, setPos:Bool = false, offsetInSteps:Bool = false)
|
||||||
{
|
{
|
||||||
// Clone the notes to prevent editing from affecting the history.
|
// Clone the events to prevent editing from affecting the history.
|
||||||
this.events = [for (event in events) event.clone()];
|
this.events = [for (event in events) event.clone()];
|
||||||
|
if (offsetInSteps) this.offset = Conductor.instance.getStepTimeInMs(offset);
|
||||||
|
else
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
|
this.setPos = setPos;
|
||||||
this.movedEvents = [];
|
this.movedEvents = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,8 +36,11 @@ class MoveEventsCommand implements ChartEditorCommand
|
|||||||
|
|
||||||
for (event in events)
|
for (event in events)
|
||||||
{
|
{
|
||||||
// Clone the notes to prevent editing from affecting the history.
|
// Clone the events to prevent editing from affecting the history.
|
||||||
var resultEvent = event.clone();
|
var resultEvent = event.clone();
|
||||||
|
// If setting position, use the offset as the resulting time
|
||||||
|
if (setPos) resultEvent.time = offset.clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||||
|
else
|
||||||
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||||
|
|
||||||
movedEvents.push(resultEvent);
|
movedEvents.push(resultEvent);
|
||||||
|
|||||||
@@ -19,12 +19,15 @@ class MoveItemsCommand implements ChartEditorCommand
|
|||||||
var offset:Float;
|
var offset:Float;
|
||||||
var columns:Int;
|
var columns:Int;
|
||||||
|
|
||||||
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, offset:Float, columns:Int)
|
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, offset:Float, columns:Int, offsetInSteps:Bool = false,
|
||||||
|
roundStepTimeToNearestStep:Bool = false)
|
||||||
{
|
{
|
||||||
// Clone the notes to prevent editing from affecting the history.
|
// Clone the notes to prevent editing from affecting the history.
|
||||||
this.notes = notes.clone();
|
this.notes = notes.clone();
|
||||||
this.events = events.clone();
|
this.events = events.clone();
|
||||||
this.offset = offset;
|
if (offsetInSteps) this.offset = Conductor.instance.getStepTimeInMs(offset);
|
||||||
|
else
|
||||||
|
this.offset = offset;
|
||||||
this.columns = columns;
|
this.columns = columns;
|
||||||
this.movedNotes = [];
|
this.movedNotes = [];
|
||||||
this.movedEvents = [];
|
this.movedEvents = [];
|
||||||
|
|||||||
@@ -15,13 +15,17 @@ class MoveNotesCommand implements ChartEditorCommand
|
|||||||
var movedNotes:Array<SongNoteData>;
|
var movedNotes:Array<SongNoteData>;
|
||||||
var offset:Float;
|
var offset:Float;
|
||||||
var columns:Int;
|
var columns:Int;
|
||||||
|
var setPos:Bool;
|
||||||
|
|
||||||
public function new(notes:Array<SongNoteData>, offset:Float, columns:Int)
|
public function new(notes:Array<SongNoteData>, offset:Float, columns:Int, setPos:Bool = false, offsetInSteps:Bool = false)
|
||||||
{
|
{
|
||||||
// Clone the notes to prevent editing from affecting the history.
|
// Clone the notes to prevent editing from affecting the history.
|
||||||
this.notes = [for (note in notes) note.clone()];
|
this.notes = [for (note in notes) note.clone()];
|
||||||
this.offset = offset;
|
if (offsetInSteps) this.offset = Conductor.instance.getStepTimeInMs(offset);
|
||||||
|
else
|
||||||
|
this.offset = offset;
|
||||||
this.columns = columns;
|
this.columns = columns;
|
||||||
|
this.setPos = setPos;
|
||||||
this.movedNotes = [];
|
this.movedNotes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +39,9 @@ class MoveNotesCommand implements ChartEditorCommand
|
|||||||
{
|
{
|
||||||
// Clone the notes to prevent editing from affecting the history.
|
// Clone the notes to prevent editing from affecting the history.
|
||||||
var resultNote = note.clone();
|
var resultNote = note.clone();
|
||||||
|
// If setting position, use the offset as the resulting time
|
||||||
|
if (setPos) resultNote.time = offset.clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||||
|
else
|
||||||
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
|
||||||
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
|
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
|
||||||
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
|
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
|
||||||
|
|||||||
@@ -22,18 +22,27 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||||||
var removedNotes:Array<SongNoteData> = [];
|
var removedNotes:Array<SongNoteData> = [];
|
||||||
var isRedo:Bool = false;
|
var isRedo:Bool = false;
|
||||||
|
|
||||||
|
var currentClipboard:SongClipboardItems =
|
||||||
|
{
|
||||||
|
valid: false,
|
||||||
|
notes: [],
|
||||||
|
events: []
|
||||||
|
};
|
||||||
|
|
||||||
public function new(targetTimestamp:Float)
|
public function new(targetTimestamp:Float)
|
||||||
{
|
{
|
||||||
this.targetTimestamp = targetTimestamp;
|
this.targetTimestamp = targetTimestamp;
|
||||||
|
// Doing this here so that clearing or changing the clipboard doesn't break the redo and string.
|
||||||
|
this.currentClipboard = SongDataUtils.readItemsFromClipboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
|
||||||
|
|
||||||
if (currentClipboard.valid != true)
|
if (currentClipboard.valid != true)
|
||||||
{
|
{
|
||||||
state.error('Failed to Paste', 'Could not parse clipboard contents.');
|
state.error('Failed to Paste', 'Could not parse clipboard contents.');
|
||||||
|
state.clipboardDirty = true;
|
||||||
|
state.clipboardValid = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +66,7 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
|
|
||||||
@@ -93,6 +103,7 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
state.editButtonsDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
|
|
||||||
@@ -107,8 +118,6 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
|
||||||
|
|
||||||
var len:Int = currentClipboard.notes.length + currentClipboard.events.length;
|
var len:Int = currentClipboard.notes.length + currentClipboard.events.length;
|
||||||
|
|
||||||
if (currentClipboard.notes.length == 0) return 'Paste $len Events';
|
if (currentClipboard.notes.length == 0) return 'Paste $len Events';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user