mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-12-06 12:18:38 +00:00
Merge branch 'feature/chart-editor'
This commit is contained in:
commit
709cfab6a7
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
export/
|
.DS_STORE
|
||||||
|
.haxelib/
|
||||||
.vscode/
|
.vscode/
|
||||||
APIStuff.hx
|
APIStuff.hx
|
||||||
.DS_STORE
|
dump/
|
||||||
|
export/
|
||||||
RECOVER_*.fla
|
RECOVER_*.fla
|
||||||
.haxelib/
|
|
||||||
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
|
|
@ -28,5 +28,6 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceRoot}/export/debug/macos/bin/Funkin.app/Contents/MacOS/Funkin",
|
"program": "${workspaceRoot}/export/debug/macos/bin/Funkin.app/Contents/MacOS/Funkin",
|
||||||
"preLaunchTask": "debug: mac"
|
"preLaunchTask": "debug: mac"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
Project.xml
68
Project.xml
|
|
@ -54,7 +54,7 @@
|
||||||
<library name="week5" preload="true" />
|
<library name="week5" preload="true" />
|
||||||
<library name="week6" preload="true" />
|
<library name="week6" preload="true" />
|
||||||
<library name="week7" preload="true" />
|
<library name="week7" preload="true" />
|
||||||
<library name="week8" preload="true" />
|
<library name="weekend1" preload="true" />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section if="NO_PRELOAD_ALL">
|
<section if="NO_PRELOAD_ALL">
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
<library name="week5" preload="false" />
|
<library name="week5" preload="false" />
|
||||||
<library name="week6" preload="false" />
|
<library name="week6" preload="false" />
|
||||||
<library name="week7" preload="false" />
|
<library name="week7" preload="false" />
|
||||||
<library name="week8" preload="false" />
|
<library name="weekend1" preload="false" />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web" />
|
||||||
|
|
@ -91,8 +91,8 @@
|
||||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3" unless="web" />
|
||||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg" if="web" />
|
||||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3" unless="web" />
|
||||||
<assets path="assets/week8" library="week8" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg" if="web" />
|
||||||
<assets path="assets/week8" library="week8" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3" unless="web" />
|
||||||
|
|
||||||
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
||||||
|
|
||||||
|
|
@ -125,62 +125,16 @@
|
||||||
<haxelib name="haxeui-core"/>
|
<haxelib name="haxeui-core"/>
|
||||||
<haxelib name="haxeui-flixel"/>
|
<haxelib name="haxeui-flixel"/>
|
||||||
|
|
||||||
<!--In case you want to use the ui package-->
|
|
||||||
<haxelib name="flixel-ui" />
|
<haxelib name="flixel-ui" />
|
||||||
<!--haxelib name="newgrounds" unless="switch"/> -->
|
<haxelib name="haxeui-core"/>
|
||||||
<haxelib name="faxe" if='switch' />
|
<haxelib name="haxeui-flixel"/>
|
||||||
<haxelib name="polymod" />
|
<haxelib name="polymod" />
|
||||||
|
<haxelib name="flxanimate" />
|
||||||
|
|
||||||
<haxelib name="thx.semver" />
|
<haxelib name="thx.semver" />
|
||||||
|
|
||||||
<!-- <haxelib name="colyseus"/> -->
|
|
||||||
<!-- <haxelib name="colyseus-websocket" /> -->
|
|
||||||
<!-- <haxelib name="newgrounds"/> -->
|
|
||||||
<haxelib name="hxcpp-debug-server" if="desktop debug" />
|
<haxelib name="hxcpp-debug-server" if="desktop debug" />
|
||||||
|
|
||||||
<!-- swf stufffff -->
|
|
||||||
<!-- <haxelib name="swf"/> -->
|
|
||||||
<!-- <library path="assets/tanky.swf" preload="true"/> -->
|
|
||||||
<!-- <library path="assets/tankBG.swf" preload="true"/> -->
|
|
||||||
|
|
||||||
<!-- <haxelib name="flixel-animate" /> -->
|
|
||||||
<!-- <haxelib name="spinehaxe" /> -->
|
|
||||||
<!-- https://github.com/ninjamuffin99/Flixel-Animate-Atlas-Player -->
|
|
||||||
|
|
||||||
|
|
||||||
<!--<haxelib name="discord_rpc" if="cpp"/> -->
|
|
||||||
<!-- foesn't work with neko -->
|
|
||||||
<!-- <haxelib name="hxcpp-debug-server" if="desktop"/> -->
|
|
||||||
|
|
||||||
<!-- <haxelib name="markdown" /> -->
|
|
||||||
<!-- <haxelib name="HtmlParser" /> -->
|
|
||||||
|
|
||||||
<!--In case you want to use nape with flixel-->
|
|
||||||
<!--<haxelib name="nape-haxe4" />-->
|
|
||||||
|
|
||||||
<!-- ______________________________ Haxedefines _____________________________ -->
|
|
||||||
|
|
||||||
<!--Enable the Flixel core recording system-->
|
|
||||||
<!--<haxedef name="FLX_RECORD" />-->
|
|
||||||
|
|
||||||
<!--Disable the right and middle mouse buttons-->
|
|
||||||
<!-- <haxedef name="FLX_NO_MOUSE_ADVANCED" /> -->
|
|
||||||
|
|
||||||
<!--Disable the native cursor API on Flash-->
|
|
||||||
<!--<haxedef name="FLX_NO_NATIVE_CURSOR" />-->
|
|
||||||
|
|
||||||
<!--Optimise inputs, be careful you will get null errors if you don't use conditionals in your game-->
|
|
||||||
<!-- <haxedef name="FLX_NO_MOUSE" if="mobile" /> -->
|
|
||||||
<!-- <haxedef name="FLX_NO_KEYBOARD" if="mobile" /> -->
|
|
||||||
<!-- <haxedef name="FLX_NO_TOUCH" if="desktop" /> -->
|
|
||||||
<!--<haxedef name="FLX_NO_GAMEPAD" />-->
|
|
||||||
|
|
||||||
<!--Disable the Flixel core sound tray-->
|
|
||||||
<!--<haxedef name="FLX_NO_SOUND_TRAY" />-->
|
|
||||||
|
|
||||||
<!--Disable the Flixel sound management code-->
|
|
||||||
<!--<haxedef name="FLX_NO_SOUND_SYSTEM" />-->
|
|
||||||
|
|
||||||
<!--Disable the Flixel core focus lost screen-->
|
<!--Disable the Flixel core focus lost screen-->
|
||||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
||||||
|
|
||||||
|
|
@ -202,6 +156,11 @@
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
||||||
|
|
||||||
|
<!-- Ensure all UI components are available at runtime. -->
|
||||||
|
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
||||||
|
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
||||||
|
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
||||||
|
|
||||||
<!-- Necessary to provide stack traces for HScript. -->
|
<!-- Necessary to provide stack traces for HScript. -->
|
||||||
<haxedef name="hscriptPos" />
|
<haxedef name="hscriptPos" />
|
||||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||||
|
|
@ -222,6 +181,7 @@
|
||||||
|
|
||||||
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
|
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
|
||||||
<haxedef name="CAN_CHEAT" if="switch debug" />
|
<haxedef name="CAN_CHEAT" if="switch debug" />
|
||||||
|
<haxedef name="haxeui_no_mouse_reset" />
|
||||||
|
|
||||||
<!-- Skip the Intro -->
|
<!-- Skip the Intro -->
|
||||||
<section if="debug">
|
<section if="debug">
|
||||||
|
|
@ -263,8 +223,6 @@
|
||||||
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
|
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
|
||||||
<!-- The base path from which scripts should be accessed. -->
|
<!-- The base path from which scripts should be accessed. -->
|
||||||
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
|
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
|
||||||
<!-- Determines the precision required for mods to be compatible. -->
|
|
||||||
<haxedef name="POLYMOD_API_VERSION_MATCH" value="MATCH_MINOR" />
|
|
||||||
<!-- Determines the subdirectory of the mod folder used for file appending. -->
|
<!-- Determines the subdirectory of the mod folder used for file appending. -->
|
||||||
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
|
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
|
||||||
<!-- Determines the subdirectory of the mod folder used for file merges. -->
|
<!-- Determines the subdirectory of the mod folder used for file merges. -->
|
||||||
|
|
|
||||||
108
README.md
108
README.md
|
|
@ -1,94 +1,44 @@
|
||||||
# Friday Night Funkin
|
# Friday Night Funkin' · [](https://github.com/ninjamuffin99/Funkin/blob/master/LICENSE.md)  [](https://github.com/ninjamuffin99/Funkin/pulls)
|
||||||
|
|
||||||
This is the repository for Friday Night Funkin, a game originally made for Ludum Dare 47 "Stuck In a Loop".
|
Friday Night Funkin' is a rhythm game that doubles as a playable cartoon. Built using HaxeFlixel for Ludem Dare 47.
|
||||||
|
|
||||||
Play free in your browser on Newgrounds: https://www.newgrounds.com/portal/view/770371
|
|
||||||
Download the game for desktop on Itch.io: https://ninja-muffin24.itch.io/funkin
|
|
||||||
|
|
||||||
# Credits
|
|
||||||
|
|
||||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Programmer
|
|
||||||
- [PhantomArcade3K](https://twitter.com/phantomarcade3k) and [Evilsk8r](https://twitter.com/evilsk8r) - Art
|
|
||||||
- [Kawaisprite](https://twitter.com/kawaisprite) - Musician
|
|
||||||
|
|
||||||
This game was made with love to Newgrounds and it's community. Extra love to Tom Fulp.
|
This game was made with love to Newgrounds and it's community. Extra love to Tom Fulp.
|
||||||
|
|
||||||
|
## [Play for free on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
|
||||||
|
## [Download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
IF YOU MAKE A MOD AND DISTRIBUTE A MODIFIED / RECOMIPLED VERSION, YOU MUST OPEN SOURCE YOUR MOD AS WELL
|
# Getting Started
|
||||||
|
|
||||||
## Credits / shoutouts
|
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||||
|
|
||||||
|
To learn how to install the necessary dependencies and compile the game from source, please check out our [building the game]() guide.
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can actively participate in the development of Friday Night Funkin'.
|
||||||
|
|
||||||
|
# Credits and Special Thanks
|
||||||
|
|
||||||
## Build instructions
|
## Programming
|
||||||
|
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||||
|
- [MasterEric](https://twitter.com/EliteMasterEric) - Programmer
|
||||||
|
- [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
|
||||||
|
- [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
|
||||||
|
- Our contributors on GitHub
|
||||||
|
|
||||||
THESE INSTRUCTIONS ARE FOR COMPILING THE GAME'S SOURCE CODE!!!
|
## Art / Animation / UI
|
||||||
|
- [PhantomArcade3K](https://twitter.com/phantomarcade3k) - Artist and Animator
|
||||||
|
- [Evilsk8r](https://twitter.com/evilsk8r) - Art
|
||||||
|
- [Moawling](https://twitter.com/moawko) - Week 6 Pixel Art
|
||||||
|
- [IvanAlmighty](https://twitter.com/IvanA1mighty) - Misc UI Design
|
||||||
|
|
||||||
IF YOU WANT TO JUST DOWNLOAD AND INSTALL AND PLAY THE GAME NORMALLY, GO TO ITCH.IO TO DOWNLOAD THE GAME FOR PC, MAC, AND LINUX!!
|
## Music
|
||||||
|
- [Kawaisprite](https://twitter.com/kawaisprite) - Musician
|
||||||
|
- [BassetFilms](https://twitter.com/Bassetfilms) - Music for "Monster", Additional Character Design
|
||||||
|
|
||||||
https://ninja-muffin24.itch.io/funkin
|
## Special Thanks
|
||||||
|
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
|
||||||
IF YOU WANT TO COMPILE THE GAME YOURSELF, CONTINUE READING!!!
|
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
|
||||||
|
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||||
### Installing the Required Programs
|
|
||||||
|
|
||||||
First you need to install Haxe and HaxeFlixel. I'm too lazy to write and keep updated with that setup (which is pretty simple).
|
|
||||||
1. [Install Haxe 4.1.5](https://haxe.org/download/version/4.1.5/) (Download 4.1.5 instead of 4.2.0 because 4.2.0 is broken and is not working with gits properly...)
|
|
||||||
2. [Install HaxeFlixel](https://haxeflixel.com/documentation/install-haxeflixel/) after downloading Haxe
|
|
||||||
|
|
||||||
Other installations you'd need is the additional libraries, a fully updated list will be in `hmm.json` in the project root. Currently, these are all of the things you need to install:
|
|
||||||
```
|
|
||||||
haxelib --global install hmm
|
|
||||||
haxelib --global run hmm setup
|
|
||||||
hmm install
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- You'll also need to install a couple things that involve Gits. To do this, you need to do a few things first.
|
|
||||||
1. Download [git-scm](https://git-scm.com/downloads). Works for Windows, Mac, and Linux, just select your build.
|
|
||||||
2. Follow instructions to install the application properly.
|
|
||||||
3. Run `haxelib git polymod https://github.com/larsiusprime/polymod.git` to install Polymod.
|
|
||||||
4. Run `haxelib git discord_rpc https://github.com/Aidan63/linc_discord-rpc` to install Discord RPC. -->
|
|
||||||
|
|
||||||
You should have everything ready for compiling the game! Follow the guide below to continue!
|
|
||||||
|
|
||||||
### Ignored files
|
|
||||||
|
|
||||||
I gitignore the API keys for the game, so that no one can nab them and post fake highscores on the leaderboards. But because of that the game
|
|
||||||
doesn't compile without it.
|
|
||||||
|
|
||||||
Just make a file in `/source` and call it `APIStuff.hx`, and copy paste this into it
|
|
||||||
|
|
||||||
```haxe
|
|
||||||
package;
|
|
||||||
|
|
||||||
class APIStuff
|
|
||||||
{
|
|
||||||
inline public static var API:String = "51348:TtzK0rZ8";
|
|
||||||
inline public static var EncKey:String = "5NqKsSVSNKHbF9fPgZPqPg==";
|
|
||||||
inline public static var SESSION:String = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
and you should be good to go there.
|
|
||||||
|
|
||||||
### Compiling game
|
|
||||||
|
|
||||||
Once you have all those installed, it's pretty easy to compile the game. You just need to run 'lime test html5 -debug' in the root of the project to build and run the HTML5 version. (command prompt navigation guide can be found here: [https://ninjamuffin99.newgrounds.com/news/post/1090480](https://ninjamuffin99.newgrounds.com/news/post/1090480))
|
|
||||||
|
|
||||||
To run it from your desktop (Windows, Mac, Linux) it can be a bit more involved. For Linux, you only need to open a terminal in the project directory and run 'lime test linux -debug' and then run the executable file in export/release/linux/bin. For Windows, you need to install Visual Studio Community 2019. While installing VSC, don't click on any of the options to install workloads. Instead, go to the individual components tab and choose the following:
|
|
||||||
* MSVC v142 - VS 2019 C++ x64/x86 build tools
|
|
||||||
* Windows SDK (10.0.17763.0)
|
|
||||||
|
|
||||||
Once that is done you can open up a command line in the project's directory and run `lime test windows -debug`. Once that command finishes (it takes forever even on a higher end PC), you can run FNF from the .exe file under export\release\windows\bin
|
|
||||||
As for Mac, 'lime test mac -debug' should work, if not the internet surely has a guide on how to compile Haxe stuff for Mac.
|
|
||||||
|
|
||||||
### Additional guides
|
|
||||||
|
|
||||||
- [Command line basics](https://ninjamuffin99.newgrounds.com/news/post/1090480)
|
|
||||||
|
|
||||||
|
|
||||||
Commits are generally signed and verified, as of September 8th, 2021! Using GPG!
|
|
||||||
|
|
|
||||||
191
hmm.json
191
hmm.json
|
|
@ -1,81 +1,114 @@
|
||||||
{
|
{
|
||||||
"dependencies": [{
|
"dependencies": [
|
||||||
"name": "discord_rpc",
|
{
|
||||||
"type": "git",
|
"name": "discord_rpc",
|
||||||
"dir": null,
|
"type": "git",
|
||||||
"ref": "2d83fa8",
|
"dir": null,
|
||||||
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
"ref": "2d83fa8",
|
||||||
},
|
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
||||||
{
|
},
|
||||||
"name": "firetongue",
|
{
|
||||||
"type": "git",
|
"name": "flixel",
|
||||||
"dir": null,
|
"type": "git",
|
||||||
"ref": "c5666c8",
|
"dir": null,
|
||||||
"url": "https://github.com/larsiusprime/firetongue"
|
"ref": "dev",
|
||||||
},
|
"url": "https://github.com/MasterEric/flixel"
|
||||||
{
|
},
|
||||||
"name": "flixel",
|
{
|
||||||
"type": "git",
|
"name": "flixel-addons",
|
||||||
"dir": null,
|
"type": "git",
|
||||||
"ref": "93a049d6",
|
"dir": null,
|
||||||
"url": "https://github.com/haxeflixel/flixel"
|
"ref": "a3877f0",
|
||||||
},
|
"url": "https://github.com/MasterEric/flixel-addons"
|
||||||
{
|
},
|
||||||
"name": "flixel-addons",
|
{
|
||||||
"type": "haxelib",
|
"name": "flixel-addons",
|
||||||
"version": "2.11.0"
|
"type": "git",
|
||||||
},
|
"dir": null,
|
||||||
{
|
"ref": "dev",
|
||||||
"name": "flixel-ui",
|
"url": "https://github.com/MasterEric/flixel-addons"
|
||||||
"type": "haxelib",
|
},
|
||||||
"version": "2.4.0"
|
{
|
||||||
},
|
"name": "flixel-ui",
|
||||||
{
|
"type": "haxelib",
|
||||||
"name": "haxeui-core",
|
"version": "2.4.0"
|
||||||
"type": "haxelib",
|
},
|
||||||
"version": null
|
{
|
||||||
},
|
"name": "flxanimate",
|
||||||
{
|
"type": "git",
|
||||||
"name": "haxeui-flixel",
|
"dir": null,
|
||||||
"type": "haxelib",
|
"ref": "18b2060",
|
||||||
"version": null
|
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hscript",
|
"name": "format",
|
||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
"version": "2.5.0"
|
"version": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxcpp",
|
"name": "haxeui-core",
|
||||||
"type": "haxelib",
|
"type": "git",
|
||||||
"version": "4.2.1"
|
"dir": null,
|
||||||
},
|
"ref": "fc8d656b",
|
||||||
{
|
"url": "https://github.com/haxeui/haxeui-core/"
|
||||||
"name": "hxcpp-debug-server",
|
},
|
||||||
"type": "haxelib",
|
{
|
||||||
"version": "1.2.4"
|
"name": "haxeui-flixel",
|
||||||
},
|
"type": "git",
|
||||||
{
|
"dir": null,
|
||||||
"name": "lime",
|
"ref": "80941a7",
|
||||||
"type": "haxelib",
|
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||||
"version": "7.9.0"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "hmm",
|
||||||
"name": "openfl",
|
"type": "haxelib",
|
||||||
"type": "haxelib",
|
"version": "2.1.0"
|
||||||
"version": "9.1.0"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "hscript",
|
||||||
"name": "polymod",
|
"type": "haxelib",
|
||||||
"type": "git",
|
"version": "2.5.0"
|
||||||
"dir": null,
|
},
|
||||||
"ref": "c858b48",
|
{
|
||||||
"url": "https://github.com/larsiusprime/polymod"
|
"name": "hxcpp",
|
||||||
},
|
"type": "haxelib",
|
||||||
{
|
"version": "4.2.1"
|
||||||
"name": "thx.semver",
|
},
|
||||||
"type": "haxelib",
|
{
|
||||||
"version": "0.2.2"
|
"name": "hxcpp-debug-server",
|
||||||
}
|
"type": "haxelib",
|
||||||
]
|
"version": "1.2.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hxp",
|
||||||
|
"type": "haxelib",
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lime",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "develop",
|
||||||
|
"url": "https://github.com/openfl/lime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "openfl",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "develop",
|
||||||
|
"url": "https://github.com/openfl/openfl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "polymod",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "develop",
|
||||||
|
"url": "https://github.com/larsiusprime/polymod"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "thx.semver",
|
||||||
|
"type": "haxelib",
|
||||||
|
"version": "0.2.2"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"lineEnds": {
|
"lineEnds": {
|
||||||
"leftCurly": "both",
|
"leftCurly": "both",
|
||||||
"rightCurly": "both",
|
"rightCurly": "both",
|
||||||
|
"emptyCurly": "break",
|
||||||
"objectLiteralCurly": {
|
"objectLiteralCurly": {
|
||||||
"leftCurly": "after"
|
"leftCurly": "after"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1300
rfc/chart-format/chartformat-7.jsonc
Normal file
1300
rfc/chart-format/chartformat-7.jsonc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,8 @@ package;
|
||||||
|
|
||||||
import flixel.FlxGame;
|
import flixel.FlxGame;
|
||||||
import flixel.FlxState;
|
import flixel.FlxState;
|
||||||
import funkin.InitState;
|
|
||||||
import funkin.MemoryCounter;
|
import funkin.MemoryCounter;
|
||||||
|
import haxe.ui.Toolkit;
|
||||||
import openfl.Lib;
|
import openfl.Lib;
|
||||||
import openfl.display.FPS;
|
import openfl.display.FPS;
|
||||||
import openfl.display.Sprite;
|
import openfl.display.Sprite;
|
||||||
|
|
@ -15,7 +15,7 @@ class Main extends Sprite
|
||||||
{
|
{
|
||||||
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
||||||
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
||||||
var initialState:Class<FlxState> = InitState; // The FlxState the game starts with.
|
var initialState:Class<FlxState> = funkin.InitState; // The FlxState the game starts with.
|
||||||
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
|
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
|
||||||
#if web
|
#if web
|
||||||
var framerate:Int = 60; // How many frames per second the game should run at.
|
var framerate:Int = 60; // How many frames per second the game should run at.
|
||||||
|
|
@ -69,26 +69,21 @@ class Main extends Sprite
|
||||||
|
|
||||||
private function setupGame():Void
|
private function setupGame():Void
|
||||||
{
|
{
|
||||||
// Lib.current.stage.color = null;
|
/**
|
||||||
|
* The `zoom` argument of FlxGame was removed in the dev branch of Flixel,
|
||||||
var stageWidth:Int = Lib.current.stage.stageWidth;
|
* since it was considered confusing and unintuitive.
|
||||||
var stageHeight:Int = Lib.current.stage.stageHeight;
|
* If you want to change how the game scales when you resize the window,
|
||||||
|
* you can use `FlxG.scaleMode`.
|
||||||
if (zoom == -1)
|
* -Eric
|
||||||
{
|
*/
|
||||||
var ratioX:Float = stageWidth / gameWidth;
|
|
||||||
var ratioY:Float = stageHeight / gameHeight;
|
|
||||||
zoom = Math.min(ratioX, ratioY);
|
|
||||||
gameWidth = Math.ceil(stageWidth / zoom);
|
|
||||||
gameHeight = Math.ceil(stageHeight / zoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !debug
|
#if !debug
|
||||||
initialState = TitleState;
|
initialState = funkin.TitleState;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
haxe.ui.Toolkit.init();
|
initHaxeUI();
|
||||||
addChild(new FlxGame(gameWidth, gameHeight, initialState, zoom, framerate, framerate, skipSplash, startFullscreen));
|
|
||||||
|
addChild(new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen));
|
||||||
|
|
||||||
#if debug
|
#if debug
|
||||||
fpsCounter = new FPS(10, 3, 0xFFFFFF);
|
fpsCounter = new FPS(10, 3, 0xFFFFFF);
|
||||||
|
|
@ -98,53 +93,14 @@ class Main extends Sprite
|
||||||
addChild(memoryCounter);
|
addChild(memoryCounter);
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
|
||||||
/*
|
|
||||||
video = new Video();
|
|
||||||
addChild(video);
|
|
||||||
|
|
||||||
var netConnection = new NetConnection();
|
|
||||||
netConnection.connect(null);
|
|
||||||
|
|
||||||
netStream = new NetStream(netConnection);
|
|
||||||
netStream.client = {onMetaData: client_onMetaData};
|
|
||||||
netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, netStream_onAsyncError);
|
|
||||||
|
|
||||||
#if (js && html5)
|
|
||||||
overlay = new Sprite();
|
|
||||||
overlay.graphics.beginFill(0, 0.5);
|
|
||||||
overlay.graphics.drawRect(0, 0, 560, 320);
|
|
||||||
overlay.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown);
|
|
||||||
overlay.buttonMode = true;
|
|
||||||
addChild(overlay);
|
|
||||||
|
|
||||||
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus);
|
|
||||||
#else
|
|
||||||
netStream.play("assets/preload/music/dredd.mp4");
|
|
||||||
#end
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
private function client_onMetaData(metaData:Dynamic)
|
|
||||||
{
|
|
||||||
video.attachNetStream(netStream);
|
|
||||||
|
|
||||||
video.width = video.videoWidth;
|
function initHaxeUI()
|
||||||
video.height = video.videoHeight;
|
{
|
||||||
}
|
// Calling this before any HaxeUI components get used is important:
|
||||||
|
// - It initializes the theme styles.
|
||||||
private function netStream_onAsyncError(event:AsyncErrorEvent):Void
|
// - It scans the class path and registers any HaxeUI components.
|
||||||
{
|
Toolkit.init();
|
||||||
trace("Error loading video");
|
Toolkit.theme = "dark"; // don't be cringe
|
||||||
}
|
}
|
||||||
|
|
||||||
private function netConnection_onNetStatus(event:NetStatusEvent):Void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private function overlay_onMouseDown(event:MouseEvent):Void
|
|
||||||
{
|
|
||||||
netStream.play("assets/preload/music/dredd.mp4");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
|
@ -11,7 +10,8 @@ using StringTools;
|
||||||
/**
|
/**
|
||||||
* Loosley based on FlxTypeText lolol
|
* Loosley based on FlxTypeText lolol
|
||||||
*/
|
*/
|
||||||
@:deprecated("Use ui.AtlasText instead")
|
// TODO: Re-eanble this
|
||||||
|
// @:deprecated("Use ui.AtlasText instead")
|
||||||
class Alphabet extends FlxSpriteGroup
|
class Alphabet extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
public var delay:Float = 0.05;
|
public var delay:Float = 0.05;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
|
import flixel.util.FlxSignal;
|
||||||
import funkin.SongLoad.SwagSong;
|
import funkin.SongLoad.SwagSong;
|
||||||
|
import funkin.play.song.Song.SongDifficulty;
|
||||||
|
import funkin.play.song.SongData.SongTimeChange;
|
||||||
|
|
||||||
/**
|
|
||||||
* ...
|
|
||||||
* @author
|
|
||||||
*/
|
|
||||||
typedef BPMChangeEvent =
|
typedef BPMChangeEvent =
|
||||||
{
|
{
|
||||||
var stepTime:Int;
|
var stepTime:Int;
|
||||||
|
|
@ -16,12 +15,45 @@ typedef BPMChangeEvent =
|
||||||
class Conductor
|
class Conductor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Beats per minute of the song.
|
* The list of time changes in the song.
|
||||||
|
* There should be at least one time change (at the beginning of the song) to define the BPM.
|
||||||
*/
|
*/
|
||||||
public static var bpm:Float = 100;
|
private static var timeChanges:Array<SongTimeChange> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of a beat in millisecond.
|
* The current time change.
|
||||||
|
*/
|
||||||
|
private static var currentTimeChange:SongTimeChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current position in the song in milliseconds.
|
||||||
|
* Updated every frame based on the audio position.
|
||||||
|
*/
|
||||||
|
public static var songPosition:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beats per minute of the current song at the current time.
|
||||||
|
*/
|
||||||
|
public static var bpm(get, null):Float;
|
||||||
|
|
||||||
|
static function get_bpm():Float
|
||||||
|
{
|
||||||
|
if (bpmOverride != null)
|
||||||
|
return bpmOverride;
|
||||||
|
|
||||||
|
if (currentTimeChange == null)
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
return currentTimeChange.bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static var bpmOverride:Null<Float> = null;
|
||||||
|
|
||||||
|
// OLD, replaced with timeChanges.
|
||||||
|
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of a beat in millisecond. Calculated based on bpm.
|
||||||
*/
|
*/
|
||||||
public static var crochet(get, null):Float;
|
public static var crochet(get, null):Float;
|
||||||
|
|
||||||
|
|
@ -31,7 +63,7 @@ class Conductor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of a step in milliseconds.
|
* Duration of a step in milliseconds. Calculated based on bpm.
|
||||||
*/
|
*/
|
||||||
public static var stepCrochet(get, null):Float;
|
public static var stepCrochet(get, null):Float;
|
||||||
|
|
||||||
|
|
@ -40,16 +72,121 @@ class Conductor
|
||||||
return crochet / 4;
|
return crochet / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var songPosition:Float;
|
/**
|
||||||
|
* Current position in the song, in beats.
|
||||||
|
**/
|
||||||
|
public static var currentBeat(default, null):Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current position in the song, in steps.
|
||||||
|
*/
|
||||||
|
public static var currentStep(default, null):Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current position in the song, in steps and fractions of a step.
|
||||||
|
*/
|
||||||
|
public static var currentStepTime(default, null):Float;
|
||||||
|
|
||||||
|
public static var beatHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
public static var stepHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
public static var lastSongPos:Float;
|
public static var lastSongPos:Float;
|
||||||
public static var visualOffset:Float = 0;
|
public static var visualOffset:Float = 0;
|
||||||
public static var audioOffset:Float = 0;
|
public static var audioOffset:Float = 0;
|
||||||
public static var offset:Float = 0;
|
public static var offset:Float = 0;
|
||||||
|
|
||||||
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
|
// TODO: Add code to update this.
|
||||||
|
public static var beatsPerMeasure:Int = 4;
|
||||||
|
|
||||||
public function new() {}
|
private function new()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLastBPMChange()
|
||||||
|
{
|
||||||
|
var lastChange:BPMChangeEvent = {
|
||||||
|
stepTime: 0,
|
||||||
|
songTime: 0,
|
||||||
|
bpm: 0
|
||||||
|
}
|
||||||
|
for (i in 0...Conductor.bpmChangeMap.length)
|
||||||
|
{
|
||||||
|
if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
|
||||||
|
lastChange = Conductor.bpmChangeMap[i];
|
||||||
|
|
||||||
|
if (Conductor.songPosition < Conductor.bpmChangeMap[i].songTime)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return lastChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forcibly defines the current BPM of the song.
|
||||||
|
* Useful for things like the chart editor that need to manipulate BPM in real time.
|
||||||
|
*
|
||||||
|
* WARNING: Avoid this for things like setting the BPM of the title screen music,
|
||||||
|
* you should have a metadata file for it instead.
|
||||||
|
*/
|
||||||
|
public static function forceBPM(bpm:Float)
|
||||||
|
{
|
||||||
|
Conductor.bpmOverride = bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the conductor with the current song position.
|
||||||
|
* BPM, current step, etc. will be re-calculated based on the song position.
|
||||||
|
*
|
||||||
|
* @param songPosition The current position in the song in milliseconds.
|
||||||
|
* Leave blank to use the FlxG.sound.music position.
|
||||||
|
*/
|
||||||
|
public static function update(songPosition:Float = null)
|
||||||
|
{
|
||||||
|
if (songPosition == null)
|
||||||
|
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + Conductor.offset) : 0;
|
||||||
|
|
||||||
|
var oldBeat = currentBeat;
|
||||||
|
var oldStep = currentStep;
|
||||||
|
|
||||||
|
Conductor.songPosition = songPosition;
|
||||||
|
// Conductor.bpm = Conductor.getLastBPMChange().bpm;
|
||||||
|
|
||||||
|
currentTimeChange = timeChanges[0];
|
||||||
|
for (i in 0...timeChanges.length)
|
||||||
|
{
|
||||||
|
if (songPosition >= timeChanges[i].timeStamp)
|
||||||
|
currentTimeChange = timeChanges[i];
|
||||||
|
|
||||||
|
if (songPosition < timeChanges[i].timeStamp)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTimeChange == null && bpmOverride == null)
|
||||||
|
{
|
||||||
|
trace('WARNING: Conductor is broken, timeChanges is empty.');
|
||||||
|
}
|
||||||
|
else if (currentTimeChange != null)
|
||||||
|
{
|
||||||
|
currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepCrochet;
|
||||||
|
currentStep = Math.floor(currentStepTime);
|
||||||
|
currentBeat = Math.floor(currentStep / 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Assume a constant BPM equal to the forced value.
|
||||||
|
currentStepTime = (songPosition / stepCrochet);
|
||||||
|
currentStep = Math.floor(currentStepTime);
|
||||||
|
currentBeat = Math.floor(currentStep / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlxSignals are really cool.
|
||||||
|
if (currentStep != oldStep)
|
||||||
|
stepHit.dispatch();
|
||||||
|
|
||||||
|
if (currentBeat != oldBeat)
|
||||||
|
beatHit.dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@:deprecated // Switch to TimeChanges instead.
|
||||||
public static function mapBPMChanges(song:SwagSong)
|
public static function mapBPMChanges(song:SwagSong)
|
||||||
{
|
{
|
||||||
bpmChangeMap = [];
|
bpmChangeMap = [];
|
||||||
|
|
@ -74,6 +211,56 @@ class Conductor
|
||||||
totalSteps += deltaSteps;
|
totalSteps += deltaSteps;
|
||||||
totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
|
totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
|
||||||
}
|
}
|
||||||
trace("new BPM map BUDDY " + bpmChangeMap);
|
}
|
||||||
|
|
||||||
|
public static function mapTimeChanges(currentChart:SongDifficulty)
|
||||||
|
{
|
||||||
|
var songTimeChanges:Array<SongTimeChange> = currentChart.timeChanges;
|
||||||
|
|
||||||
|
timeChanges = [];
|
||||||
|
|
||||||
|
for (currentTimeChange in songTimeChanges)
|
||||||
|
{
|
||||||
|
timeChanges.push(currentTimeChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace('Done mapping time changes: ' + timeChanges);
|
||||||
|
|
||||||
|
// Done.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a time in milliseconds, return a time in steps.
|
||||||
|
*/
|
||||||
|
public static function getTimeInSteps(ms:Float):Int
|
||||||
|
{
|
||||||
|
if (timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
// Assume a constant BPM equal to the forced value.
|
||||||
|
return Math.floor(ms / stepCrochet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var resultStep:Int = 0;
|
||||||
|
|
||||||
|
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||||
|
for (timeChange in timeChanges)
|
||||||
|
{
|
||||||
|
if (ms >= timeChange.timeStamp)
|
||||||
|
{
|
||||||
|
lastTimeChange = timeChange;
|
||||||
|
resultStep = lastTimeChange.beatTime * 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This time change is after the requested time.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet);
|
||||||
|
|
||||||
|
return resultStep;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
|
import flixel.util.FlxDirectionFlags;
|
||||||
import flixel.FlxObject;
|
import flixel.FlxObject;
|
||||||
import flixel.input.FlxInput;
|
import flixel.input.FlxInput;
|
||||||
import flixel.input.actions.FlxAction;
|
import flixel.input.actions.FlxAction;
|
||||||
|
|
@ -544,7 +545,7 @@ class Controls extends FlxActionSet
|
||||||
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bindSwipe(control:Control, swipeDir:Int = FlxObject.UP, ?swpLength:Float = 90)
|
public function bindSwipe(control:Control, swipeDir:Int = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||||
{
|
{
|
||||||
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
||||||
}
|
}
|
||||||
|
|
@ -635,16 +636,16 @@ class Controls extends FlxActionSet
|
||||||
#if FLX_TOUCH
|
#if FLX_TOUCH
|
||||||
// MAKE BETTER TOUCH BIND CODE
|
// MAKE BETTER TOUCH BIND CODE
|
||||||
|
|
||||||
bindSwipe(Control.NOTE_UP, FlxObject.UP, 40);
|
bindSwipe(Control.NOTE_UP, FlxDirectionFlags.UP, 40);
|
||||||
bindSwipe(Control.NOTE_DOWN, FlxObject.DOWN, 40);
|
bindSwipe(Control.NOTE_DOWN, FlxDirectionFlags.DOWN, 40);
|
||||||
bindSwipe(Control.NOTE_LEFT, FlxObject.LEFT, 40);
|
bindSwipe(Control.NOTE_LEFT, FlxDirectionFlags.LEFT, 40);
|
||||||
bindSwipe(Control.NOTE_RIGHT, FlxObject.RIGHT, 40);
|
bindSwipe(Control.NOTE_RIGHT, FlxDirectionFlags.RIGHT, 40);
|
||||||
|
|
||||||
// feels more like drag when up/down are inversed
|
// feels more like drag when up/down are inversed
|
||||||
bindSwipe(Control.UI_UP, FlxObject.DOWN);
|
bindSwipe(Control.UI_UP, FlxDirectionFlags.DOWN);
|
||||||
bindSwipe(Control.UI_DOWN, FlxObject.UP);
|
bindSwipe(Control.UI_DOWN, FlxDirectionFlags.UP);
|
||||||
bindSwipe(Control.UI_LEFT, FlxObject.LEFT);
|
bindSwipe(Control.UI_LEFT, FlxDirectionFlags.LEFT);
|
||||||
bindSwipe(Control.UI_RIGHT, FlxObject.RIGHT);
|
bindSwipe(Control.UI_RIGHT, FlxDirectionFlags.RIGHT);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if android
|
#if android
|
||||||
|
|
@ -870,7 +871,7 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||||
var activateLength:Float = 90;
|
var activateLength:Float = 90;
|
||||||
var hapticPressure:Int = 100;
|
var hapticPressure:Int = 100;
|
||||||
|
|
||||||
public function new(swipeDir:Int = FlxObject.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
public function new(swipeDir:Int = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||||
{
|
{
|
||||||
super(OTHER, swipeDir, Trigger);
|
super(OTHER, swipeDir, Trigger);
|
||||||
|
|
||||||
|
|
@ -933,9 +934,9 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||||
|
|
||||||
/* switch (inputID)
|
/* switch (inputID)
|
||||||
{
|
{
|
||||||
case FlxObject.UP:
|
case FlxDirectionFlags.UP:
|
||||||
return
|
return
|
||||||
case FlxObject.DOWN:
|
case FlxDirectionFlags.DOWN:
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
@ -955,13 +956,13 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||||
{
|
{
|
||||||
switch (inputID)
|
switch (inputID)
|
||||||
{
|
{
|
||||||
case FlxObject.UP:
|
case FlxDirectionFlags.UP:
|
||||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||||
case FlxObject.DOWN:
|
case FlxDirectionFlags.DOWN:
|
||||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||||
case FlxObject.LEFT:
|
case FlxDirectionFlags.LEFT:
|
||||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||||
case FlxObject.RIGHT:
|
case FlxDirectionFlags.RIGHT:
|
||||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import funkin.freeplayStuff.FreeplayScore;
|
||||||
import funkin.freeplayStuff.SongMenuItem;
|
import funkin.freeplayStuff.SongMenuItem;
|
||||||
import funkin.play.HealthIcon;
|
import funkin.play.HealthIcon;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
import funkin.shaderslmfao.AngleMask;
|
import funkin.shaderslmfao.AngleMask;
|
||||||
import funkin.shaderslmfao.PureColor;
|
import funkin.shaderslmfao.PureColor;
|
||||||
import funkin.shaderslmfao.StrokeShader;
|
import funkin.shaderslmfao.StrokeShader;
|
||||||
|
|
@ -80,7 +81,7 @@ class FreeplayState extends MusicBeatSubstate
|
||||||
#if debug
|
#if debug
|
||||||
isDebug = true;
|
isDebug = true;
|
||||||
addSong('Test', 1, 'bf-pixel');
|
addSong('Test', 1, 'bf-pixel');
|
||||||
addSong('Pyro', 4, 'bf');
|
addSong('Pyro', 8, 'darnell');
|
||||||
#end
|
#end
|
||||||
|
|
||||||
var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
|
var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
|
||||||
|
|
@ -117,8 +118,7 @@ class FreeplayState extends MusicBeatSubstate
|
||||||
if (StoryMenuState.weekUnlocked[7] || isDebug)
|
if (StoryMenuState.weekUnlocked[7] || isDebug)
|
||||||
addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']);
|
addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']);
|
||||||
|
|
||||||
addWeek(["Darnell", "lit-up", "2hot"], 8, ['darnell']);
|
addWeek(["Darnell", "lit-up", "2hot", "blazin"], 8, ['darnell']);
|
||||||
addWeek(["bro"], 1, ['gf']);
|
|
||||||
|
|
||||||
// LOAD MUSIC
|
// LOAD MUSIC
|
||||||
|
|
||||||
|
|
@ -128,7 +128,6 @@ class FreeplayState extends MusicBeatSubstate
|
||||||
trace(FlxG.camera.zoom);
|
trace(FlxG.camera.zoom);
|
||||||
trace(FlxG.camera.initialZoom);
|
trace(FlxG.camera.initialZoom);
|
||||||
trace(FlxCamera.defaultZoom);
|
trace(FlxCamera.defaultZoom);
|
||||||
trace(FlxG.initialZoom);
|
|
||||||
|
|
||||||
var pinkBack:FlxSprite = new FlxSprite().loadGraphic(Paths.image('freeplay/pinkBack'));
|
var pinkBack:FlxSprite = new FlxSprite().loadGraphic(Paths.image('freeplay/pinkBack'));
|
||||||
pinkBack.color = 0xFFffd4e9; // sets it to pink!
|
pinkBack.color = 0xFFffd4e9; // sets it to pink!
|
||||||
|
|
@ -522,11 +521,10 @@ class FreeplayState extends MusicBeatSubstate
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
|
PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
|
||||||
|
PlayState.currentSong_NEW = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase());
|
||||||
PlayState.isStoryMode = false;
|
PlayState.isStoryMode = false;
|
||||||
PlayState.storyDifficulty = curDifficulty;
|
PlayState.storyDifficulty = curDifficulty;
|
||||||
// SongLoad.curDiff = Highscore.formatSong()
|
PlayState.storyDifficulty_NEW = switch (curDifficulty)
|
||||||
|
|
||||||
SongLoad.curDiff = switch (curDifficulty)
|
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
'easy';
|
'easy';
|
||||||
|
|
@ -536,6 +534,9 @@ class FreeplayState extends MusicBeatSubstate
|
||||||
'hard';
|
'hard';
|
||||||
default: 'normal';
|
default: 'normal';
|
||||||
};
|
};
|
||||||
|
// SongLoad.curDiff = Highscore.formatSong()
|
||||||
|
|
||||||
|
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
|
||||||
|
|
||||||
PlayState.storyWeek = songs[curSelected].week;
|
PlayState.storyWeek = songs[curSelected].week;
|
||||||
trace(' CUR WEEK ' + PlayState.storyWeek);
|
trace(' CUR WEEK ' + PlayState.storyWeek);
|
||||||
|
|
@ -564,6 +565,17 @@ class FreeplayState extends MusicBeatSubstate
|
||||||
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
|
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
|
||||||
|
|
||||||
PlayState.storyDifficulty = curDifficulty;
|
PlayState.storyDifficulty = curDifficulty;
|
||||||
|
PlayState.storyDifficulty_NEW = switch (curDifficulty)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
'easy';
|
||||||
|
case 1:
|
||||||
|
'normal';
|
||||||
|
case 2:
|
||||||
|
'hard';
|
||||||
|
default:
|
||||||
|
'normal';
|
||||||
|
};
|
||||||
|
|
||||||
grpDifficulties.group.forEach(function(spr)
|
grpDifficulties.group.forEach(function(spr)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,13 @@ import flixel.graphics.FlxGraphic;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.charting.ChartingState;
|
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.PicoFight;
|
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
import funkin.play.event.SongEvent.SongEventHandler;
|
||||||
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import funkin.ui.PreferencesMenu;
|
import funkin.ui.PreferencesMenu;
|
||||||
import funkin.ui.animDebugShit.DebugBoundingState;
|
|
||||||
import funkin.ui.stageBuildShit.StageBuilderState;
|
|
||||||
import funkin.util.macro.MacroUtil;
|
import funkin.util.macro.MacroUtil;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
|
|
@ -28,11 +26,6 @@ import io.colyseus.Room;
|
||||||
#if discord_rpc
|
#if discord_rpc
|
||||||
import Discord.DiscordClient;
|
import Discord.DiscordClient;
|
||||||
#end
|
#end
|
||||||
#if desktop
|
|
||||||
import sys.FileSystem;
|
|
||||||
import sys.io.File;
|
|
||||||
import sys.thread.Thread;
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the game state using custom defines.
|
* Initializes the game state using custom defines.
|
||||||
|
|
@ -123,6 +116,10 @@ class InitState extends FlxTransitionableState
|
||||||
// FlxTransitionableState.skipNextTransOut = true;
|
// FlxTransitionableState.skipNextTransOut = true;
|
||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
|
|
||||||
|
SongEventHandler.registerBaseEventCallbacks();
|
||||||
|
// TODO: Register custom event callbacks here
|
||||||
|
|
||||||
|
SongDataParser.loadSongCache();
|
||||||
StageDataParser.loadStageCache();
|
StageDataParser.loadStageCache();
|
||||||
CharacterDataParser.loadCharacterCache();
|
CharacterDataParser.loadCharacterCache();
|
||||||
ModuleHandler.buildModuleCallbacks();
|
ModuleHandler.buildModuleCallbacks();
|
||||||
|
|
@ -171,7 +168,7 @@ class InitState extends FlxTransitionableState
|
||||||
#elseif FREEPLAY
|
#elseif FREEPLAY
|
||||||
FlxG.switchState(new FreeplayState());
|
FlxG.switchState(new FreeplayState());
|
||||||
#elseif ANIMATE
|
#elseif ANIMATE
|
||||||
FlxG.switchState(new animate.AnimTestStage());
|
FlxG.switchState(new funkin.animate.dotstuff.DotStuffTestStage());
|
||||||
#elseif CHARTING
|
#elseif CHARTING
|
||||||
FlxG.switchState(new ChartingState());
|
FlxG.switchState(new ChartingState());
|
||||||
#elseif STAGEBUILD
|
#elseif STAGEBUILD
|
||||||
|
|
@ -195,15 +192,17 @@ class InitState extends FlxTransitionableState
|
||||||
var dif = getDif();
|
var dif = getDif();
|
||||||
|
|
||||||
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
||||||
|
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
|
||||||
PlayState.isStoryMode = isStoryMode;
|
PlayState.isStoryMode = isStoryMode;
|
||||||
PlayState.storyDifficulty = dif;
|
PlayState.storyDifficulty = dif;
|
||||||
SongLoad.curDiff = switch (dif)
|
PlayState.storyDifficulty_NEW = switch (dif)
|
||||||
{
|
{
|
||||||
case 0: 'easy';
|
case 0: 'easy';
|
||||||
case 1: 'normal';
|
case 1: 'normal';
|
||||||
case 2: 'hard';
|
case 2: 'hard';
|
||||||
default: 'normal';
|
default: 'normal';
|
||||||
};
|
};
|
||||||
|
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
|
||||||
PlayState.storyWeek = week;
|
PlayState.storyWeek = week;
|
||||||
LoadingState.loadAndSwitchState(new PlayState());
|
LoadingState.loadAndSwitchState(new PlayState());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import flixel.system.FlxSound;
|
||||||
import flixel.system.debug.stats.StatsGraph;
|
import flixel.system.debug.stats.StatsGraph;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.audiovis.PolygonSpectogram;
|
import funkin.audio.visualize.PolygonSpectogram;
|
||||||
import funkin.ui.CoolStatsGraph;
|
import funkin.ui.CoolStatsGraph;
|
||||||
import haxe.Timer;
|
import haxe.Timer;
|
||||||
import openfl.events.KeyboardEvent;
|
import openfl.events.KeyboardEvent;
|
||||||
|
|
@ -70,7 +70,7 @@ class LatencyState extends MusicBeatSubstate
|
||||||
|
|
||||||
// funnyStatsGraph.hi
|
// funnyStatsGraph.hi
|
||||||
|
|
||||||
Conductor.bpm = 60;
|
Conductor.forceBPM(60);
|
||||||
|
|
||||||
noteGrp = new FlxTypedGroup<Note>();
|
noteGrp = new FlxTypedGroup<Note>();
|
||||||
add(noteGrp);
|
add(noteGrp);
|
||||||
|
|
@ -139,17 +139,17 @@ class LatencyState extends MusicBeatSubstate
|
||||||
super.create();
|
super.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
override function stepHit()
|
override function stepHit():Bool
|
||||||
{
|
{
|
||||||
if (curStep % 4 == 2)
|
if (curStep % 4 == 2)
|
||||||
{
|
{
|
||||||
blocks.members[((curBeat % 8) + 1) % 8].alpha = 0.5;
|
blocks.members[((curBeat % 8) + 1) % 8].alpha = 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.stepHit();
|
return super.stepHit();
|
||||||
}
|
}
|
||||||
|
|
||||||
override function beatHit()
|
override function beatHit():Bool
|
||||||
{
|
{
|
||||||
if (curBeat % 8 == 0)
|
if (curBeat % 8 == 0)
|
||||||
blocks.forEach(blok ->
|
blocks.forEach(blok ->
|
||||||
|
|
@ -160,7 +160,7 @@ class LatencyState extends MusicBeatSubstate
|
||||||
blocks.members[curBeat % 8].alpha = 1;
|
blocks.members[curBeat % 8].alpha = 1;
|
||||||
// block.visible = !block.visible;
|
// block.visible = !block.visible;
|
||||||
|
|
||||||
super.beatHit();
|
return super.beatHit();
|
||||||
}
|
}
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
|
|
|
||||||
|
|
@ -188,8 +188,10 @@ class LoadingState extends MusicBeatState
|
||||||
{
|
{
|
||||||
Paths.setCurrentLevel('tutorial');
|
Paths.setCurrentLevel('tutorial');
|
||||||
}
|
}
|
||||||
else
|
else if (PlayState.storyWeek == 8) {
|
||||||
{
|
// TODO: Refactor this code.
|
||||||
|
Paths.setCurrentLevel("weekend1");
|
||||||
|
} else {
|
||||||
Paths.setCurrentLevel("week" + PlayState.storyWeek);
|
Paths.setCurrentLevel("week" + PlayState.storyWeek);
|
||||||
}
|
}
|
||||||
#if NO_PRELOAD_ALL
|
#if NO_PRELOAD_ALL
|
||||||
|
|
|
||||||
|
|
@ -112,17 +112,10 @@ class MainMenuState extends MusicBeatState
|
||||||
persistentUpdate = false;
|
persistentUpdate = false;
|
||||||
openSubState(new FreeplayState());
|
openSubState(new FreeplayState());
|
||||||
});
|
});
|
||||||
|
|
||||||
#if CAN_OPEN_LINKS
|
#if CAN_OPEN_LINKS
|
||||||
var hasPopupBlocker = #if web true #else false #end;
|
var hasPopupBlocker = #if web true #else false #end;
|
||||||
|
createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker);
|
||||||
if (VideoState.seenVideo)
|
|
||||||
{
|
|
||||||
createMenuItem('kickstarter', 'mainmenu/kickstarter', selectDonate, hasPopupBlocker);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker);
|
|
||||||
}
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
createMenuItem('options', 'mainmenu/options', function()
|
createMenuItem('options', 'mainmenu/options', function()
|
||||||
|
|
@ -195,7 +188,7 @@ class MainMenuState extends MusicBeatState
|
||||||
#if CAN_OPEN_LINKS
|
#if CAN_OPEN_LINKS
|
||||||
function selectDonate()
|
function selectDonate()
|
||||||
{
|
{
|
||||||
WindowUtil.openURL(Constants.URL_KICKSTARTER);
|
WindowUtil.openURL(Constants.URL_ITCH);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ import flixel.addons.ui.FlxUIState;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.util.FlxSort;
|
import flixel.util.FlxSort;
|
||||||
import funkin.Conductor.BPMChangeEvent;
|
|
||||||
import funkin.modding.PolymodHandler;
|
import funkin.modding.PolymodHandler;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.ui.debug.DebugMenuSubState;
|
||||||
import funkin.play.stage.StageData.StageDataParser;
|
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,10 +18,7 @@ import funkin.util.SortUtil;
|
||||||
*/
|
*/
|
||||||
class MusicBeatState extends FlxUIState
|
class MusicBeatState extends FlxUIState
|
||||||
{
|
{
|
||||||
private var curStep:Int = 0;
|
|
||||||
private var curBeat:Int = 0;
|
|
||||||
private var controls(get, never):Controls;
|
private var controls(get, never):Controls;
|
||||||
private var lastBeatHitTime:Float = 0;
|
|
||||||
|
|
||||||
inline function get_controls():Controls
|
inline function get_controls():Controls
|
||||||
return PlayerSettings.player1.controls;
|
return PlayerSettings.player1.controls;
|
||||||
|
|
@ -49,24 +44,37 @@ class MusicBeatState extends FlxUIState
|
||||||
super.create();
|
super.create();
|
||||||
|
|
||||||
createWatermarkText();
|
createWatermarkText();
|
||||||
|
|
||||||
|
Conductor.beatHit.add(this.beatHit);
|
||||||
|
Conductor.stepHit.add(this.stepHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function destroy():Void
|
||||||
|
{
|
||||||
|
super.destroy();
|
||||||
|
Conductor.beatHit.remove(this.beatHit);
|
||||||
|
Conductor.stepHit.remove(this.stepHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
|
if (FlxG.keys.justPressed.F4)
|
||||||
|
FlxG.switchState(new MainMenuState());
|
||||||
|
|
||||||
// This can now be used in EVERY STATE YAY!
|
// This can now be used in EVERY STATE YAY!
|
||||||
if (FlxG.keys.justPressed.F5)
|
if (FlxG.keys.justPressed.F5)
|
||||||
debug_refreshModules();
|
debug_refreshModules();
|
||||||
|
|
||||||
// everyStep();
|
// ` / ~
|
||||||
var oldStep:Int = curStep;
|
if (FlxG.keys.justPressed.GRAVEACCENT)
|
||||||
|
{
|
||||||
updateCurStep();
|
// TODO: Does this break anything?
|
||||||
updateBeat();
|
this.persistentUpdate = false;
|
||||||
|
this.persistentDraw = false;
|
||||||
if (oldStep != curStep && curStep >= 0)
|
FlxG.state.openSubState(new DebugMenuSubState());
|
||||||
stepHit();
|
}
|
||||||
|
|
||||||
FlxG.watch.addQuick("songPos", Conductor.songPosition);
|
FlxG.watch.addQuick("songPos", Conductor.songPosition);
|
||||||
|
|
||||||
|
|
@ -105,56 +113,26 @@ class MusicBeatState extends FlxUIState
|
||||||
FlxG.resetState();
|
FlxG.resetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateBeat():Void
|
|
||||||
{
|
|
||||||
curBeat = Math.floor(curStep / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateCurStep():Void
|
|
||||||
{
|
|
||||||
var lastChange:BPMChangeEvent = {
|
|
||||||
stepTime: 0,
|
|
||||||
songTime: 0,
|
|
||||||
bpm: 0
|
|
||||||
}
|
|
||||||
for (i in 0...Conductor.bpmChangeMap.length)
|
|
||||||
{
|
|
||||||
if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
|
|
||||||
lastChange = Conductor.bpmChangeMap[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
curStep = lastChange.stepTime + Math.floor((Conductor.songPosition - lastChange.songTime) / Conductor.stepCrochet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stepHit():Bool
|
public function stepHit():Bool
|
||||||
{
|
{
|
||||||
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep);
|
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||||
|
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
if (event.eventCanceled)
|
if (event.eventCanceled)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (curStep % 4 == 0)
|
|
||||||
beatHit();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function beatHit():Bool
|
public function beatHit():Bool
|
||||||
{
|
{
|
||||||
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep);
|
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
|
||||||
|
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
if (event.eventCanceled)
|
if (event.eventCanceled)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
lastBeatHitTime = Conductor.songPosition;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -175,9 +153,7 @@ class MusicBeatState extends FlxUIState
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
if (event.eventCanceled)
|
if (event.eventCanceled)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return super.switchTo(nextState);
|
return super.switchTo(nextState);
|
||||||
}
|
}
|
||||||
|
|
@ -189,9 +165,7 @@ class MusicBeatState extends FlxUIState
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
if (event.eventCanceled)
|
if (event.eventCanceled)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
super.openSubState(targetSubstate);
|
super.openSubState(targetSubstate);
|
||||||
}
|
}
|
||||||
|
|
@ -208,9 +182,7 @@ class MusicBeatState extends FlxUIState
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
if (event.eventCanceled)
|
if (event.eventCanceled)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
super.closeSubState();
|
super.closeSubState();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
import funkin.Conductor.BPMChangeEvent;
|
import funkin.Conductor.BPMChangeEvent;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
|
|
@ -10,9 +11,9 @@ import funkin.modding.module.ModuleHandler;
|
||||||
*/
|
*/
|
||||||
class MusicBeatSubstate extends FlxSubState
|
class MusicBeatSubstate extends FlxSubState
|
||||||
{
|
{
|
||||||
public function new()
|
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||||
{
|
{
|
||||||
super();
|
super(bgColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private var curStep:Int = 0;
|
private var curStep:Int = 0;
|
||||||
|
|
@ -52,10 +53,19 @@ class MusicBeatSubstate extends FlxSubState
|
||||||
curStep = lastChange.stepTime + Math.floor(((Conductor.songPosition - Conductor.audioOffset) - lastChange.songTime) / Conductor.stepCrochet);
|
curStep = lastChange.stepTime + Math.floor(((Conductor.songPosition - Conductor.audioOffset) - lastChange.songTime) / Conductor.stepCrochet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stepHit():Void
|
public function stepHit():Bool
|
||||||
{
|
{
|
||||||
|
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep);
|
||||||
|
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
if (event.eventCanceled)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (curStep % 4 == 0)
|
if (curStep % 4 == 0)
|
||||||
beatHit();
|
beatHit();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchEvent(event:ScriptEvent)
|
function dispatchEvent(event:ScriptEvent)
|
||||||
|
|
@ -63,8 +73,24 @@ class MusicBeatSubstate extends FlxSubState
|
||||||
ModuleHandler.callEvent(event);
|
ModuleHandler.callEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function beatHit():Void
|
/**
|
||||||
|
* Close this substate and replace it with a different one.
|
||||||
|
*/
|
||||||
|
public function switchSubState(substate:FlxSubState):Void
|
||||||
{
|
{
|
||||||
// do literally nothing dumbass
|
this.close();
|
||||||
|
this._parentState.openSubState(substate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function beatHit():Bool
|
||||||
|
{
|
||||||
|
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep);
|
||||||
|
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
if (event.eventCanceled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package funkin;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
|
import funkin.noteStuff.NoteBasic.NoteData;
|
||||||
|
import funkin.noteStuff.NoteBasic.NoteType;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.play.Strumline.StrumlineStyle;
|
import funkin.play.Strumline.StrumlineStyle;
|
||||||
import funkin.shaderslmfao.ColorSwap;
|
import funkin.shaderslmfao.ColorSwap;
|
||||||
|
|
@ -151,10 +153,10 @@ class Note extends FlxSprite
|
||||||
default:
|
default:
|
||||||
frames = Paths.getSparrowAtlas('NOTE_assets');
|
frames = Paths.getSparrowAtlas('NOTE_assets');
|
||||||
|
|
||||||
|
animation.addByPrefix('purpleScroll', 'purple instance');
|
||||||
|
animation.addByPrefix('blueScroll', 'blue instance');
|
||||||
animation.addByPrefix('greenScroll', 'green instance');
|
animation.addByPrefix('greenScroll', 'green instance');
|
||||||
animation.addByPrefix('redScroll', 'red instance');
|
animation.addByPrefix('redScroll', 'red instance');
|
||||||
animation.addByPrefix('blueScroll', 'blue instance');
|
|
||||||
animation.addByPrefix('purpleScroll', 'purple instance');
|
|
||||||
|
|
||||||
animation.addByPrefix('purpleholdend', 'pruple end hold');
|
animation.addByPrefix('purpleholdend', 'pruple end hold');
|
||||||
animation.addByPrefix('greenholdend', 'green hold end');
|
animation.addByPrefix('greenholdend', 'green hold end');
|
||||||
|
|
@ -283,198 +285,8 @@ class Note extends FlxSprite
|
||||||
|
|
||||||
static public function fromData(data:NoteData, prevNote:Note, isSustainNote = false)
|
static public function fromData(data:NoteData, prevNote:Note, isSustainNote = false)
|
||||||
{
|
{
|
||||||
return new Note(data.strumTime, data.noteData, prevNote, isSustainNote);
|
var result = new Note(data.strumTime, data.noteData, prevNote, isSustainNote);
|
||||||
|
result.data = data;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef RawNoteData =
|
|
||||||
{
|
|
||||||
var strumTime:Float;
|
|
||||||
var noteData:NoteType;
|
|
||||||
var sustainLength:Float;
|
|
||||||
var altNote:String;
|
|
||||||
var noteKind:NoteKind;
|
|
||||||
}
|
|
||||||
|
|
||||||
@:forward
|
|
||||||
abstract NoteData(RawNoteData)
|
|
||||||
{
|
|
||||||
public function new(strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, altNote = "", noteKind = NORMAL)
|
|
||||||
{
|
|
||||||
this = {
|
|
||||||
strumTime: strumTime,
|
|
||||||
noteData: noteData,
|
|
||||||
sustainLength: sustainLength,
|
|
||||||
altNote: altNote,
|
|
||||||
noteKind: noteKind
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var note(get, never):NoteType;
|
|
||||||
|
|
||||||
inline function get_note()
|
|
||||||
return this.noteData.value;
|
|
||||||
|
|
||||||
public var int(get, never):Int;
|
|
||||||
|
|
||||||
inline function get_int()
|
|
||||||
return this.noteData.int;
|
|
||||||
|
|
||||||
public var dir(get, never):NoteDir;
|
|
||||||
|
|
||||||
inline function get_dir()
|
|
||||||
return this.noteData.value;
|
|
||||||
|
|
||||||
public var dirName(get, never):String;
|
|
||||||
|
|
||||||
inline function get_dirName()
|
|
||||||
return dir.name;
|
|
||||||
|
|
||||||
public var dirNameUpper(get, never):String;
|
|
||||||
|
|
||||||
inline function get_dirNameUpper()
|
|
||||||
return dir.nameUpper;
|
|
||||||
|
|
||||||
public var color(get, never):NoteColor;
|
|
||||||
|
|
||||||
inline function get_color()
|
|
||||||
return this.noteData.value;
|
|
||||||
|
|
||||||
public var colorName(get, never):String;
|
|
||||||
|
|
||||||
inline function get_colorName()
|
|
||||||
return color.name;
|
|
||||||
|
|
||||||
public var colorNameUpper(get, never):String;
|
|
||||||
|
|
||||||
inline function get_colorNameUpper()
|
|
||||||
return color.nameUpper;
|
|
||||||
|
|
||||||
public var highStakes(get, never):Bool;
|
|
||||||
|
|
||||||
inline function get_highStakes()
|
|
||||||
return this.noteData.highStakes;
|
|
||||||
|
|
||||||
public var lowStakes(get, never):Bool;
|
|
||||||
|
|
||||||
inline function get_lowStakes()
|
|
||||||
return this.noteData.lowStakes;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum abstract NoteType(Int) from Int to Int
|
|
||||||
{
|
|
||||||
// public var raw(get, never):Int;
|
|
||||||
// inline function get_raw() return this;
|
|
||||||
public var int(get, never):Int;
|
|
||||||
|
|
||||||
inline function get_int()
|
|
||||||
return this < 0 ? -this : this % 4;
|
|
||||||
|
|
||||||
public var value(get, never):NoteType;
|
|
||||||
|
|
||||||
inline function get_value()
|
|
||||||
return int;
|
|
||||||
|
|
||||||
public var highStakes(get, never):Bool;
|
|
||||||
|
|
||||||
inline function get_highStakes()
|
|
||||||
return this > 3;
|
|
||||||
|
|
||||||
public var lowStakes(get, never):Bool;
|
|
||||||
|
|
||||||
inline function get_lowStakes()
|
|
||||||
return this < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@:forward
|
|
||||||
enum abstract NoteDir(NoteType) from Int to Int from NoteType
|
|
||||||
{
|
|
||||||
var LEFT = 0;
|
|
||||||
var DOWN = 1;
|
|
||||||
var UP = 2;
|
|
||||||
var RIGHT = 3;
|
|
||||||
var value(get, never):NoteDir;
|
|
||||||
|
|
||||||
inline function get_value()
|
|
||||||
return this.value;
|
|
||||||
|
|
||||||
public var name(get, never):String;
|
|
||||||
|
|
||||||
function get_name()
|
|
||||||
{
|
|
||||||
return switch (value)
|
|
||||||
{
|
|
||||||
case LEFT: "left";
|
|
||||||
case DOWN: "down";
|
|
||||||
case UP: "up";
|
|
||||||
case RIGHT: "right";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var nameUpper(get, never):String;
|
|
||||||
|
|
||||||
function get_nameUpper()
|
|
||||||
{
|
|
||||||
return switch (value)
|
|
||||||
{
|
|
||||||
case LEFT: "LEFT";
|
|
||||||
case DOWN: "DOWN";
|
|
||||||
case UP: "UP";
|
|
||||||
case RIGHT: "RIGHT";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@:forward
|
|
||||||
enum abstract NoteColor(NoteType) from Int to Int from NoteType
|
|
||||||
{
|
|
||||||
var PURPLE = 0;
|
|
||||||
var BLUE = 1;
|
|
||||||
var GREEN = 2;
|
|
||||||
var RED = 3;
|
|
||||||
var value(get, never):NoteColor;
|
|
||||||
|
|
||||||
inline function get_value()
|
|
||||||
return this.value;
|
|
||||||
|
|
||||||
public var name(get, never):String;
|
|
||||||
|
|
||||||
function get_name()
|
|
||||||
{
|
|
||||||
return switch (value)
|
|
||||||
{
|
|
||||||
case PURPLE: "purple";
|
|
||||||
case BLUE: "blue";
|
|
||||||
case GREEN: "green";
|
|
||||||
case RED: "red";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var nameUpper(get, never):String;
|
|
||||||
|
|
||||||
function get_nameUpper()
|
|
||||||
{
|
|
||||||
return switch (value)
|
|
||||||
{
|
|
||||||
case PURPLE: "PURPLE";
|
|
||||||
case BLUE: "BLUE";
|
|
||||||
case GREEN: "GREEN";
|
|
||||||
case RED: "RED";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum abstract NoteKind(String) from String to String
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The default note type.
|
|
||||||
*/
|
|
||||||
var NORMAL = "normal";
|
|
||||||
|
|
||||||
// Testing shiz
|
|
||||||
var PYRO_LIGHT = "pyro_light";
|
|
||||||
var PYRO_KICK = "pyro_kick";
|
|
||||||
var PYRO_TOSS = "pyro_toss";
|
|
||||||
var PYRO_COCK = "pyro_cock"; // lol
|
|
||||||
var PYRO_SHOOT = "pyro_shoot";
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,16 @@ class Paths
|
||||||
return getPath('data/$key.txt', TEXT, library);
|
return getPath('data/$key.txt', TEXT, library);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline static public function frag(key:String, ?library:String)
|
||||||
|
{
|
||||||
|
return getPath('shaders/$key.frag', TEXT, library);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static public function vert(key:String, ?library:String)
|
||||||
|
{
|
||||||
|
return getPath('shaders/$key.vert', TEXT, library);
|
||||||
|
}
|
||||||
|
|
||||||
inline static public function xml(key:String, ?library:String)
|
inline static public function xml(key:String, ?library:String)
|
||||||
{
|
{
|
||||||
return getPath('data/$key.xml', TEXT, library);
|
return getPath('data/$key.xml', TEXT, library);
|
||||||
|
|
@ -107,6 +117,11 @@ class Paths
|
||||||
return 'assets/fonts/$key';
|
return 'assets/fonts/$key';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline static public function ui(key:String, ?library:String)
|
||||||
|
{
|
||||||
|
return xml('ui/$key', library);
|
||||||
|
}
|
||||||
|
|
||||||
static public function getSparrowAtlas(key:String, ?library:String)
|
static public function getSparrowAtlas(key:String, ?library:String)
|
||||||
{
|
{
|
||||||
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
|
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.FlxSubState;
|
|
||||||
import flixel.addons.transition.FlxTransitionableState;
|
import flixel.addons.transition.FlxTransitionableState;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import flixel.input.keyboard.FlxKey;
|
|
||||||
import flixel.system.FlxSound;
|
import flixel.system.FlxSound;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.Controls.Control;
|
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
|
|
||||||
class PauseSubState extends MusicBeatSubstate
|
class PauseSubState extends MusicBeatSubstate
|
||||||
{
|
{
|
||||||
|
|
@ -61,7 +59,14 @@ class PauseSubState extends MusicBeatSubstate
|
||||||
add(metaDataGrp);
|
add(metaDataGrp);
|
||||||
|
|
||||||
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32);
|
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32);
|
||||||
levelInfo.text += PlayState.currentSong.song;
|
if (PlayState.instance.currentChart != null)
|
||||||
|
{
|
||||||
|
levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
levelInfo.text += PlayState.currentSong.song;
|
||||||
|
}
|
||||||
levelInfo.scrollFactor.set();
|
levelInfo.scrollFactor.set();
|
||||||
levelInfo.setFormat(Paths.font("vcr.ttf"), 32);
|
levelInfo.setFormat(Paths.font("vcr.ttf"), 32);
|
||||||
levelInfo.updateHitbox();
|
levelInfo.updateHitbox();
|
||||||
|
|
@ -180,9 +185,11 @@ class PauseSubState extends MusicBeatSubstate
|
||||||
close();
|
close();
|
||||||
case "EASY" | 'NORMAL' | "HARD":
|
case "EASY" | 'NORMAL' | "HARD":
|
||||||
PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase());
|
PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase());
|
||||||
|
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.currentSong.song.toLowerCase());
|
||||||
SongLoad.curDiff = daSelected.toLowerCase();
|
SongLoad.curDiff = daSelected.toLowerCase();
|
||||||
|
|
||||||
PlayState.storyDifficulty = curSelected;
|
PlayState.storyDifficulty = curSelected;
|
||||||
|
PlayState.storyDifficulty_NEW = daSelected.toLowerCase();
|
||||||
|
|
||||||
PlayState.needsReset = true;
|
PlayState.needsReset = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import funkin.Note.NoteData;
|
import funkin.noteStuff.NoteBasic.NoteData;
|
||||||
|
|
||||||
typedef SwagSection =
|
typedef SwagSection =
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import funkin.Note.NoteData;
|
|
||||||
import funkin.Section.SwagSection;
|
import funkin.Section.SwagSection;
|
||||||
|
import funkin.noteStuff.NoteBasic.NoteData;
|
||||||
|
import funkin.play.PlayState;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
import haxe.format.JsonParser;
|
|
||||||
import lime.utils.Assets;
|
import lime.utils.Assets;
|
||||||
|
|
||||||
using StringTools;
|
using StringTools;
|
||||||
|
|
@ -48,30 +48,27 @@ class SongLoad
|
||||||
|
|
||||||
public static function loadFromJson(jsonInput:String, ?folder:String):SwagSong
|
public static function loadFromJson(jsonInput:String, ?folder:String):SwagSong
|
||||||
{
|
{
|
||||||
var rawJson = Assets.getText(Paths.json('songs/${folder.toLowerCase()}/${jsonInput.toLowerCase()}')).trim();
|
var rawJson:String = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rawJson = Assets.getText(Paths.json('songs/${folder.toLowerCase()}/${jsonInput.toLowerCase()}')).trim();
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace('Failed to load song data: ${e}');
|
||||||
|
rawJson = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawJson == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
while (!rawJson.endsWith("}"))
|
while (!rawJson.endsWith("}"))
|
||||||
{
|
{
|
||||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||||
// LOL GOING THROUGH THE BULLSHIT TO CLEAN IDK WHATS STRANGE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIX THE CASTING ON WINDOWS/NATIVE
|
|
||||||
// Windows???
|
|
||||||
// trace(songData);
|
|
||||||
|
|
||||||
// trace('LOADED FROM JSON: ' + songData.notes);
|
|
||||||
/*
|
|
||||||
for (i in 0...songData.notes.length)
|
|
||||||
{
|
|
||||||
trace('LOADED FROM JSON: ' + songData.notes[i].sectionNotes);
|
|
||||||
// songData.notes[i].sectionNotes = songData.notes[i].sectionNotes
|
|
||||||
}
|
|
||||||
|
|
||||||
daNotes = songData.notes;
|
|
||||||
daSong = songData.song;
|
|
||||||
daBpm = songData.bpm; */
|
|
||||||
|
|
||||||
return parseJSONshit(rawJson);
|
return parseJSONshit(rawJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,12 +102,19 @@ class SongLoad
|
||||||
|
|
||||||
public static function checkAndCreateNotemap(diff:String):Void
|
public static function checkAndCreateNotemap(diff:String):Void
|
||||||
{
|
{
|
||||||
|
if (songData == null || songData.noteMap == null)
|
||||||
|
return;
|
||||||
if (songData.noteMap[diff] == null)
|
if (songData.noteMap[diff] == null)
|
||||||
songData.noteMap[diff] = [];
|
songData.noteMap[diff] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getSpeed(?diff:String):Float
|
public static function getSpeed(?diff:String):Float
|
||||||
{
|
{
|
||||||
|
if (PlayState.instance != null && PlayState.instance.currentChart != null)
|
||||||
|
{
|
||||||
|
return getSpeed_NEW(diff);
|
||||||
|
}
|
||||||
|
|
||||||
if (diff == null)
|
if (diff == null)
|
||||||
diff = SongLoad.curDiff;
|
diff = SongLoad.curDiff;
|
||||||
|
|
||||||
|
|
@ -136,6 +140,14 @@ class SongLoad
|
||||||
return speedShit;
|
return speedShit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getSpeed_NEW(?diff:String):Float
|
||||||
|
{
|
||||||
|
if (PlayState.instance == null || PlayState.instance.currentChart == null || PlayState.instance.currentChart.scrollSpeed == 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
return PlayState.instance.currentChart.scrollSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getDefaultSwagSong():SwagSong
|
public static function getDefaultSwagSong():SwagSong
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
@ -191,11 +203,7 @@ class SongLoad
|
||||||
noteStuff[sectionIndex].sectionNotes[noteIndex].sustainLength = arrayDipshit[2];
|
noteStuff[sectionIndex].sectionNotes[noteIndex].sustainLength = arrayDipshit[2];
|
||||||
if (arrayDipshit.length > 3)
|
if (arrayDipshit.length > 3)
|
||||||
{
|
{
|
||||||
noteStuff[sectionIndex].sectionNotes[noteIndex].altNote = arrayDipshit[3];
|
noteStuff[sectionIndex].sectionNotes[noteIndex].noteKind = arrayDipshit[3];
|
||||||
}
|
|
||||||
if (arrayDipshit.length > 4)
|
|
||||||
{
|
|
||||||
noteStuff[sectionIndex].sectionNotes[noteIndex].noteKind = arrayDipshit[4];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (noteDataArray != null)
|
else if (noteDataArray != null)
|
||||||
|
|
@ -227,7 +235,7 @@ class SongLoad
|
||||||
noteTypeDefShit.strumTime,
|
noteTypeDefShit.strumTime,
|
||||||
noteTypeDefShit.noteData,
|
noteTypeDefShit.noteData,
|
||||||
noteTypeDefShit.sustainLength,
|
noteTypeDefShit.sustainLength,
|
||||||
noteTypeDefShit.altNote
|
noteTypeDefShit.noteKind
|
||||||
];
|
];
|
||||||
|
|
||||||
noteStuff[sectionIndex].sectionNotes[noteIndex] = cast dipshitArray;
|
noteStuff[sectionIndex].sectionNotes[noteIndex] = cast dipshitArray;
|
||||||
|
|
@ -252,7 +260,18 @@ class SongLoad
|
||||||
|
|
||||||
public static function parseJSONshit(rawJson:String):SwagSong
|
public static function parseJSONshit(rawJson:String):SwagSong
|
||||||
{
|
{
|
||||||
var songParsed:Dynamic = Json.parse(rawJson);
|
var songParsed:Dynamic;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
songParsed = Json.parse(rawJson);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
FlxG.log.warn("Error parsing JSON: " + e.message);
|
||||||
|
trace("Error parsing JSON: " + e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var swagShit:SwagSong = cast songParsed.song;
|
var swagShit:SwagSong = cast songParsed.song;
|
||||||
swagShit.difficulties = []; // reset it to default before load
|
swagShit.difficulties = []; // reset it to default before load
|
||||||
swagShit.noteMap = new Map();
|
swagShit.noteMap = new Map();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import flixel.util.FlxColor;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.MenuItem.WeekType;
|
import funkin.MenuItem.WeekType;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
import lime.net.curl.CURLCode;
|
import lime.net.curl.CURLCode;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
|
|
||||||
|
|
@ -372,11 +373,12 @@ class StoryMenuState extends MusicBeatState
|
||||||
selectedWeek = true;
|
selectedWeek = true;
|
||||||
|
|
||||||
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
|
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
|
||||||
|
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase());
|
||||||
PlayState.storyWeek = curWeek;
|
PlayState.storyWeek = curWeek;
|
||||||
PlayState.campaignScore = 0;
|
PlayState.campaignScore = 0;
|
||||||
|
|
||||||
PlayState.storyDifficulty = curDifficulty;
|
PlayState.storyDifficulty = curDifficulty;
|
||||||
SongLoad.curDiff = switch (curDifficulty)
|
PlayState.storyDifficulty_NEW = switch (curDifficulty)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
'easy';
|
'easy';
|
||||||
|
|
@ -387,6 +389,7 @@ class StoryMenuState extends MusicBeatState
|
||||||
default:
|
default:
|
||||||
'normal';
|
'normal';
|
||||||
};
|
};
|
||||||
|
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
|
||||||
|
|
||||||
new FlxTimer().start(1, function(tmr:FlxTimer)
|
new FlxTimer().start(1, function(tmr:FlxTimer)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import flixel.FlxObject;
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.FlxState;
|
import flixel.FlxState;
|
||||||
import flixel.group.FlxGroup;
|
import flixel.group.FlxGroup;
|
||||||
|
|
@ -8,9 +7,9 @@ import flixel.input.gamepad.FlxGamepad;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
|
import flixel.util.FlxDirectionFlags;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.audiovis.SpectogramSprite;
|
import funkin.audiovis.SpectogramSprite;
|
||||||
import funkin.shaderslmfao.BuildingShaders;
|
|
||||||
import funkin.shaderslmfao.ColorSwap;
|
import funkin.shaderslmfao.ColorSwap;
|
||||||
import funkin.shaderslmfao.TitleOutline;
|
import funkin.shaderslmfao.TitleOutline;
|
||||||
import funkin.ui.AtlasText;
|
import funkin.ui.AtlasText;
|
||||||
|
|
@ -39,7 +38,6 @@ class TitleState extends MusicBeatState
|
||||||
var curWacky:Array<String> = [];
|
var curWacky:Array<String> = [];
|
||||||
var lastBeat:Int = 0;
|
var lastBeat:Int = 0;
|
||||||
var swagShader:ColorSwap;
|
var swagShader:ColorSwap;
|
||||||
var alphaShader:BuildingShaders;
|
|
||||||
|
|
||||||
var video:Video;
|
var video:Video;
|
||||||
var netStream:NetStream;
|
var netStream:NetStream;
|
||||||
|
|
@ -48,7 +46,6 @@ class TitleState extends MusicBeatState
|
||||||
override public function create():Void
|
override public function create():Void
|
||||||
{
|
{
|
||||||
swagShader = new ColorSwap();
|
swagShader = new ColorSwap();
|
||||||
alphaShader = new BuildingShaders();
|
|
||||||
|
|
||||||
curWacky = FlxG.random.getObject(getIntroTextShit());
|
curWacky = FlxG.random.getObject(getIntroTextShit());
|
||||||
FlxG.sound.cache(Paths.music('freakyMenu'));
|
FlxG.sound.cache(Paths.music('freakyMenu'));
|
||||||
|
|
@ -143,7 +140,7 @@ class TitleState extends MusicBeatState
|
||||||
{
|
{
|
||||||
FlxG.sound.playMusic(Paths.music('freakyMenu'), 0);
|
FlxG.sound.playMusic(Paths.music('freakyMenu'), 0);
|
||||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||||
Conductor.bpm = Constants.FREAKY_MENU_BPM;
|
Conductor.forceBPM(Constants.FREAKY_MENU_BPM);
|
||||||
}
|
}
|
||||||
|
|
||||||
persistentUpdate = true;
|
persistentUpdate = true;
|
||||||
|
|
@ -160,26 +157,15 @@ class TitleState extends MusicBeatState
|
||||||
logoBl.updateHitbox();
|
logoBl.updateHitbox();
|
||||||
|
|
||||||
outlineShaderShit = new TitleOutline();
|
outlineShaderShit = new TitleOutline();
|
||||||
// logoBl.shader = swagShader.shader;
|
|
||||||
// logoBl.shader = outlineShaderShit;
|
|
||||||
|
|
||||||
// trace();
|
|
||||||
// logoBl.screenCenter();
|
|
||||||
// logoBl.color = FlxColor.BLACK;
|
|
||||||
|
|
||||||
gfDance = new FlxSprite(FlxG.width * 0.4, FlxG.height * 0.07);
|
gfDance = new FlxSprite(FlxG.width * 0.4, FlxG.height * 0.07);
|
||||||
gfDance.frames = Paths.getSparrowAtlas('gfDanceTitle');
|
gfDance.frames = Paths.getSparrowAtlas('gfDanceTitle');
|
||||||
gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||||
gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||||
gfDance.antialiasing = true;
|
gfDance.antialiasing = true;
|
||||||
|
|
||||||
add(gfDance);
|
add(gfDance);
|
||||||
|
|
||||||
// alphaShader.shader.funnyShit.input = gfDance.pixels; // old shit
|
|
||||||
|
|
||||||
logoBl.shader = alphaShader.shader;
|
|
||||||
|
|
||||||
// trace(alphaShader.shader.glFragmentSource)
|
|
||||||
|
|
||||||
// gfDance.shader = swagShader.shader;
|
// gfDance.shader = swagShader.shader;
|
||||||
|
|
||||||
// gfDance.shader = new TitleOutline();
|
// gfDance.shader = new TitleOutline();
|
||||||
|
|
@ -273,6 +259,8 @@ class TitleState extends MusicBeatState
|
||||||
FlxG.sound.music.pitch -= 0.5 * elapsed;
|
FlxG.sound.music.pitch -= 0.5 * elapsed;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
Conductor.update();
|
||||||
|
|
||||||
/* if (FlxG.onMobile)
|
/* if (FlxG.onMobile)
|
||||||
{
|
{
|
||||||
if (gfDance != null)
|
if (gfDance != null)
|
||||||
|
|
@ -455,13 +443,13 @@ class TitleState extends MusicBeatState
|
||||||
if (FlxG.keys.justPressed.ANY)
|
if (FlxG.keys.justPressed.ANY)
|
||||||
{
|
{
|
||||||
if (controls.NOTE_DOWN_P || controls.UI_DOWN_P)
|
if (controls.NOTE_DOWN_P || controls.UI_DOWN_P)
|
||||||
codePress(FlxObject.DOWN);
|
codePress(FlxDirectionFlags.DOWN);
|
||||||
if (controls.NOTE_UP_P || controls.UI_UP_P)
|
if (controls.NOTE_UP_P || controls.UI_UP_P)
|
||||||
codePress(FlxObject.UP);
|
codePress(FlxDirectionFlags.UP);
|
||||||
if (controls.NOTE_LEFT_P || controls.UI_LEFT_P)
|
if (controls.NOTE_LEFT_P || controls.UI_LEFT_P)
|
||||||
codePress(FlxObject.LEFT);
|
codePress(FlxDirectionFlags.LEFT);
|
||||||
if (controls.NOTE_RIGHT_P || controls.UI_RIGHT_P)
|
if (controls.NOTE_RIGHT_P || controls.UI_RIGHT_P)
|
||||||
codePress(FlxObject.RIGHT);
|
codePress(FlxDirectionFlags.RIGHT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,7 +476,7 @@ class TitleState extends MusicBeatState
|
||||||
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
|
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
|
||||||
add(spec);
|
add(spec);
|
||||||
|
|
||||||
Conductor.bpm = 190;
|
Conductor.forceBPM(190);
|
||||||
FlxG.camera.flash(FlxColor.WHITE, 1);
|
FlxG.camera.flash(FlxColor.WHITE, 1);
|
||||||
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
|
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
|
||||||
}
|
}
|
||||||
|
|
@ -544,12 +532,13 @@ class TitleState extends MusicBeatState
|
||||||
|
|
||||||
if (!skippedIntro)
|
if (!skippedIntro)
|
||||||
{
|
{
|
||||||
FlxG.log.add(curBeat);
|
// FlxG.log.add(Conductor.currentBeat);
|
||||||
// if the user is draggin the window some beats will
|
// if the user is draggin the window some beats will
|
||||||
// be missed so this is just to compensate
|
// be missed so this is just to compensate
|
||||||
if (curBeat > lastBeat)
|
if (Conductor.currentBeat > lastBeat)
|
||||||
{
|
{
|
||||||
for (i in lastBeat...curBeat)
|
// TODO: Why does it perform ALL the previous steps each beat?
|
||||||
|
for (i in lastBeat...Conductor.currentBeat)
|
||||||
{
|
{
|
||||||
switch (i + 1)
|
switch (i + 1)
|
||||||
{
|
{
|
||||||
|
|
@ -563,10 +552,12 @@ class TitleState extends MusicBeatState
|
||||||
createCoolText(['In association', 'with']);
|
createCoolText(['In association', 'with']);
|
||||||
case 7:
|
case 7:
|
||||||
addMoreText('newgrounds');
|
addMoreText('newgrounds');
|
||||||
ngSpr.visible = true;
|
if (ngSpr != null)
|
||||||
|
ngSpr.visible = true;
|
||||||
case 8:
|
case 8:
|
||||||
deleteCoolText();
|
deleteCoolText();
|
||||||
ngSpr.visible = false;
|
if (ngSpr != null)
|
||||||
|
ngSpr.visible = false;
|
||||||
case 9:
|
case 9:
|
||||||
createCoolText([curWacky[0]]);
|
createCoolText([curWacky[0]]);
|
||||||
case 11:
|
case 11:
|
||||||
|
|
@ -584,21 +575,25 @@ class TitleState extends MusicBeatState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastBeat = curBeat;
|
lastBeat = Conductor.currentBeat;
|
||||||
}
|
}
|
||||||
if (skippedIntro)
|
if (skippedIntro)
|
||||||
{
|
{
|
||||||
if (cheatActive && curBeat % 2 == 0)
|
if (cheatActive && Conductor.currentBeat % 2 == 0)
|
||||||
swagShader.update(0.125);
|
swagShader.update(0.125);
|
||||||
|
|
||||||
logoBl.animation.play('bump', true);
|
if (logoBl != null && logoBl.animation != null)
|
||||||
|
logoBl.animation.play('bump', true);
|
||||||
|
|
||||||
danceLeft = !danceLeft;
|
danceLeft = !danceLeft;
|
||||||
|
|
||||||
if (danceLeft)
|
if (gfDance != null && gfDance.animation != null)
|
||||||
gfDance.animation.play('danceRight');
|
{
|
||||||
else
|
if (danceLeft)
|
||||||
gfDance.animation.play('danceLeft');
|
gfDance.animation.play('danceRight');
|
||||||
|
else
|
||||||
|
gfDance.animation.play('danceLeft');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,17 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
|
||||||
public var pitch(default, set):Float = 1;
|
public var pitch(default, set):Float = 1;
|
||||||
|
|
||||||
// make it a group that you add to?
|
// make it a group that you add to?
|
||||||
public function new(song:String, ?files:Array<String>, ?needsVoices:Bool = true)
|
public function new(song:String, ?files:Array<String> = null)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (!needsVoices)
|
if (files == null)
|
||||||
{
|
{
|
||||||
// simply adds an empty sound? fills it in moreso for easier backwards compatibility
|
// Add an empty voice.
|
||||||
add(new FlxSound());
|
add(new FlxSound());
|
||||||
// FlxG.sound.list.add(snd);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files == null)
|
|
||||||
files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally
|
|
||||||
|
|
||||||
for (sndFile in files)
|
for (sndFile in files)
|
||||||
{
|
{
|
||||||
var snd:FlxSound = new FlxSound().loadEmbedded(Paths.voices(song, '$sndFile'));
|
var snd:FlxSound = new FlxSound().loadEmbedded(Paths.voices(song, '$sndFile'));
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package funkin.animate;
|
package funkin.animate;
|
||||||
|
|
||||||
import funkin.animate.ParseAnimate.Frame;
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.input.mouse.FlxMouseEventManager;
|
import flixel.input.mouse.FlxMouseEvent;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
|
import funkin.animate.ParseAnimate.Frame;
|
||||||
|
|
||||||
class TimelineFrame extends FlxSprite
|
class TimelineFrame extends FlxSprite
|
||||||
{
|
{
|
||||||
|
|
@ -17,7 +17,7 @@ class TimelineFrame extends FlxSprite
|
||||||
|
|
||||||
makeGraphic((10 * length) + (2 * (length - 1)), 10, FlxColor.RED);
|
makeGraphic((10 * length) + (2 * (length - 1)), 10, FlxColor.RED);
|
||||||
|
|
||||||
FlxMouseEventManager.add(this, null, null, function(spr:TimelineFrame)
|
FlxMouseEvent.add(this, null, null, function(spr:TimelineFrame)
|
||||||
{
|
{
|
||||||
alpha = 0.5;
|
alpha = 0.5;
|
||||||
}, function(spr:TimelineFrame)
|
}, function(spr:TimelineFrame)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package funkin.audiovis;
|
package funkin.audio.visualize;
|
||||||
|
|
||||||
import funkin.audiovis.VisShit.CurAudioInfo;
|
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.system.FlxSound;
|
import flixel.system.FlxSound;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
|
import funkin.audiovis.VisShit;
|
||||||
|
import funkin.graphics.rendering.MeshRender;
|
||||||
import lime.utils.Int16Array;
|
import lime.utils.Int16Array;
|
||||||
import funkin.rendering.MeshRender;
|
|
||||||
|
|
||||||
class PolygonSpectogram extends MeshRender
|
class PolygonSpectogram extends MeshRender
|
||||||
{
|
{
|
||||||
|
|
@ -30,7 +30,7 @@ class PolygonSpectogram extends MeshRender
|
||||||
{
|
{
|
||||||
super(0, 0, col);
|
super(0, 0, col);
|
||||||
|
|
||||||
vis = new VisShit(daSound);
|
setSound(daSound);
|
||||||
|
|
||||||
if (height != null)
|
if (height != null)
|
||||||
this.daHeight = height;
|
this.daHeight = height;
|
||||||
|
|
@ -40,6 +40,11 @@ class PolygonSpectogram extends MeshRender
|
||||||
// col not in yet
|
// col not in yet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setSound(daSound:FlxSound)
|
||||||
|
{
|
||||||
|
vis = new VisShit(daSound);
|
||||||
|
}
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package funkin.audiovis;
|
package funkin.audiovis;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.group.FlxGroup;
|
|
||||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.math.FlxVector;
|
import flixel.math.FlxVector;
|
||||||
import flixel.system.FlxSound;
|
import flixel.system.FlxSound;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.audiovis.PolygonSpectogram.VISTYPE;
|
import funkin.audio.visualize.PolygonSpectogram.VISTYPE;
|
||||||
import funkin.audiovis.VisShit.CurAudioInfo;
|
import funkin.audiovis.VisShit.CurAudioInfo;
|
||||||
import funkin.audiovis.dsp.FFT;
|
import funkin.audiovis.dsp.FFT;
|
||||||
import haxe.Timer;
|
import haxe.Timer;
|
||||||
|
|
@ -115,7 +114,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
prevLine.x = (curAud.balanced * swagheight / 2 + swagheight / 2) + x;
|
prevLine.x = (curAud.balanced * swagheight / 2 + swagheight / 2) + x;
|
||||||
prevLine.y = (i / group.members.length * daHeight) + y;
|
prevLine.y = (i / group.members.length * daHeight) + y;
|
||||||
|
|
||||||
var line = FlxVector.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
|
var line = FlxPoint.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
|
||||||
|
|
||||||
group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
|
group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
|
||||||
group.members[i].angle = line.degrees;
|
group.members[i].angle = line.degrees;
|
||||||
|
|
@ -213,7 +212,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
prevLine.x = (freqIDK * swagheight / 2 + swagheight / 2) + x;
|
prevLine.x = (freqIDK * swagheight / 2 + swagheight / 2) + x;
|
||||||
prevLine.y = (i / group.members.length * daHeight) + y;
|
prevLine.y = (i / group.members.length * daHeight) + y;
|
||||||
|
|
||||||
var line = FlxVector.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
|
var line = FlxPoint.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
|
||||||
|
|
||||||
// dont draw a line until i figure out a nicer way to view da spikes and shit idk lol!
|
// dont draw a line until i figure out a nicer way to view da spikes and shit idk lol!
|
||||||
// group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
|
// group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
|
||||||
|
|
@ -273,7 +272,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
prevLine.x = (curAud.balanced * swagheight / 2 + swagheight / 2) + x;
|
prevLine.x = (curAud.balanced * swagheight / 2 + swagheight / 2) + x;
|
||||||
prevLine.y = (Std.int(remappedSample) / lengthOfShit * daHeight) + y;
|
prevLine.y = (Std.int(remappedSample) / lengthOfShit * daHeight) + y;
|
||||||
|
|
||||||
var line = FlxVector.get(prevLine.x - group.members[Std.int(remappedSample)].x, prevLine.y - group.members[Std.int(remappedSample)].y);
|
var line = FlxPoint.get(prevLine.x - group.members[Std.int(remappedSample)].x, prevLine.y - group.members[Std.int(remappedSample)].y);
|
||||||
|
|
||||||
group.members[Std.int(remappedSample)].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
|
group.members[Std.int(remappedSample)].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
|
||||||
group.members[Std.int(remappedSample)].angle = line.degrees;
|
group.members[Std.int(remappedSample)].angle = line.degrees;
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,15 @@ import flixel.text.FlxText;
|
||||||
import flixel.ui.FlxButton;
|
import flixel.ui.FlxButton;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.Conductor.BPMChangeEvent;
|
import funkin.Conductor.BPMChangeEvent;
|
||||||
import funkin.Note.NoteData;
|
|
||||||
import funkin.Section.SwagSection;
|
import funkin.Section.SwagSection;
|
||||||
import funkin.SongLoad.SwagSong;
|
import funkin.SongLoad.SwagSong;
|
||||||
|
import funkin.audio.visualize.PolygonSpectogram;
|
||||||
import funkin.audiovis.ABotVis;
|
import funkin.audiovis.ABotVis;
|
||||||
import funkin.audiovis.PolygonSpectogram;
|
|
||||||
import funkin.audiovis.SpectogramSprite;
|
import funkin.audiovis.SpectogramSprite;
|
||||||
|
import funkin.graphics.rendering.MeshRender;
|
||||||
|
import funkin.noteStuff.NoteBasic.NoteData;
|
||||||
import funkin.play.HealthIcon;
|
import funkin.play.HealthIcon;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.rendering.MeshRender;
|
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
import lime.media.AudioBuffer;
|
import lime.media.AudioBuffer;
|
||||||
import lime.utils.Assets;
|
import lime.utils.Assets;
|
||||||
|
|
@ -148,7 +148,7 @@ class ChartingState extends MusicBeatState
|
||||||
updateGrid();
|
updateGrid();
|
||||||
|
|
||||||
loadSong(_song.song);
|
loadSong(_song.song);
|
||||||
Conductor.bpm = _song.bpm;
|
// Conductor.bpm = _song.bpm;
|
||||||
Conductor.mapBPMChanges(_song);
|
Conductor.mapBPMChanges(_song);
|
||||||
|
|
||||||
bpmTxt = new FlxText(1000, 50, 0, "", 16);
|
bpmTxt = new FlxText(1000, 50, 0, "", 16);
|
||||||
|
|
@ -549,7 +549,7 @@ class ChartingState extends MusicBeatState
|
||||||
{
|
{
|
||||||
tempBpm = nums.value;
|
tempBpm = nums.value;
|
||||||
Conductor.mapBPMChanges(_song);
|
Conductor.mapBPMChanges(_song);
|
||||||
Conductor.bpm = nums.value;
|
Conductor.forceBPM(nums.value);
|
||||||
}
|
}
|
||||||
else if (wname == 'note_susLength')
|
else if (wname == 'note_susLength')
|
||||||
{
|
{
|
||||||
|
|
@ -622,7 +622,7 @@ class ChartingState extends MusicBeatState
|
||||||
|
|
||||||
FlxG.sound.music.pan = FlxMath.remapToRange(FlxG.mouse.screenX, 0, FlxG.width, -1, 1) * 10;
|
FlxG.sound.music.pan = FlxMath.remapToRange(FlxG.mouse.screenX, 0, FlxG.width, -1, 1) * 10;
|
||||||
|
|
||||||
curStep = recalculateSteps();
|
// curStep = recalculateSteps();
|
||||||
|
|
||||||
Conductor.songPosition = FlxG.sound.music.time;
|
Conductor.songPosition = FlxG.sound.music.time;
|
||||||
_song.song = typingShit.text;
|
_song.song = typingShit.text;
|
||||||
|
|
@ -649,7 +649,7 @@ class ChartingState extends MusicBeatState
|
||||||
if (FlxG.keys.justPressed.X)
|
if (FlxG.keys.justPressed.X)
|
||||||
toggleAltAnimNote();
|
toggleAltAnimNote();
|
||||||
|
|
||||||
if (curBeat % 4 == 0 && curStep >= 16 * (curSection + 1))
|
if (false) // (curBeat % 4 == 0 && curStep >= 16 * (curSection + 1))
|
||||||
{
|
{
|
||||||
// trace(curStep);
|
// trace(curStep);
|
||||||
// trace((SongLoad.getSong()[curSection].lengthInSteps) * (curSection + 1));
|
// trace((SongLoad.getSong()[curSection].lengthInSteps) * (curSection + 1));
|
||||||
|
|
@ -663,8 +663,8 @@ class ChartingState extends MusicBeatState
|
||||||
changeSection(curSection + 1, false);
|
changeSection(curSection + 1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.watch.addQuick('daBeat', curBeat);
|
FlxG.watch.addQuick('daBeat', Conductor.currentBeat);
|
||||||
FlxG.watch.addQuick('daStep', curStep);
|
FlxG.watch.addQuick('daStep', Conductor.currentStep);
|
||||||
|
|
||||||
if (FlxG.mouse.pressedMiddle && FlxG.mouse.overlaps(gridBG))
|
if (FlxG.mouse.pressedMiddle && FlxG.mouse.overlaps(gridBG))
|
||||||
{
|
{
|
||||||
|
|
@ -1016,12 +1016,12 @@ class ChartingState extends MusicBeatState
|
||||||
if (curSelectedNote != null)
|
if (curSelectedNote != null)
|
||||||
{
|
{
|
||||||
trace('ALT NOTE SHIT');
|
trace('ALT NOTE SHIT');
|
||||||
curSelectedNote.altNote = (curSelectedNote.altNote == "alt") ? "" : "alt";
|
curSelectedNote.noteKind = (curSelectedNote.noteKind == "alt") ? "" : "alt";
|
||||||
trace(curSelectedNote.altNote);
|
trace(curSelectedNote.noteKind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recalculateSteps():Int
|
function recalculateSteps():Float
|
||||||
{
|
{
|
||||||
var lastChange:BPMChangeEvent = {
|
var lastChange:BPMChangeEvent = {
|
||||||
stepTime: 0,
|
stepTime: 0,
|
||||||
|
|
@ -1034,10 +1034,10 @@ class ChartingState extends MusicBeatState
|
||||||
lastChange = Conductor.bpmChangeMap[i];
|
lastChange = Conductor.bpmChangeMap[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
curStep = lastChange.stepTime + Math.floor((FlxG.sound.music.time - lastChange.songTime) / Conductor.stepCrochet);
|
// curStep = lastChange.stepTime + Math.floor((FlxG.sound.music.time - lastChange.songTime) / Conductor.stepCrochet);
|
||||||
updateBeat();
|
// updateBeat();
|
||||||
|
|
||||||
return curStep;
|
return Conductor.currentStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSection(songBeginning:SongResetType = SECTION):Void
|
function resetSection(songBeginning:SongResetType = SECTION):Void
|
||||||
|
|
@ -1061,7 +1061,7 @@ class ChartingState extends MusicBeatState
|
||||||
}
|
}
|
||||||
|
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.time = FlxG.sound.music.time;
|
||||||
updateCurStep();
|
// updateCurStep();
|
||||||
|
|
||||||
updateGrid();
|
updateGrid();
|
||||||
updateSectionUI();
|
updateSectionUI();
|
||||||
|
|
@ -1092,7 +1092,7 @@ class ChartingState extends MusicBeatState
|
||||||
|
|
||||||
FlxG.sound.music.time = sectionStartTime();
|
FlxG.sound.music.time = sectionStartTime();
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.time = FlxG.sound.music.time;
|
||||||
updateCurStep();
|
// updateCurStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGrid();
|
updateGrid();
|
||||||
|
|
@ -1223,7 +1223,7 @@ class ChartingState extends MusicBeatState
|
||||||
|
|
||||||
if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0)
|
if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0)
|
||||||
{
|
{
|
||||||
Conductor.bpm = SongLoad.getSong()[curSection].bpm;
|
Conductor.forceBPM(SongLoad.getSong()[curSection].bpm);
|
||||||
FlxG.log.add('CHANGED BPM!');
|
FlxG.log.add('CHANGED BPM!');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1233,7 +1233,7 @@ class ChartingState extends MusicBeatState
|
||||||
for (i in 0...curSection)
|
for (i in 0...curSection)
|
||||||
if (SongLoad.getSong()[i].changeBPM)
|
if (SongLoad.getSong()[i].changeBPM)
|
||||||
daBPM = SongLoad.getSong()[i].bpm;
|
daBPM = SongLoad.getSong()[i].bpm;
|
||||||
Conductor.bpm = daBPM;
|
Conductor.forceBPM(daBPM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE
|
/* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE
|
||||||
|
|
@ -1358,7 +1358,7 @@ class ChartingState extends MusicBeatState
|
||||||
var noteStrum = getStrumTime(dummyArrow.y) + sectionStartTime();
|
var noteStrum = getStrumTime(dummyArrow.y) + sectionStartTime();
|
||||||
var noteData = Math.floor(FlxG.mouse.x / GRID_SIZE);
|
var noteData = Math.floor(FlxG.mouse.x / GRID_SIZE);
|
||||||
var noteSus = 0;
|
var noteSus = 0;
|
||||||
var noteAlt = "";
|
var noteKind = "";
|
||||||
|
|
||||||
justPlacedNote = true;
|
justPlacedNote = true;
|
||||||
|
|
||||||
|
|
@ -1399,7 +1399,7 @@ class ChartingState extends MusicBeatState
|
||||||
|
|
||||||
var daNewNote:Note = new Note(noteStrum, noteData);
|
var daNewNote:Note = new Note(noteStrum, noteData);
|
||||||
daNewNote.data.sustainLength = noteSus;
|
daNewNote.data.sustainLength = noteSus;
|
||||||
daNewNote.data.altNote = noteAlt;
|
daNewNote.data.noteKind = noteKind;
|
||||||
SongLoad.getSong()[curSection].sectionNotes.push(daNewNote.data);
|
SongLoad.getSong()[curSection].sectionNotes.push(daNewNote.data);
|
||||||
|
|
||||||
curSelectedNote = SongLoad.getSong()[curSection].sectionNotes[SongLoad.getSong()[curSection].sectionNotes.length - 1];
|
curSelectedNote = SongLoad.getSong()[curSection].sectionNotes[SongLoad.getSong()[curSection].sectionNotes.length - 1];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package funkin.rendering;
|
package funkin.graphics.rendering;
|
||||||
|
|
||||||
import flixel.FlxStrip;
|
import flixel.FlxStrip;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
238
source/funkin/graphics/rendering/SustainTrail.hx
Normal file
238
source/funkin/graphics/rendering/SustainTrail.hx
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
package funkin.graphics.rendering;
|
||||||
|
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.graphics.FlxGraphic;
|
||||||
|
import flixel.graphics.tile.FlxDrawTrianglesItem;
|
||||||
|
import flixel.math.FlxMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is based heavily on the `FlxStrip` class. It uses `drawTriangles()` to clip a sustain note
|
||||||
|
* trail at a certain time.
|
||||||
|
* The whole `FlxGraphic` is used as a texture map. See the `NOTE_hold_assets.fla` file for specifics
|
||||||
|
* on how it should be constructed.
|
||||||
|
*
|
||||||
|
* @author MtH
|
||||||
|
*/
|
||||||
|
class SustainTrail extends FlxSprite
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Used to determine which note color/direction to draw for the sustain.
|
||||||
|
*/
|
||||||
|
public var noteData:Int = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The zoom level to render the sustain at.
|
||||||
|
* Defaults to 1.0, increased to 6.0 for pixel notes.
|
||||||
|
*/
|
||||||
|
public var zoom(default, set):Float = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strumtime of the note, in milliseconds.
|
||||||
|
*/
|
||||||
|
public var strumTime:Float = 0; // millis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sustain length of the note, in milliseconds.
|
||||||
|
*/
|
||||||
|
public var sustainLength(default, set):Float = 0; // millis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scroll speed of the note, as a multiplier.
|
||||||
|
*/
|
||||||
|
public var scrollSpeed(default, set):Float = 1.0; // stand-in for PlayState scroll speed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the note was missed.
|
||||||
|
*/
|
||||||
|
public var missed:Bool = false; // maybe BlendMode.MULTIPLY if missed somehow, drawTriangles does not support!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Vector` of floats where each pair of numbers is treated as a coordinate location (an x, y pair).
|
||||||
|
*/
|
||||||
|
private var vertices:DrawData<Float> = new DrawData<Float>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Vector` of integers or indexes, where every three indexes define a triangle.
|
||||||
|
*/
|
||||||
|
private var indices:DrawData<Int> = new DrawData<Int>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Vector` of normalized coordinates used to apply texture mapping.
|
||||||
|
*/
|
||||||
|
private var uvtData:DrawData<Float> = new DrawData<Float>();
|
||||||
|
|
||||||
|
private var processedGraphic:FlxGraphic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What part of the trail's end actually represents the end of the note.
|
||||||
|
* This can be used to have a little bit sticking out.
|
||||||
|
*/
|
||||||
|
public var endOffset:Float = 0.5; // 0.73 is roughly the bottom of the sprite in the normal graphic!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At what point the bottom for the trail's end should be clipped off.
|
||||||
|
* Used in cases where there's an extra bit of the graphic on the bottom to avoid antialiasing issues with overflow.
|
||||||
|
*/
|
||||||
|
public var bottomClip:Float = 0.9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally you would take strumTime:Float, noteData:Int, sustainLength:Float, parentNote:Note (?)
|
||||||
|
* @param NoteData
|
||||||
|
* @param SustainLength
|
||||||
|
* @param FileName
|
||||||
|
*/
|
||||||
|
public function new(NoteData:Int, SustainLength:Float, Path:String, ?Alpha:Float = 0.6, ?Pixel:Bool = false)
|
||||||
|
{
|
||||||
|
super(0, 0, Path);
|
||||||
|
|
||||||
|
// BASIC SETUP
|
||||||
|
this.sustainLength = SustainLength;
|
||||||
|
this.noteData = NoteData;
|
||||||
|
|
||||||
|
// CALCULATE SIZE
|
||||||
|
if (Pixel)
|
||||||
|
{
|
||||||
|
this.endOffset = bottomClip = 1;
|
||||||
|
this.antialiasing = false;
|
||||||
|
this.zoom = 6.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.antialiasing = true;
|
||||||
|
this.zoom = 1.0;
|
||||||
|
}
|
||||||
|
// width = graphic.width / 8 * zoom; // amount of notes * 2
|
||||||
|
height = sustainHeight(sustainLength, scrollSpeed);
|
||||||
|
// instead of scrollSpeed, PlayState.SONG.speed
|
||||||
|
|
||||||
|
alpha = Alpha; // setting alpha calls updateColorTransform(), which initializes processedGraphic!
|
||||||
|
|
||||||
|
updateClipping();
|
||||||
|
indices = new DrawData<Int>(12, true, [0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates height of a sustain note for a given length (milliseconds) and scroll speed.
|
||||||
|
* @param susLength The length of the sustain note in milliseconds.
|
||||||
|
* @param scroll The current scroll speed.
|
||||||
|
*/
|
||||||
|
public static inline function sustainHeight(susLength:Float, scroll:Float)
|
||||||
|
{
|
||||||
|
return (susLength * 0.45 * scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_zoom(z:Float)
|
||||||
|
{
|
||||||
|
this.zoom = z;
|
||||||
|
width = graphic.width / 8 * z;
|
||||||
|
updateClipping();
|
||||||
|
return this.zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_sustainLength(s:Float)
|
||||||
|
{
|
||||||
|
height = sustainHeight(s, scrollSpeed);
|
||||||
|
return sustainLength = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_scrollSpeed(s:Float)
|
||||||
|
{
|
||||||
|
height = sustainHeight(sustainLength, s);
|
||||||
|
return scrollSpeed = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up new vertex and UV data to clip the trail.
|
||||||
|
* If flipY is true, top and bottom bounds swap places.
|
||||||
|
* @param songTime The time to clip the note at, in milliseconds.
|
||||||
|
*/
|
||||||
|
public function updateClipping(songTime:Float = 0):Void
|
||||||
|
{
|
||||||
|
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), scrollSpeed), 0, height);
|
||||||
|
if (clipHeight == 0)
|
||||||
|
{
|
||||||
|
visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
visible = true;
|
||||||
|
var bottomHeight:Float = graphic.height * zoom * endOffset;
|
||||||
|
var partHeight:Float = clipHeight - bottomHeight;
|
||||||
|
// == HOLD == //
|
||||||
|
// left bound
|
||||||
|
vertices[6] = vertices[0] = 0.0;
|
||||||
|
// top bound
|
||||||
|
vertices[3] = vertices[1] = flipY ? clipHeight : height - clipHeight;
|
||||||
|
// right bound
|
||||||
|
vertices[4] = vertices[2] = width;
|
||||||
|
// bottom bound (also top bound for hold ends)
|
||||||
|
if (partHeight > 0)
|
||||||
|
vertices[7] = vertices[5] = flipY ? 0.0 + bottomHeight : vertices[1] + partHeight;
|
||||||
|
else
|
||||||
|
vertices[7] = vertices[5] = vertices[1];
|
||||||
|
|
||||||
|
// same shit with da bounds, just in relation to the texture
|
||||||
|
uvtData[6] = uvtData[0] = 1 / 4 * (noteData % 4);
|
||||||
|
// height overflows past image bounds so wraps around, looping the texture
|
||||||
|
// flipY bounds are not swapped for UV data, so the graphic is actually flipped
|
||||||
|
// top bound
|
||||||
|
uvtData[3] = uvtData[1] = (-partHeight) / graphic.height / zoom;
|
||||||
|
uvtData[4] = uvtData[2] = uvtData[0] + 1 / 8; // 1
|
||||||
|
// bottom bound
|
||||||
|
uvtData[7] = uvtData[5] = 0.0;
|
||||||
|
|
||||||
|
// == HOLD ENDS == //
|
||||||
|
// left bound
|
||||||
|
vertices[14] = vertices[8] = vertices[0];
|
||||||
|
// top bound
|
||||||
|
vertices[11] = vertices[9] = vertices[5];
|
||||||
|
// right bound
|
||||||
|
vertices[12] = vertices[10] = vertices[2];
|
||||||
|
// bottom bound, mind the bottomClip because it clips off bottom of graphic!!
|
||||||
|
vertices[15] = vertices[13] = flipY ? graphic.height * (-bottomClip + endOffset) : height + graphic.height * (bottomClip - endOffset);
|
||||||
|
|
||||||
|
uvtData[14] = uvtData[8] = uvtData[2];
|
||||||
|
if (partHeight > 0)
|
||||||
|
uvtData[11] = uvtData[9] = 0.0;
|
||||||
|
else
|
||||||
|
uvtData[11] = uvtData[9] = (bottomHeight - clipHeight) / zoom / graphic.height;
|
||||||
|
uvtData[12] = uvtData[10] = uvtData[8] + 1 / 8;
|
||||||
|
// again, clips off bottom !!
|
||||||
|
uvtData[15] = uvtData[13] = bottomClip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:access(flixel.FlxCamera)
|
||||||
|
override public function draw():Void
|
||||||
|
{
|
||||||
|
if (alpha == 0 || graphic == null || vertices == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (camera in cameras)
|
||||||
|
{
|
||||||
|
if (!camera.visible || !camera.exists || !isOnScreen(camera))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||||
|
camera.drawTriangles(processedGraphic, vertices, indices, uvtData, null, _point, blend, true, antialiasing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function destroy():Void
|
||||||
|
{
|
||||||
|
vertices = null;
|
||||||
|
indices = null;
|
||||||
|
uvtData = null;
|
||||||
|
processedGraphic.destroy();
|
||||||
|
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
override function updateColorTransform():Void
|
||||||
|
{
|
||||||
|
super.updateColorTransform();
|
||||||
|
if (processedGraphic != null)
|
||||||
|
processedGraphic.destroy();
|
||||||
|
processedGraphic = FlxGraphic.fromGraphic(graphic, true);
|
||||||
|
processedGraphic.bitmap.colorTransform(processedGraphic.bitmap.rect, colorTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package funkin.modding;
|
|
||||||
|
|
||||||
import polymod.hscript.HScriptable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functions annotated with @:hscript will call the relevant script.
|
|
||||||
* Functions annotated with @:hookable can be reassigned.
|
|
||||||
* NOTE: If you receive the following error when making a function use @:hookable:
|
|
||||||
* `Cannot access this or other member field in variable initialization`
|
|
||||||
* This is because you need to perform calls and assignments using a static variable referencing the target object.
|
|
||||||
*/
|
|
||||||
@:hscript({
|
|
||||||
// ALL of these values are added to ALL scripts in the child classes.
|
|
||||||
context: [FlxG, FlxSprite, Math, Paths, Std]
|
|
||||||
})
|
|
||||||
interface IHook extends HScriptable {}
|
|
||||||
|
|
@ -44,7 +44,7 @@ interface INoteScriptedClass extends IScriptedClass
|
||||||
*
|
*
|
||||||
* I previously considered adding events for onKeyDown, onKeyUp, mouse events, etc.
|
* I previously considered adding events for onKeyDown, onKeyUp, mouse events, etc.
|
||||||
* However, I realized that you can simply call something like the following within a module:
|
* However, I realized that you can simply call something like the following within a module:
|
||||||
* `FlxG.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);`
|
* `FlxG.state.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);`
|
||||||
* This is more efficient than adding an entire event handler for every key press.
|
* This is more efficient than adding an entire event handler for every key press.
|
||||||
*
|
*
|
||||||
* -Eric
|
* -Eric
|
||||||
|
|
@ -54,23 +54,72 @@ interface INoteScriptedClass extends IScriptedClass
|
||||||
*/
|
*/
|
||||||
interface IPlayStateScriptedClass extends IScriptedClass
|
interface IPlayStateScriptedClass extends IScriptedClass
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Called when the game is paused.
|
||||||
|
* Has properties to set whether the pause easter egg will happen,
|
||||||
|
* and can be cancelled by scripts.
|
||||||
|
*/
|
||||||
public function onPause(event:PauseScriptEvent):Void;
|
public function onPause(event:PauseScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the game is unpaused.
|
||||||
|
*/
|
||||||
public function onResume(event:ScriptEvent):Void;
|
public function onResume(event:ScriptEvent):Void;
|
||||||
|
|
||||||
public function onSongLoaded(eent:SongLoadScriptEvent):Void;
|
/**
|
||||||
|
* Called when the song has been parsed, before notes have been placed.
|
||||||
|
* Use this to mutate the chart.
|
||||||
|
*/
|
||||||
|
public function onSongLoaded(event:SongLoadScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the song starts (conductor time is 0 seconds).
|
||||||
|
*/
|
||||||
public function onSongStart(event:ScriptEvent):Void;
|
public function onSongStart(event:ScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the song ends and the song is about to be unloaded.
|
||||||
|
*/
|
||||||
public function onSongEnd(event:ScriptEvent):Void;
|
public function onSongEnd(event:ScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called as the player runs out of health just before the game over substate is entered.
|
||||||
|
*/
|
||||||
public function onGameOver(event:ScriptEvent):Void;
|
public function onGameOver(event:ScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the player restarts the song, either via pause menu or restarting after a game over.
|
||||||
|
*/
|
||||||
public function onSongRetry(event:ScriptEvent):Void;
|
public function onSongRetry(event:ScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when EITHER player hits a note.
|
||||||
|
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
||||||
|
*/
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when EITHER player (usually the player) misses a note.
|
||||||
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the player presses a key when no note is on the strumline.
|
||||||
|
*/
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void;
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once every step of the song.
|
||||||
|
*/
|
||||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called once every beat of the song.
|
||||||
|
*/
|
||||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the countdown of the song starts.
|
||||||
|
*/
|
||||||
public function onCountdownStart(event:CountdownScriptEvent):Void;
|
public function onCountdownStart(event:CountdownScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the a part of the countdown happens.
|
||||||
|
*/
|
||||||
public function onCountdownStep(event:CountdownScriptEvent):Void;
|
public function onCountdownStep(event:CountdownScriptEvent):Void;
|
||||||
|
/**
|
||||||
|
* Called when the countdown of the song ends.
|
||||||
|
*/
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent):Void;
|
public function onCountdownEnd(event:CountdownScriptEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package funkin.modding;
|
package funkin.modding;
|
||||||
|
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
import funkin.play.song.SongData;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import polymod.Polymod;
|
import polymod.Polymod;
|
||||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||||
|
|
@ -21,11 +23,21 @@ class PolymodHandler
|
||||||
*/
|
*/
|
||||||
static final MOD_FOLDER = "mods";
|
static final MOD_FOLDER = "mods";
|
||||||
|
|
||||||
|
public static function createModRoot()
|
||||||
|
{
|
||||||
|
if (!sys.FileSystem.exists(MOD_FOLDER))
|
||||||
|
{
|
||||||
|
sys.FileSystem.createDirectory(MOD_FOLDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the game with ALL mods enabled with Polymod.
|
* Loads the game with ALL mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadAllMods()
|
public static function loadAllMods()
|
||||||
{
|
{
|
||||||
|
// Create the mod root if it doesn't exist.
|
||||||
|
createModRoot();
|
||||||
trace("Initializing Polymod (using all mods)...");
|
trace("Initializing Polymod (using all mods)...");
|
||||||
loadModsById(getAllModIds());
|
loadModsById(getAllModIds());
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +47,9 @@ class PolymodHandler
|
||||||
*/
|
*/
|
||||||
public static function loadEnabledMods()
|
public static function loadEnabledMods()
|
||||||
{
|
{
|
||||||
|
// Create the mod root if it doesn't exist.
|
||||||
|
createModRoot();
|
||||||
|
|
||||||
trace("Initializing Polymod (using configured mods)...");
|
trace("Initializing Polymod (using configured mods)...");
|
||||||
loadModsById(getEnabledModIds());
|
loadModsById(getEnabledModIds());
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +59,9 @@ class PolymodHandler
|
||||||
*/
|
*/
|
||||||
public static function loadNoMods()
|
public static function loadNoMods()
|
||||||
{
|
{
|
||||||
|
// Create the mod root if it doesn't exist.
|
||||||
|
createModRoot();
|
||||||
|
|
||||||
// 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([]);
|
loadModsById([]);
|
||||||
|
|
@ -67,7 +85,7 @@ class PolymodHandler
|
||||||
// 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.
|
||||||
apiVersion: API_VERSION,
|
apiVersionRule: API_VERSION,
|
||||||
// Call this function any time an error occurs.
|
// Call this function any time an error occurs.
|
||||||
errorCallback: PolymodErrorHandler.onPolymodError,
|
errorCallback: PolymodErrorHandler.onPolymodError,
|
||||||
// Enforce semantic version patterns for each mod.
|
// Enforce semantic version patterns for each mod.
|
||||||
|
|
@ -156,8 +174,8 @@ class PolymodHandler
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
assetLibraryPaths: [
|
assetLibraryPaths: [
|
||||||
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
||||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8",
|
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +183,7 @@ class PolymodHandler
|
||||||
public static function getAllMods():Array<ModMetadata>
|
public static function getAllMods():Array<ModMetadata>
|
||||||
{
|
{
|
||||||
trace('Scanning the mods folder...');
|
trace('Scanning the mods folder...');
|
||||||
var modMetadata = Polymod.scan(MOD_FOLDER);
|
var modMetadata = Polymod.scan();
|
||||||
trace('Found ${modMetadata.length} mods when scanning.');
|
trace('Found ${modMetadata.length} mods when scanning.');
|
||||||
return modMetadata;
|
return modMetadata;
|
||||||
}
|
}
|
||||||
|
|
@ -219,17 +237,23 @@ class PolymodHandler
|
||||||
{
|
{
|
||||||
// Forcibly clear scripts so that scripts can be edited.
|
// Forcibly clear scripts so that scripts can be edited.
|
||||||
ModuleHandler.clearModuleCache();
|
ModuleHandler.clearModuleCache();
|
||||||
polymod.hscript.PolymodScriptClass.clearScriptClasses();
|
Polymod.clearScripts();
|
||||||
|
|
||||||
// Forcibly reload Polymod so it finds any new files.
|
// Forcibly reload Polymod so it finds any new files.
|
||||||
loadEnabledMods();
|
// TODO: Replace this with loadEnabledMods().
|
||||||
|
funkin.modding.PolymodHandler.loadAllMods();
|
||||||
|
|
||||||
// Reload scripted classes so stages and modules will update.
|
// Reload scripted classes so stages and modules will update.
|
||||||
polymod.hscript.PolymodScriptClass.registerAllScriptClasses();
|
Polymod.registerAllScriptClasses();
|
||||||
|
|
||||||
// Reload the stages in cache.
|
// Reload everything that is cached.
|
||||||
// TODO: Currently this causes lag since you're reading a lot of files, how to fix?
|
// Currently this freezes the game for a second but I guess that's tolerable?
|
||||||
|
|
||||||
|
// TODO: Reload event callbacks
|
||||||
|
|
||||||
|
SongDataParser.loadSongCache();
|
||||||
StageDataParser.loadStageCache();
|
StageDataParser.loadStageCache();
|
||||||
|
CharacterDataParser.loadCharacterCache();
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.loadModuleCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
source/funkin/modding/base/ScriptedFlxRuntimeShader.hx
Normal file
7
source/funkin/modding/base/ScriptedFlxRuntimeShader.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxRuntimeShader extends FlxRuntimeShader implements HScriptedClass {}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package funkin.modding.base;
|
package funkin.modding.base;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import funkin.modding.IHook;
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedFlxSprite extends FlxSprite implements IHook {}
|
class ScriptedFlxSprite extends FlxSprite implements HScriptedClass {}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package funkin.modding.base;
|
package funkin.modding.base;
|
||||||
|
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
import funkin.modding.IHook;
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements IHook {}
|
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements HScriptedClass {}
|
||||||
|
|
|
||||||
7
source/funkin/modding/base/ScriptedFlxState.hx
Normal file
7
source/funkin/modding/base/ScriptedFlxState.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import flixel.FlxState;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxState extends FlxState implements HScriptedClass {}
|
||||||
7
source/funkin/modding/base/ScriptedFlxSubState.hx
Normal file
7
source/funkin/modding/base/ScriptedFlxSubState.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import flixel.FlxSubState;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxSubState extends FlxSubState implements HScriptedClass {}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import flixel.addons.transition.FlxTransitionableState;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxTransitionableState extends FlxTransitionableState implements HScriptedClass {}
|
||||||
7
source/funkin/modding/base/ScriptedFlxUIState.hx
Normal file
7
source/funkin/modding/base/ScriptedFlxUIState.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import flixel.addons.ui.FlxUIState;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxUIState extends FlxUIState implements HScriptedClass {}
|
||||||
7
source/funkin/modding/base/ScriptedMusicBeatState.hx
Normal file
7
source/funkin/modding/base/ScriptedMusicBeatState.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import funkin.MusicBeatState;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedMusicBeatState extends MusicBeatState implements HScriptedClass {}
|
||||||
7
source/funkin/modding/base/ScriptedMusicBeatSubstate.hx
Normal file
7
source/funkin/modding/base/ScriptedMusicBeatSubstate.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
import funkin.MusicBeatSubstate;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedMusicBeatSubstate extends MusicBeatSubstate implements HScriptedClass {}
|
||||||
|
|
@ -2,7 +2,7 @@ package funkin.modding.events;
|
||||||
|
|
||||||
import flixel.FlxState;
|
import flixel.FlxState;
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
import funkin.Note.NoteDir;
|
import funkin.noteStuff.NoteBasic.NoteDir;
|
||||||
import funkin.play.Countdown.CountdownStep;
|
import funkin.play.Countdown.CountdownStep;
|
||||||
import openfl.events.EventType;
|
import openfl.events.EventType;
|
||||||
import openfl.events.KeyboardEvent;
|
import openfl.events.KeyboardEvent;
|
||||||
|
|
@ -142,7 +142,7 @@ class ScriptEvent
|
||||||
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER";
|
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player presses a key to restart the game.
|
* Called after the player presses a key to restart the game.
|
||||||
* This can happen from the pause menu or the game over screen.
|
* This can happen from the pause menu or the game over screen.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
||||||
|
|
@ -294,15 +294,22 @@ class NoteScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
public var note(default, null):Note;
|
public var note(default, null):Note;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, note:Note, cancelable:Bool = false):Void
|
/**
|
||||||
|
* The combo count as it is with this event.
|
||||||
|
* Will be (combo) on miss events and (combo + 1) on hit events (the stored combo count won't update if the event is cancelled).
|
||||||
|
*/
|
||||||
|
public var comboCount(default, null):Int;
|
||||||
|
|
||||||
|
public function new(type:ScriptEventType, note:Note, comboCount:Int = 0, cancelable:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(type, cancelable);
|
super(type, cancelable);
|
||||||
this.note = note;
|
this.note = note;
|
||||||
|
this.comboCount = comboCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ')';
|
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ', comboCount=' + comboCount + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package funkin.modding.module;
|
package funkin.modding.module;
|
||||||
|
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
|
||||||
import funkin.modding.events.ScriptEvent;
|
|
||||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
|
import funkin.modding.module.Module;
|
||||||
|
import funkin.modding.module.ScriptedModule;
|
||||||
|
|
||||||
using funkin.util.IteratorTools;
|
using funkin.util.IteratorTools;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package funkin.modding.module;
|
package funkin.modding.module;
|
||||||
|
|
||||||
import funkin.modding.IHook;
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedModule extends Module implements IHook {}
|
class ScriptedModule extends Module implements HScriptedClass {}
|
||||||
|
|
|
||||||
196
source/funkin/noteStuff/NoteBasic.hx
Normal file
196
source/funkin/noteStuff/NoteBasic.hx
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
package funkin.noteStuff;
|
||||||
|
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.text.FlxText;
|
||||||
|
|
||||||
|
typedef RawNoteData =
|
||||||
|
{
|
||||||
|
var strumTime:Float;
|
||||||
|
var noteData:NoteType;
|
||||||
|
var sustainLength:Float;
|
||||||
|
var altNote:String;
|
||||||
|
var noteKind:NoteKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:forward
|
||||||
|
abstract NoteData(RawNoteData)
|
||||||
|
{
|
||||||
|
public function new(strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, altNote = "", noteKind = NORMAL)
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
strumTime: strumTime,
|
||||||
|
noteData: noteData,
|
||||||
|
sustainLength: sustainLength,
|
||||||
|
altNote: altNote,
|
||||||
|
noteKind: noteKind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var note(get, never):NoteType;
|
||||||
|
|
||||||
|
inline function get_note()
|
||||||
|
return this.noteData.value;
|
||||||
|
|
||||||
|
public var int(get, never):Int;
|
||||||
|
|
||||||
|
inline function get_int()
|
||||||
|
return this.noteData.int;
|
||||||
|
|
||||||
|
public var dir(get, never):NoteDir;
|
||||||
|
|
||||||
|
inline function get_dir()
|
||||||
|
return this.noteData.value;
|
||||||
|
|
||||||
|
public var dirName(get, never):String;
|
||||||
|
|
||||||
|
inline function get_dirName()
|
||||||
|
return dir.name;
|
||||||
|
|
||||||
|
public var dirNameUpper(get, never):String;
|
||||||
|
|
||||||
|
inline function get_dirNameUpper()
|
||||||
|
return dir.nameUpper;
|
||||||
|
|
||||||
|
public var color(get, never):NoteColor;
|
||||||
|
|
||||||
|
inline function get_color()
|
||||||
|
return this.noteData.value;
|
||||||
|
|
||||||
|
public var colorName(get, never):String;
|
||||||
|
|
||||||
|
inline function get_colorName()
|
||||||
|
return color.name;
|
||||||
|
|
||||||
|
public var colorNameUpper(get, never):String;
|
||||||
|
|
||||||
|
inline function get_colorNameUpper()
|
||||||
|
return color.nameUpper;
|
||||||
|
|
||||||
|
public var highStakes(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_highStakes()
|
||||||
|
return this.noteData.highStakes;
|
||||||
|
|
||||||
|
public var lowStakes(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_lowStakes()
|
||||||
|
return this.noteData.lowStakes;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract NoteType(Int) from Int to Int
|
||||||
|
{
|
||||||
|
// public var raw(get, never):Int;
|
||||||
|
// inline function get_raw() return this;
|
||||||
|
public var int(get, never):Int;
|
||||||
|
|
||||||
|
inline function get_int()
|
||||||
|
return this < 0 ? -this : this % 4;
|
||||||
|
|
||||||
|
public var value(get, never):NoteType;
|
||||||
|
|
||||||
|
inline function get_value()
|
||||||
|
return int;
|
||||||
|
|
||||||
|
public var highStakes(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_highStakes()
|
||||||
|
return this > 3;
|
||||||
|
|
||||||
|
public var lowStakes(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_lowStakes()
|
||||||
|
return this < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:forward
|
||||||
|
enum abstract NoteDir(NoteType) from Int to Int from NoteType
|
||||||
|
{
|
||||||
|
var LEFT = 0;
|
||||||
|
var DOWN = 1;
|
||||||
|
var UP = 2;
|
||||||
|
var RIGHT = 3;
|
||||||
|
var value(get, never):NoteDir;
|
||||||
|
|
||||||
|
inline function get_value()
|
||||||
|
return this.value;
|
||||||
|
|
||||||
|
public var name(get, never):String;
|
||||||
|
|
||||||
|
function get_name()
|
||||||
|
{
|
||||||
|
return switch (value)
|
||||||
|
{
|
||||||
|
case LEFT: "left";
|
||||||
|
case DOWN: "down";
|
||||||
|
case UP: "up";
|
||||||
|
case RIGHT: "right";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var nameUpper(get, never):String;
|
||||||
|
|
||||||
|
function get_nameUpper()
|
||||||
|
{
|
||||||
|
return switch (value)
|
||||||
|
{
|
||||||
|
case LEFT: "LEFT";
|
||||||
|
case DOWN: "DOWN";
|
||||||
|
case UP: "UP";
|
||||||
|
case RIGHT: "RIGHT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:forward
|
||||||
|
enum abstract NoteColor(NoteType) from Int to Int from NoteType
|
||||||
|
{
|
||||||
|
var PURPLE = 0;
|
||||||
|
var BLUE = 1;
|
||||||
|
var GREEN = 2;
|
||||||
|
var RED = 3;
|
||||||
|
var value(get, never):NoteColor;
|
||||||
|
|
||||||
|
inline function get_value()
|
||||||
|
return this.value;
|
||||||
|
|
||||||
|
public var name(get, never):String;
|
||||||
|
|
||||||
|
function get_name()
|
||||||
|
{
|
||||||
|
return switch (value)
|
||||||
|
{
|
||||||
|
case PURPLE: "purple";
|
||||||
|
case BLUE: "blue";
|
||||||
|
case GREEN: "green";
|
||||||
|
case RED: "red";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var nameUpper(get, never):String;
|
||||||
|
|
||||||
|
function get_nameUpper()
|
||||||
|
{
|
||||||
|
return switch (value)
|
||||||
|
{
|
||||||
|
case PURPLE: "PURPLE";
|
||||||
|
case BLUE: "BLUE";
|
||||||
|
case GREEN: "GREEN";
|
||||||
|
case RED: "RED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract NoteKind(String) from String to String
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The default note type.
|
||||||
|
*/
|
||||||
|
var NORMAL = "normal";
|
||||||
|
|
||||||
|
// Testing shiz
|
||||||
|
var PYRO_LIGHT = "pyro_light";
|
||||||
|
var PYRO_KICK = "pyro_kick";
|
||||||
|
var PYRO_TOSS = "pyro_toss";
|
||||||
|
var PYRO_COCK = "pyro_cock"; // lol
|
||||||
|
var PYRO_SHOOT = "pyro_shoot";
|
||||||
|
}
|
||||||
12
source/funkin/noteStuff/NoteEvent.hx
Normal file
12
source/funkin/noteStuff/NoteEvent.hx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package funkin.noteStuff;
|
||||||
|
|
||||||
|
import funkin.noteStuff.NoteBasic.NoteType;
|
||||||
|
import funkin.play.Strumline.StrumlineStyle;
|
||||||
|
|
||||||
|
class NoteEvent extends Note
|
||||||
|
{
|
||||||
|
public function new(strumTime:Float = 0, noteData:NoteType, ?prevNote:Note, ?sustainNote:Bool = false, ?style:StrumlineStyle = NORMAL)
|
||||||
|
{
|
||||||
|
super(strumTime, noteData, prevNote, sustainNote, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
source/funkin/noteStuff/NoteUtil.hx
Normal file
99
source/funkin/noteStuff/NoteUtil.hx
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
package funkin.noteStuff;
|
||||||
|
|
||||||
|
import haxe.Json;
|
||||||
|
import openfl.Assets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just various functions that IDK where to put em!!!
|
||||||
|
* Semi-temp for now? the note stuff is super clutter-y right now
|
||||||
|
* so I am putting this new stuff here right now XDD
|
||||||
|
*
|
||||||
|
* A lot of this stuff can probably be moved to where appropriate!
|
||||||
|
* i dont care about NoteUtil.hx at all!!!
|
||||||
|
*/
|
||||||
|
class NoteUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* IDK THING FOR BOTH LOL! DIS SHIT HACK-Y
|
||||||
|
* @param jsonPath
|
||||||
|
* @return Map<Int, Array<SongEventInfo>>
|
||||||
|
*/
|
||||||
|
public static function loadSongEvents(jsonPath:String):Map<Int, Array<SongEventInfo>>
|
||||||
|
{
|
||||||
|
return parseSongEvents(loadSongEventFromJson(jsonPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function loadSongEventFromJson(jsonPath:String):Array<SongEvent>
|
||||||
|
{
|
||||||
|
var daEvents:Array<SongEvent>;
|
||||||
|
daEvents = cast Json.parse(Assets.getText(jsonPath)).events; // DUMB LIL DETAIL HERE: MAKE SURE THAT .events IS THERE??
|
||||||
|
trace('GET JSON SONG EVENTS:');
|
||||||
|
trace(daEvents);
|
||||||
|
return daEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses song event json stuff into a neater lil map grouping?
|
||||||
|
* @param songEvents
|
||||||
|
*/
|
||||||
|
public static function parseSongEvents(songEvents:Array<SongEvent>):Map<Int, Array<SongEventInfo>>
|
||||||
|
{
|
||||||
|
var songData:Map<Int, Array<SongEventInfo>> = new Map();
|
||||||
|
|
||||||
|
for (songEvent in songEvents)
|
||||||
|
{
|
||||||
|
trace(songEvent);
|
||||||
|
if (songData[songEvent.t] == null)
|
||||||
|
songData[songEvent.t] = [];
|
||||||
|
|
||||||
|
songData[songEvent.t].push({songEventType: songEvent.e, value: songEvent.v, activated: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
trace("FINISH SONG EVENTS!");
|
||||||
|
trace(songData);
|
||||||
|
|
||||||
|
return songData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function checkSongEvents(songData:Map<Int, Array<SongEventInfo>>, time:Float)
|
||||||
|
{
|
||||||
|
for (eventGrp in songData.keys())
|
||||||
|
{
|
||||||
|
if (time >= eventGrp)
|
||||||
|
{
|
||||||
|
for (events in songData[eventGrp])
|
||||||
|
{
|
||||||
|
if (!events.activated)
|
||||||
|
{
|
||||||
|
// TURN TO NICER SWITCH STATEMENT CHECKER OF EVENT TYPES!!
|
||||||
|
trace(events.value);
|
||||||
|
trace(eventGrp);
|
||||||
|
trace(Conductor.songPosition);
|
||||||
|
events.activated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef SongEventInfo =
|
||||||
|
{
|
||||||
|
var songEventType:SongEventType;
|
||||||
|
var value:Dynamic;
|
||||||
|
var activated:Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef SongEvent =
|
||||||
|
{
|
||||||
|
var t:Int;
|
||||||
|
var e:SongEventType;
|
||||||
|
var v:Dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract SongEventType(String)
|
||||||
|
{
|
||||||
|
var FocusCamera;
|
||||||
|
var PlayCharAnim;
|
||||||
|
var Trace;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package funkin;
|
package funkin.play;
|
||||||
|
|
||||||
import flixel.FlxObject;
|
import flixel.FlxObject;
|
||||||
|
import flixel.FlxSprite;
|
||||||
import flixel.system.FlxSound;
|
import flixel.system.FlxSound;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
|
@ -20,6 +21,27 @@ using StringTools;
|
||||||
*/
|
*/
|
||||||
class GameOverSubstate extends MusicBeatSubstate
|
class GameOverSubstate extends MusicBeatSubstate
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Which alternate animation on the character to use.
|
||||||
|
* You can set this via script.
|
||||||
|
* For example, playing a different animation when BF dies in Week 4
|
||||||
|
* or Pico dies in Weekend 1.
|
||||||
|
*/
|
||||||
|
public static var animationSuffix:String = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which alternate game over music to use.
|
||||||
|
* You can set this via script.
|
||||||
|
* For example, the bf-pixel script sets this to `-pixel`
|
||||||
|
* and the pico-playable script sets this to `Pico`.
|
||||||
|
*/
|
||||||
|
public static var musicSuffix:String = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which alternate "blue ball" sound effect to use.
|
||||||
|
*/
|
||||||
|
public static var blueBallSuffix:String = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The boyfriend character.
|
* The boyfriend character.
|
||||||
*/
|
*/
|
||||||
|
|
@ -41,79 +63,99 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||||
*/
|
*/
|
||||||
var isEnding:Bool = false;
|
var isEnding:Bool = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Music variant to use.
|
|
||||||
* TODO: De-hardcode this somehow.
|
|
||||||
*/
|
|
||||||
var musicVariant:String = "";
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the game over configuration to the default.
|
||||||
|
*/
|
||||||
|
public static function reset()
|
||||||
|
{
|
||||||
|
animationSuffix = "";
|
||||||
|
musicSuffix = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function create()
|
||||||
|
{
|
||||||
|
super.create();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set up the visuals
|
||||||
|
//
|
||||||
|
|
||||||
|
// Add a black background to the screen.
|
||||||
|
// We make this transparent so that we can see the stage underneath during debugging.
|
||||||
|
var bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||||
|
bg.alpha = 0.25;
|
||||||
|
bg.scrollFactor.set();
|
||||||
|
add(bg);
|
||||||
|
|
||||||
|
// Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubstate.
|
||||||
|
// We can then play the character's `firstDeath` animation.
|
||||||
|
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||||
|
boyfriend.isDead = true;
|
||||||
|
add(boyfriend);
|
||||||
|
boyfriend.resetCharacter();
|
||||||
|
boyfriend.playAnimation('firstDeath', true, true);
|
||||||
|
|
||||||
|
// Assign a camera follow point to the boyfriend's position.
|
||||||
|
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||||
|
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||||
|
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||||
|
add(cameraFollowPoint);
|
||||||
|
|
||||||
|
FlxG.camera.target = null;
|
||||||
|
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set up the audio
|
||||||
|
//
|
||||||
|
|
||||||
|
// Prepare the game over music.
|
||||||
FlxG.sound.list.add(gameOverMusic);
|
FlxG.sound.list.add(gameOverMusic);
|
||||||
gameOverMusic.stop();
|
gameOverMusic.stop();
|
||||||
|
|
||||||
|
// The conductor now represents the BPM of the game over music.
|
||||||
Conductor.songPosition = 0;
|
Conductor.songPosition = 0;
|
||||||
|
|
||||||
|
// Play the "blue balled" sound. May play a variant if one has been assigned.
|
||||||
playBlueBalledSFX();
|
playBlueBalledSFX();
|
||||||
|
|
||||||
switch (PlayState.instance.currentStageId)
|
|
||||||
{
|
|
||||||
case 'school' | 'schoolEvil':
|
|
||||||
musicVariant = "-pixel";
|
|
||||||
default:
|
|
||||||
if (PlayState.instance.currentStage.getBoyfriend().characterId == 'pico')
|
|
||||||
{
|
|
||||||
musicVariant = "Pico";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
musicVariant = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to remove boyfriend from the stage. Then we can add him back at the end.
|
|
||||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
|
||||||
boyfriend.isDead = true;
|
|
||||||
boyfriend.playAnimation('firstDeath');
|
|
||||||
add(boyfriend);
|
|
||||||
|
|
||||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
|
||||||
add(cameraFollowPoint);
|
|
||||||
|
|
||||||
// FlxG.camera.scroll.set();
|
|
||||||
FlxG.camera.target = null;
|
|
||||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
{
|
{
|
||||||
// makes the lerp non-dependant on the framerate
|
|
||||||
// FlxG.camera.followLerp = CoolUtil.camLerpShit(0.01);
|
|
||||||
|
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Handle user inputs.
|
||||||
|
//
|
||||||
|
|
||||||
|
// MOBILE ONLY: Restart the level when tapping Boyfriend.
|
||||||
if (FlxG.onMobile)
|
if (FlxG.onMobile)
|
||||||
{
|
{
|
||||||
var touch = FlxG.touches.getFirst();
|
var touch = FlxG.touches.getFirst();
|
||||||
if (touch != null)
|
if (touch != null)
|
||||||
{
|
{
|
||||||
if (touch.overlaps(boyfriend))
|
if (touch.overlaps(boyfriend))
|
||||||
|
{
|
||||||
confirmDeath();
|
confirmDeath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYBOARD ONLY: Restart the level when pressing the assigned key.
|
||||||
if (controls.ACCEPT)
|
if (controls.ACCEPT)
|
||||||
{
|
{
|
||||||
confirmDeath();
|
confirmDeath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYBOARD ONLY: Return to the menu when pressing the assigned key.
|
||||||
if (controls.BACK)
|
if (controls.BACK)
|
||||||
{
|
{
|
||||||
PlayState.deathCounter = 0;
|
PlayState.deathCounter = 0;
|
||||||
PlayState.seenCutscene = false;
|
PlayState.seenCutscene = false;
|
||||||
// FlxG.sound.music.stop();
|
|
||||||
gameOverMusic.stop();
|
gameOverMusic.stop();
|
||||||
|
|
||||||
if (PlayState.isStoryMode)
|
if (PlayState.isStoryMode)
|
||||||
|
|
@ -122,41 +164,74 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||||
FlxG.switchState(new FreeplayState());
|
FlxG.switchState(new FreeplayState());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start panning the camera to BF after 12 frames.
|
|
||||||
// TODO: Should this be de-hardcoded?
|
|
||||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.animation.curAnim.curFrame == 12)
|
|
||||||
{
|
|
||||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
|
||||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameOverMusic.playing)
|
if (gameOverMusic.playing)
|
||||||
{
|
{
|
||||||
|
// Match the conductor to the music.
|
||||||
|
// This enables the stepHit and beatHit events.
|
||||||
Conductor.songPosition = gameOverMusic.time;
|
Conductor.songPosition = gameOverMusic.time;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Music hasn't started yet.
|
||||||
switch (PlayState.storyWeek)
|
switch (PlayState.storyWeek)
|
||||||
{
|
{
|
||||||
|
// TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
|
||||||
|
// This will simplify the class and make it easier for mods to add death quotes.
|
||||||
case 7:
|
case 7:
|
||||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
||||||
{
|
{
|
||||||
playingJeffQuote = true;
|
playingJeffQuote = true;
|
||||||
playJeffQuote();
|
playJeffQuote();
|
||||||
|
// Start music at lower volume
|
||||||
startDeathMusic(0.2);
|
startDeathMusic(0.2, false);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
// Start music at normal volume once the initial death animation finishes.
|
||||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||||
{
|
{
|
||||||
startDeathMusic();
|
startDeathMusic(1.0, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch the onUpdate event.
|
||||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do behavior which occurs when you confirm and move to restart the level.
|
||||||
|
*/
|
||||||
|
function confirmDeath():Void
|
||||||
|
{
|
||||||
|
if (!isEnding)
|
||||||
|
{
|
||||||
|
isEnding = true;
|
||||||
|
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||||
|
|
||||||
|
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
|
||||||
|
|
||||||
|
// After the animation finishes...
|
||||||
|
new FlxTimer().start(0.7, function(tmr:FlxTimer)
|
||||||
|
{
|
||||||
|
// ...fade out the graphics. Then after that happens...
|
||||||
|
FlxG.camera.fade(FlxColor.BLACK, 2, false, function()
|
||||||
|
{
|
||||||
|
// ...close the GameOverSubstate.
|
||||||
|
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
||||||
|
PlayState.needsReset = true;
|
||||||
|
|
||||||
|
// Readd Boyfriend to the stage.
|
||||||
|
boyfriend.isDead = false;
|
||||||
|
remove(boyfriend);
|
||||||
|
PlayState.instance.currentStage.addCharacter(boyfriend, BF);
|
||||||
|
|
||||||
|
// Close the substate.
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override function dispatchEvent(event:ScriptEvent)
|
override function dispatchEvent(event:ScriptEvent)
|
||||||
{
|
{
|
||||||
super.dispatchEvent(event);
|
super.dispatchEvent(event);
|
||||||
|
|
@ -168,17 +243,16 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||||
* Starts the death music at the appropriate volume.
|
* Starts the death music at the appropriate volume.
|
||||||
* @param startingVolume
|
* @param startingVolume
|
||||||
*/
|
*/
|
||||||
function startDeathMusic(?startingVolume:Float = 1):Void
|
function startDeathMusic(?startingVolume:Float = 1, ?force:Bool = false):Void
|
||||||
{
|
{
|
||||||
if (!isEnding)
|
var musicPath = Paths.music('gameOver' + musicSuffix);
|
||||||
|
if (isEnding)
|
||||||
{
|
{
|
||||||
gameOverMusic.loadEmbedded(Paths.music('gameOver' + musicVariant));
|
musicPath = Paths.music('gameOverEnd' + musicSuffix);
|
||||||
gameOverMusic.volume = startingVolume;
|
|
||||||
gameOverMusic.play();
|
|
||||||
}
|
}
|
||||||
else
|
if (!gameOverMusic.playing || force)
|
||||||
{
|
{
|
||||||
gameOverMusic.loadEmbedded(Paths.music('gameOverEnd' + musicVariant));
|
gameOverMusic.loadEmbedded(musicPath);
|
||||||
gameOverMusic.volume = startingVolume;
|
gameOverMusic.volume = startingVolume;
|
||||||
gameOverMusic.play();
|
gameOverMusic.play();
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +264,7 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||||
*/
|
*/
|
||||||
function playBlueBalledSFX()
|
function playBlueBalledSFX()
|
||||||
{
|
{
|
||||||
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + musicVariant));
|
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + blueBallSuffix));
|
||||||
}
|
}
|
||||||
|
|
||||||
var playingJeffQuote:Bool = false;
|
var playingJeffQuote:Bool = false;
|
||||||
|
|
@ -215,38 +289,4 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Do behavior which occurs when you confirm and move to restart the level.
|
|
||||||
*/
|
|
||||||
function confirmDeath():Void
|
|
||||||
{
|
|
||||||
if (!isEnding)
|
|
||||||
{
|
|
||||||
isEnding = true;
|
|
||||||
startDeathMusic(); // isEnding changes this function's behavior.
|
|
||||||
|
|
||||||
boyfriend.playAnimation('deathConfirm', true);
|
|
||||||
|
|
||||||
// After the animation finishes...
|
|
||||||
new FlxTimer().start(0.7, function(tmr:FlxTimer)
|
|
||||||
{
|
|
||||||
// ...fade out the graphics. Then after that happens...
|
|
||||||
FlxG.camera.fade(FlxColor.BLACK, 2, false, function()
|
|
||||||
{
|
|
||||||
// ...close the GameOverSubstate.
|
|
||||||
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
|
||||||
PlayState.needsReset = true;
|
|
||||||
|
|
||||||
// Readd Boyfriend to the stage.
|
|
||||||
boyfriend.isDead = false;
|
|
||||||
remove(boyfriend);
|
|
||||||
PlayState.instance.currentStage.addCharacter(boyfriend, BF);
|
|
||||||
|
|
||||||
// Close the substate.
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ class HealthIcon extends FlxSprite
|
||||||
/**
|
/**
|
||||||
* Since the `scale` of the sprite dynamically changes over time,
|
* Since the `scale` of the sprite dynamically changes over time,
|
||||||
* this value allows you to set a relative scale for the icon.
|
* this value allows you to set a relative scale for the icon.
|
||||||
* @default 1x scale
|
* @default 1x scale = 150px width and height.
|
||||||
*/
|
*/
|
||||||
public var size:FlxPoint = new FlxPoint(1, 1);
|
public var size:FlxPoint = new FlxPoint(1, 1);
|
||||||
|
|
||||||
|
|
@ -87,13 +87,13 @@ class HealthIcon extends FlxSprite
|
||||||
* The size of a non-pixel icon when using the legacy format.
|
* The size of a non-pixel icon when using the legacy format.
|
||||||
* Remember, modern icons can be any size.
|
* Remember, modern icons can be any size.
|
||||||
*/
|
*/
|
||||||
static final LEGACY_ICON_SIZE = 150;
|
public static final HEALTH_ICON_SIZE = 150;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The size of a pixel icon when using the legacy format.
|
* The size of a pixel icon when using the legacy format.
|
||||||
* Remember, modern icons can be any size.
|
* Remember, modern icons can be any size.
|
||||||
*/
|
*/
|
||||||
static final LEGACY_PIXEL_SIZE = 32;
|
static final PIXEL_ICON_SIZE = 32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shitty hardcoded value for a specific positioning!!!
|
* shitty hardcoded value for a specific positioning!!!
|
||||||
|
|
@ -110,8 +110,6 @@ class HealthIcon extends FlxSprite
|
||||||
|
|
||||||
this.antialiasing = !isPixel;
|
this.antialiasing = !isPixel;
|
||||||
|
|
||||||
this.flipX = playerId == 0;
|
|
||||||
|
|
||||||
initTargetSize();
|
initTargetSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,11 +145,9 @@ class HealthIcon extends FlxSprite
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
if (PlayState.instance == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Auto-update the state of the icon based on the player's health.
|
// Auto-update the state of the icon based on the player's health.
|
||||||
if (autoUpdate)
|
// Make sure this is false if the health icon is not being used in the PlayState.
|
||||||
|
if (autoUpdate && PlayState.instance != null)
|
||||||
{
|
{
|
||||||
switch (playerId)
|
switch (playerId)
|
||||||
{
|
{
|
||||||
|
|
@ -170,19 +166,22 @@ class HealthIcon extends FlxSprite
|
||||||
+ (PlayState.instance.healthBar.width * (FlxMath.remapToRange(PlayState.instance.healthBar.value, 0, 2, 100, 0) * 0.01))
|
+ (PlayState.instance.healthBar.width * (FlxMath.remapToRange(PlayState.instance.healthBar.value, 0, 2, 100, 0) * 0.01))
|
||||||
- (this.width - POSITION_OFFSET);
|
- (this.width - POSITION_OFFSET);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bumpEvery != 0)
|
||||||
|
{
|
||||||
// Lerp the health icon back to its normal size,
|
// Lerp the health icon back to its normal size,
|
||||||
// while maintaining aspect ratio.
|
// while maintaining aspect ratio.
|
||||||
if (this.width > this.height)
|
if (this.width > this.height)
|
||||||
{
|
{
|
||||||
// Apply linear interpolation while accounting for frame rate.
|
// Apply linear interpolation while accounting for frame rate.
|
||||||
var targetSize = Std.int(CoolUtil.coolLerp(this.width, 150 * this.size.x, 0.15));
|
var targetSize = Std.int(CoolUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
|
||||||
|
|
||||||
setGraphicSize(targetSize, 0);
|
setGraphicSize(targetSize, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var targetSize = Std.int(CoolUtil.coolLerp(this.height, 150 * this.size.y, 0.15));
|
var targetSize = Std.int(CoolUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
|
||||||
|
|
||||||
setGraphicSize(0, targetSize);
|
setGraphicSize(0, targetSize);
|
||||||
}
|
}
|
||||||
|
|
@ -192,18 +191,20 @@ class HealthIcon extends FlxSprite
|
||||||
|
|
||||||
public function onStepHit(curStep:Int)
|
public function onStepHit(curStep:Int)
|
||||||
{
|
{
|
||||||
if (curStep % bumpEvery == 0 && isLegacyStyle)
|
if (bumpEvery != 0 && curStep % bumpEvery == 0 && isLegacyStyle)
|
||||||
{
|
{
|
||||||
// Make the health icons bump (the update function causes them to lerp back down).
|
// Make the health icons bump (the update function causes them to lerp back down).
|
||||||
if (this.width > this.height)
|
if (this.width > this.height)
|
||||||
{
|
{
|
||||||
var targetSize = Std.int(CoolUtil.coolLerp(this.width + 30, 150, 0.15));
|
var targetSize = Std.int(CoolUtil.coolLerp(this.width + HEALTH_ICON_SIZE * 0.2, HEALTH_ICON_SIZE, 0.15));
|
||||||
|
targetSize = Std.int(Math.min(targetSize, HEALTH_ICON_SIZE * 1.2));
|
||||||
|
|
||||||
setGraphicSize(targetSize, 0);
|
setGraphicSize(targetSize, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var targetSize = Std.int(CoolUtil.coolLerp(this.height + 30, 150, 0.15));
|
var targetSize = Std.int(CoolUtil.coolLerp(this.height + HEALTH_ICON_SIZE * 0.2, HEALTH_ICON_SIZE, 0.15));
|
||||||
|
targetSize = Std.int(Math.min(targetSize, HEALTH_ICON_SIZE * 1.2));
|
||||||
|
|
||||||
setGraphicSize(0, targetSize);
|
setGraphicSize(0, targetSize);
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +214,7 @@ class HealthIcon extends FlxSprite
|
||||||
|
|
||||||
inline function initTargetSize()
|
inline function initTargetSize()
|
||||||
{
|
{
|
||||||
setGraphicSize(150);
|
setGraphicSize(HEALTH_ICON_SIZE);
|
||||||
updateHitbox();
|
updateHitbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,8 +333,7 @@ class HealthIcon extends FlxSprite
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
loadGraphic(Paths.image('icons/icon-$charId'), true, isPixel ? LEGACY_PIXEL_SIZE : LEGACY_ICON_SIZE,
|
loadGraphic(Paths.image('icons/icon-$charId'), true, isPixel ? PIXEL_ICON_SIZE : HEALTH_ICON_SIZE, isPixel ? PIXEL_ICON_SIZE : HEALTH_ICON_SIZE);
|
||||||
isPixel ? LEGACY_PIXEL_SIZE : LEGACY_ICON_SIZE);
|
|
||||||
|
|
||||||
loadAnimationOld(charId);
|
loadAnimationOld(charId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
package funkin.play;
|
|
||||||
|
|
||||||
import flixel.FlxObject;
|
|
||||||
import flixel.FlxSprite;
|
|
||||||
import flixel.addons.effects.FlxTrail;
|
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
||||||
import flixel.math.FlxMath;
|
|
||||||
import flixel.util.FlxColor;
|
|
||||||
import funkin.Note.NoteData;
|
|
||||||
import funkin.audiovis.PolygonSpectogram;
|
|
||||||
|
|
||||||
class PicoFight extends MusicBeatState
|
|
||||||
{
|
|
||||||
var picoHealth:Float = 1;
|
|
||||||
var darnellHealth:Float = 1;
|
|
||||||
|
|
||||||
var pico:Fighter;
|
|
||||||
var darnell:Fighter;
|
|
||||||
var darnellGhost:Fighter;
|
|
||||||
|
|
||||||
var nextHitTmr:FlxSprite;
|
|
||||||
|
|
||||||
var funnyWave:PolygonSpectogram;
|
|
||||||
|
|
||||||
var noteQueue:Array<NoteData> = [];
|
|
||||||
var noteSpawner:FlxTypedGroup<FlxSprite>;
|
|
||||||
|
|
||||||
override function create()
|
|
||||||
{
|
|
||||||
Paths.setCurrentLevel("week8");
|
|
||||||
|
|
||||||
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height);
|
|
||||||
bg.scrollFactor.set();
|
|
||||||
add(bg);
|
|
||||||
|
|
||||||
FlxG.sound.playMusic(Paths.inst("blazin"));
|
|
||||||
|
|
||||||
SongLoad.loadFromJson('blazin', "blazin");
|
|
||||||
Conductor.bpm = SongLoad.songData.bpm;
|
|
||||||
|
|
||||||
for (dumbassSection in SongLoad.songData.noteMap['hard'])
|
|
||||||
{
|
|
||||||
for (noteStuf in dumbassSection.sectionNotes)
|
|
||||||
{
|
|
||||||
noteQueue.push(noteStuf);
|
|
||||||
trace(noteStuf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
funnyWave = new PolygonSpectogram(FlxG.sound.music, FlxColor.RED, FlxG.height);
|
|
||||||
funnyWave.x = (FlxG.width / 2);
|
|
||||||
funnyWave.realtimeVisLenght = 0.6;
|
|
||||||
add(funnyWave);
|
|
||||||
|
|
||||||
noteSpawner = new FlxTypedGroup<FlxSprite>();
|
|
||||||
add(noteSpawner);
|
|
||||||
|
|
||||||
makeNotes();
|
|
||||||
|
|
||||||
nextHitTmr = new FlxSprite((FlxG.width / 2) - 5).makeGraphic(10, FlxG.height, FlxColor.BLACK);
|
|
||||||
add(nextHitTmr);
|
|
||||||
|
|
||||||
var trailShit:FlxTrail = new FlxTrail(nextHitTmr);
|
|
||||||
add(trailShit);
|
|
||||||
|
|
||||||
pico = new Fighter(0, 300, "pico-fighter");
|
|
||||||
add(pico);
|
|
||||||
|
|
||||||
darnell = new Fighter(0, 300, "darnell-fighter");
|
|
||||||
add(darnell);
|
|
||||||
|
|
||||||
darnellGhost = new Fighter(0, 300, "darnell-fighter");
|
|
||||||
darnellGhost.alpha = 0.5;
|
|
||||||
add(darnellGhost);
|
|
||||||
|
|
||||||
mid = (FlxG.width / 2) - (pico.width / 2);
|
|
||||||
resetPositions();
|
|
||||||
|
|
||||||
// fuk u, hardcoded bullshit bitch
|
|
||||||
|
|
||||||
super.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeNotes()
|
|
||||||
{
|
|
||||||
for (notes in noteQueue)
|
|
||||||
{
|
|
||||||
if (notes.strumTime < Conductor.songPosition + (Conductor.crochet * 4))
|
|
||||||
{
|
|
||||||
spawnNote(notes);
|
|
||||||
spawnNote(notes, FlxObject.RIGHT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function spawnNote(note:NoteData, facing:Int = FlxObject.LEFT)
|
|
||||||
{
|
|
||||||
var spr:FlxSprite = new FlxSprite(0, (FlxG.height / 2) - 60).makeGraphic(10, 120, Note.codeColors[note.noteData]);
|
|
||||||
spr.ID = Std.int(note.strumTime); // using ID as strum, lol!
|
|
||||||
spr.facing = facing;
|
|
||||||
noteSpawner.add(spr);
|
|
||||||
}
|
|
||||||
|
|
||||||
var mid:Float = (FlxG.width * 0.5) - 200;
|
|
||||||
|
|
||||||
function resetPositions()
|
|
||||||
{
|
|
||||||
resetPicoPos();
|
|
||||||
resetDarnell();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetPicoPos()
|
|
||||||
{
|
|
||||||
pico.x = mid + pico.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetDarnell()
|
|
||||||
{
|
|
||||||
darnell.x = mid - darnell.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
var prevNoteHit:Float = 0;
|
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
|
||||||
{
|
|
||||||
darnellGhost.x = darnell.x;
|
|
||||||
|
|
||||||
Conductor.songPosition = FlxG.sound.music.time;
|
|
||||||
|
|
||||||
funnyWave.thickness = CoolUtil.coolLerp(funnyWave.thickness, 2, 0.5);
|
|
||||||
funnyWave.waveAmplitude = Std.int(CoolUtil.coolLerp(funnyWave.waveAmplitude, 100, 0.1));
|
|
||||||
funnyWave.realtimeVisLenght = CoolUtil.coolLerp(funnyWave.realtimeVisLenght, 0.6, 0.1);
|
|
||||||
|
|
||||||
noteSpawner.forEachAlive((nt:FlxSprite) ->
|
|
||||||
{
|
|
||||||
// i forget how to make rhythm games
|
|
||||||
nt.x = (nt.ID - Conductor.songPosition) * (nt.ID / (Conductor.songPosition * 0.8));
|
|
||||||
|
|
||||||
if (nt.facing == FlxObject.RIGHT)
|
|
||||||
{
|
|
||||||
nt.x = FlxMath.remapToRange(nt.x, 0, FlxG.width, FlxG.width, 0);
|
|
||||||
nt.x -= FlxG.width / 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nt.x += FlxG.width / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
nt.scale.x = FlxMath.remapToRange(nt.ID - Conductor.songPosition, 0, Conductor.crochet * 3, 0.2, 2);
|
|
||||||
nt.scale.y = FlxMath.remapToRange((nt.ID - Conductor.songPosition), 0, Conductor.crochet * 2, 6, 0.2);
|
|
||||||
|
|
||||||
if (nt.ID < Conductor.songPosition)
|
|
||||||
nt.kill();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (noteQueue.length > 0)
|
|
||||||
{
|
|
||||||
nextHitTmr.scale.y = FlxMath.remapToRange(Conductor.songPosition, prevNoteHit, noteQueue[0].strumTime, 1, 0);
|
|
||||||
|
|
||||||
darnellGhost.scale.x = darnellGhost.scale.y = FlxMath.remapToRange(Conductor.songPosition, prevNoteHit, noteQueue[0].strumTime, 2, 1);
|
|
||||||
darnellGhost.alpha = FlxMath.remapToRange(Conductor.songPosition, prevNoteHit, noteQueue[0].strumTime, 0.3, 0.1);
|
|
||||||
|
|
||||||
if (Conductor.songPosition >= noteQueue[0].strumTime)
|
|
||||||
{
|
|
||||||
prevNoteHit = noteQueue[0].strumTime;
|
|
||||||
|
|
||||||
noteQueue.shift();
|
|
||||||
|
|
||||||
darnell.doSomething(darnellGhost.curAction);
|
|
||||||
|
|
||||||
darnellGhost.doSomething();
|
|
||||||
darnellGhost.animation.curAnim.frameRate = 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controls.NOTE_LEFT_P)
|
|
||||||
{
|
|
||||||
pico.punch();
|
|
||||||
}
|
|
||||||
if (controls.NOTE_LEFT_R)
|
|
||||||
pico.playAnimation('idle');
|
|
||||||
|
|
||||||
super.update(elapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
override function stepHit():Bool
|
|
||||||
{
|
|
||||||
return super.stepHit();
|
|
||||||
}
|
|
||||||
|
|
||||||
override function beatHit():Bool
|
|
||||||
{
|
|
||||||
// super.beatHit() returns false if a module cancelled the event.
|
|
||||||
if (!super.beatHit())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
funnyWave.thickness = 10;
|
|
||||||
funnyWave.waveAmplitude = 300;
|
|
||||||
funnyWave.realtimeVisLenght = 0.1;
|
|
||||||
|
|
||||||
picoHealth += 1;
|
|
||||||
|
|
||||||
makeNotes();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +0,0 @@
|
||||||
package funkin.play;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A static class which holds any functions related to scoring.
|
|
||||||
*/
|
|
||||||
class Scoring {}
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
package funkin.play;
|
package funkin.play;
|
||||||
|
|
||||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import funkin.Note.NoteColor;
|
import funkin.noteStuff.NoteBasic.NoteColor;
|
||||||
import funkin.Note.NoteDir;
|
import funkin.noteStuff.NoteBasic.NoteDir;
|
||||||
import funkin.Note.NoteType;
|
import funkin.noteStuff.NoteBasic.NoteType;
|
||||||
import funkin.ui.PreferencesMenu;
|
import funkin.ui.PreferencesMenu;
|
||||||
import funkin.util.Constants;
|
import funkin.util.Constants;
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ class Strumline extends FlxTypedSpriteGroup<StrumlineArrow>
|
||||||
return getArrow(value.int);
|
return getArrow(value.int);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getArrowByNoteColor(value:NoteColor):StrumlineArrow
|
public inline function getArrowByNoteColor(value:funkin.noteStuff.NoteBasic.NoteColor):StrumlineArrow
|
||||||
{
|
{
|
||||||
return getArrow(value.int);
|
return getArrow(value.int);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ class VanillaCutscenes
|
||||||
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, (Conductor.crochet / 1000) * 5, {ease: FlxEase.quadInOut});
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
PlayState.instance.startCountdown();
|
PlayState.instance.startCountdown();
|
||||||
@:privateAccess
|
// @:privateAccess
|
||||||
PlayState.instance.controlCamera();
|
// PlayState.instance.controlCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function playHorrorStartCutscene()
|
public static function playHorrorStartCutscene()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package funkin.play.character;
|
package funkin.play.character;
|
||||||
|
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import funkin.Note.NoteDir;
|
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.noteStuff.NoteBasic.NoteDir;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.stage.Bopper;
|
import funkin.play.stage.Bopper;
|
||||||
|
|
||||||
|
|
@ -34,6 +34,16 @@ class BaseCharacter extends Bopper
|
||||||
public var isDead:Bool = false;
|
public var isDead:Bool = false;
|
||||||
public var debugMode:Bool = false;
|
public var debugMode:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This character plays a given animation when hitting these specific combo numbers.
|
||||||
|
*/
|
||||||
|
public var comboNoteCounts(default, null):Array<Int>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This character plays a given animation when dropping combos larger than these numbers.
|
||||||
|
*/
|
||||||
|
public var dropNoteCounts(default, null):Array<Int>;
|
||||||
|
|
||||||
final _data:CharacterData;
|
final _data:CharacterData;
|
||||||
final singTimeCrochet:Float;
|
final singTimeCrochet:Float;
|
||||||
|
|
||||||
|
|
@ -82,7 +92,7 @@ class BaseCharacter extends Bopper
|
||||||
override function set_animOffsets(value:Array<Float>)
|
override function set_animOffsets(value:Array<Float>)
|
||||||
{
|
{
|
||||||
if (animOffsets == null)
|
if (animOffsets == null)
|
||||||
animOffsets = [0, 0];
|
value = [0, 0];
|
||||||
if (animOffsets == value)
|
if (animOffsets == value)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
|
|
@ -147,6 +157,60 @@ class BaseCharacter extends Bopper
|
||||||
shouldBop = false;
|
shouldBop = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of flipX from the character data.
|
||||||
|
* `!getFlipX()` is the direction Boyfriend should face.
|
||||||
|
*/
|
||||||
|
public function getDataFlipX():Bool
|
||||||
|
{
|
||||||
|
return _data.flipX;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCountAnimations(prefix:String):Array<Int>
|
||||||
|
{
|
||||||
|
var animNames:Array<String> = this.animation.getNameList();
|
||||||
|
|
||||||
|
var result:Array<Int> = [];
|
||||||
|
|
||||||
|
for (anim in animNames)
|
||||||
|
{
|
||||||
|
if (anim.startsWith(prefix))
|
||||||
|
{
|
||||||
|
var comboNum:Null<Int> = Std.parseInt(anim.substring(prefix.length));
|
||||||
|
if (comboNum != null)
|
||||||
|
{
|
||||||
|
result.push(comboNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort numerically.
|
||||||
|
result.sort((a, b) -> a - b);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the character so it can be used at the start of the level.
|
||||||
|
* Call this when restarting the level.
|
||||||
|
*/
|
||||||
|
public function resetCharacter(resetCamera:Bool = true):Void
|
||||||
|
{
|
||||||
|
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
|
||||||
|
this.animOffsets = [0, 0];
|
||||||
|
|
||||||
|
// Now we can set the x and y to be their original values without having to account for animOffsets.
|
||||||
|
this.resetPosition();
|
||||||
|
|
||||||
|
// Make sure we are playing the idle animation (to reapply animOffsets)...
|
||||||
|
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||||
|
// ...then update the hitbox so that this.width and this.height are correct.
|
||||||
|
this.updateHitbox();
|
||||||
|
|
||||||
|
// Reset the camera focus point while we're at it.
|
||||||
|
if (resetCamera)
|
||||||
|
this.resetCameraFocusPoint();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sprite scale to the appropriate value.
|
* Set the sprite scale to the appropriate value.
|
||||||
* @param scale
|
* @param scale
|
||||||
|
|
@ -177,28 +241,59 @@ class BaseCharacter extends Bopper
|
||||||
|
|
||||||
override function onCreate(event:ScriptEvent):Void
|
override function onCreate(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
// Camera focus point
|
// Make sure we are playing the idle animation...
|
||||||
|
this.dance();
|
||||||
|
// ...then update the hitbox so that this.width and this.height are correct.
|
||||||
|
this.updateHitbox();
|
||||||
|
// Without the above code, width and height (and therefore character position)
|
||||||
|
// will be based on the first animation in the sheet rather than the default animation.
|
||||||
|
|
||||||
|
this.resetCameraFocusPoint();
|
||||||
|
|
||||||
|
// Child class should have created animations by now,
|
||||||
|
// so we can query which ones are available.
|
||||||
|
this.comboNoteCounts = findCountAnimations('combo'); // example: combo50
|
||||||
|
this.dropNoteCounts = findCountAnimations('drop'); // example: drop50
|
||||||
|
// trace('${this.animation.getNameList()}');
|
||||||
|
// trace('Combo note counts: ' + this.comboNoteCounts);
|
||||||
|
// trace('Drop note counts: ' + this.dropNoteCounts);
|
||||||
|
|
||||||
|
super.onCreate(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCameraFocusPoint():Void
|
||||||
|
{
|
||||||
|
// Calculate the camera focus point
|
||||||
var charCenterX = this.x + this.width / 2;
|
var charCenterX = this.x + this.width / 2;
|
||||||
var charCenterY = this.y + this.height / 2;
|
var charCenterY = this.y + this.height / 2;
|
||||||
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
|
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
|
||||||
super.onCreate(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function initHealthIcon(isOpponent:Bool):Void
|
public function initHealthIcon(isOpponent:Bool):Void
|
||||||
{
|
{
|
||||||
if (!isOpponent)
|
if (!isOpponent)
|
||||||
{
|
{
|
||||||
|
if (PlayState.instance.iconP1 == null)
|
||||||
|
{
|
||||||
|
trace('[WARN] Player 1 health icon not found!');
|
||||||
|
}
|
||||||
PlayState.instance.iconP1.characterId = _data.healthIcon.id;
|
PlayState.instance.iconP1.characterId = _data.healthIcon.id;
|
||||||
PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||||
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
|
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
|
||||||
PlayState.instance.iconP1.offset.y = _data.healthIcon.offsets[1];
|
PlayState.instance.iconP1.offset.y = _data.healthIcon.offsets[1];
|
||||||
|
PlayState.instance.iconP1.flipX = !_data.healthIcon.flipX;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (PlayState.instance.iconP2 == null)
|
||||||
|
{
|
||||||
|
trace('[WARN] Player 2 health icon not found!');
|
||||||
|
}
|
||||||
PlayState.instance.iconP2.characterId = _data.healthIcon.id;
|
PlayState.instance.iconP2.characterId = _data.healthIcon.id;
|
||||||
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||||
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];
|
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];
|
||||||
PlayState.instance.iconP2.offset.y = _data.healthIcon.offsets[1];
|
PlayState.instance.iconP2.offset.y = _data.healthIcon.offsets[1];
|
||||||
|
PlayState.instance.iconP1.flipX = _data.healthIcon.flipX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,22 +310,28 @@ class BaseCharacter extends Bopper
|
||||||
if (isDead)
|
if (isDead)
|
||||||
{
|
{
|
||||||
playDeathAnimation();
|
playDeathAnimation();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAnimation('idle-end') && getCurrentAnimation() == "idle" && isAnimationFinished())
|
if (hasAnimation('idle-hold') && getCurrentAnimation() == "idle" && isAnimationFinished())
|
||||||
playAnimation('idle-end');
|
playAnimation('idle-hold');
|
||||||
if (hasAnimation('singLEFT-end') && getCurrentAnimation() == "singLEFT" && isAnimationFinished())
|
if (hasAnimation('singLEFT-hold') && getCurrentAnimation() == "singLEFT" && isAnimationFinished())
|
||||||
playAnimation('singLEFT-end');
|
playAnimation('singLEFT-hold');
|
||||||
if (hasAnimation('singDOWN-end') && getCurrentAnimation() == "singDOWN" && isAnimationFinished())
|
if (hasAnimation('singDOWN-hold') && getCurrentAnimation() == "singDOWN" && isAnimationFinished())
|
||||||
playAnimation('singDOWN-end');
|
playAnimation('singDOWN-hold');
|
||||||
if (hasAnimation('singUP-end') && getCurrentAnimation() == "singUP" && isAnimationFinished())
|
if (hasAnimation('singUP-hold') && getCurrentAnimation() == "singUP" && isAnimationFinished())
|
||||||
playAnimation('singUP-end');
|
playAnimation('singUP-hold');
|
||||||
if (hasAnimation('singRIGHT-end') && getCurrentAnimation() == "singRIGHT" && isAnimationFinished())
|
if (hasAnimation('singRIGHT-hold') && getCurrentAnimation() == "singRIGHT" && isAnimationFinished())
|
||||||
playAnimation('singRIGHT-end');
|
playAnimation('singRIGHT-hold');
|
||||||
|
|
||||||
// Handle character note hold time.
|
// Handle character note hold time.
|
||||||
if (getCurrentAnimation().startsWith("sing"))
|
if (getCurrentAnimation().startsWith("sing"))
|
||||||
{
|
{
|
||||||
|
// TODO: Rework this code (and all character animations ugh)
|
||||||
|
// such that the hold time is handled by padding frames,
|
||||||
|
// and reverting to the idle animation is done when `isAnimationFinished()`.
|
||||||
|
// This lets you add frames to the end of the sing animation to ease back into the idle!
|
||||||
|
|
||||||
holdTimer += event.elapsed;
|
holdTimer += event.elapsed;
|
||||||
var singTimeMs:Float = singTimeCrochet * (Conductor.crochet * 0.001); // x beats, to ms.
|
var singTimeMs:Float = singTimeCrochet * (Conductor.crochet * 0.001); // x beats, to ms.
|
||||||
|
|
||||||
|
|
@ -244,7 +345,6 @@ class BaseCharacter extends Bopper
|
||||||
FlxG.watch.addQuick('singTimeMs-${characterId}', singTimeMs);
|
FlxG.watch.addQuick('singTimeMs-${characterId}', singTimeMs);
|
||||||
if (holdTimer > singTimeMs && shouldStopSinging)
|
if (holdTimer > singTimeMs && shouldStopSinging)
|
||||||
{
|
{
|
||||||
trace(getCurrentAnimation());
|
|
||||||
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation');
|
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation');
|
||||||
holdTimer = 0;
|
holdTimer = 0;
|
||||||
dance(true);
|
dance(true);
|
||||||
|
|
@ -266,7 +366,7 @@ class BaseCharacter extends Bopper
|
||||||
{
|
{
|
||||||
if (force || (getCurrentAnimation().startsWith("firstDeath") && isAnimationFinished()))
|
if (force || (getCurrentAnimation().startsWith("firstDeath") && isAnimationFinished()))
|
||||||
{
|
{
|
||||||
playAnimation("deathLoop");
|
playAnimation("deathLoop" + GameOverSubstate.animationSuffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,6 +376,9 @@ class BaseCharacter extends Bopper
|
||||||
if (debugMode)
|
if (debugMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (isDead)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!force)
|
if (!force)
|
||||||
{
|
{
|
||||||
if (getCurrentAnimation().startsWith("sing"))
|
if (getCurrentAnimation().startsWith("sing"))
|
||||||
|
|
@ -365,14 +468,20 @@ class BaseCharacter extends Bopper
|
||||||
if (event.note.mustPress && characterType == BF)
|
if (event.note.mustPress && characterType == BF)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
this.playSingAnimation(event.note.data.dir, false, event.note.data.altNote);
|
this.playSingAnimation(event.note.data.dir, false);
|
||||||
holdTimer = 0;
|
|
||||||
}
|
}
|
||||||
else if (!event.note.mustPress && characterType == DAD)
|
else if (!event.note.mustPress && characterType == DAD)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
this.playSingAnimation(event.note.data.dir, false, event.note.data.altNote);
|
this.playSingAnimation(event.note.data.dir, false);
|
||||||
holdTimer = 0;
|
}
|
||||||
|
else if (characterType == GF)
|
||||||
|
{
|
||||||
|
if (event.note.mustPress && this.comboNoteCounts.contains(event.comboCount))
|
||||||
|
{
|
||||||
|
trace('Playing GF combo animation: combo${event.comboCount}');
|
||||||
|
this.playAnimation('combo${event.comboCount}', true, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,12 +496,33 @@ class BaseCharacter extends Bopper
|
||||||
if (event.note.mustPress && characterType == BF)
|
if (event.note.mustPress && characterType == BF)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
this.playSingAnimation(event.note.data.dir, true, event.note.data.altNote);
|
this.playSingAnimation(event.note.data.dir, true);
|
||||||
}
|
}
|
||||||
else if (!event.note.mustPress && characterType == DAD)
|
else if (!event.note.mustPress && characterType == DAD)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
this.playSingAnimation(event.note.data.dir, true, event.note.data.altNote);
|
this.playSingAnimation(event.note.data.dir, true);
|
||||||
|
}
|
||||||
|
else if (event.note.mustPress && characterType == GF)
|
||||||
|
{
|
||||||
|
var dropAnim = '';
|
||||||
|
|
||||||
|
// Choose the combo drop anim to play.
|
||||||
|
// If there are several (for example, drop10 and drop50) the highest one will be used.
|
||||||
|
// If the combo count is too low, no animation will be played.
|
||||||
|
for (count in dropNoteCounts)
|
||||||
|
{
|
||||||
|
if (event.comboCount >= count)
|
||||||
|
{
|
||||||
|
dropAnim = 'drop${count}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropAnim != '')
|
||||||
|
{
|
||||||
|
trace('Playing GF combo drop animation: ${dropAnim}');
|
||||||
|
this.playAnimation(dropAnim, true, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,9 +541,9 @@ class BaseCharacter extends Bopper
|
||||||
|
|
||||||
if (characterType == BF)
|
if (characterType == BF)
|
||||||
{
|
{
|
||||||
trace('Playing ghost miss animation...');
|
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
this.playSingAnimation(event.dir, true, null);
|
// trace('Playing ghost miss animation...');
|
||||||
|
this.playSingAnimation(event.dir, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,8 +569,40 @@ class BaseCharacter extends Bopper
|
||||||
|
|
||||||
enum CharacterType
|
enum CharacterType
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The BF character has the following behaviors.
|
||||||
|
* - At idle, dances with `danceLeft` and `danceRight` if available, or `idle` if not.
|
||||||
|
* - When the player hits a note, plays the appropriate `singDIR` animation until BF is done singing.
|
||||||
|
* - If there is a `singDIR-end` animation, the `singDIR` animation will play once before looping the `singDIR-end` animation until BF is done singing.
|
||||||
|
* - If the player misses or hits a ghost note, plays the appropriate `singDIR-miss` animation until BF is done singing.
|
||||||
|
*/
|
||||||
BF;
|
BF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DAD character has the following behaviors.
|
||||||
|
* - At idle, dances with `danceLeft` and `danceRight` if available, or `idle` if not.
|
||||||
|
* - When the CPU hits a note, plays the appropriate `singDIR` animation until DAD is done singing.
|
||||||
|
* - If there is a `singDIR-end` animation, the `singDIR` animation will play once before looping the `singDIR-end` animation until DAD is done singing.
|
||||||
|
* - When the CPU misses a note (NOTE: This only happens via script, not by default), plays the appropriate `singDIR-miss` animation until DAD is done singing.
|
||||||
|
*/
|
||||||
DAD;
|
DAD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GF character has the following behaviors.
|
||||||
|
* - At idle, dances with `danceLeft` and `danceRight` if available, or `idle` if not.
|
||||||
|
* - If available, `combo###` animations will play when certain combo counts are reached.
|
||||||
|
* - For example, `combo50` will play when the player hits 50 notes in a row.
|
||||||
|
* - Multiple combo animations can be provided for different thresholds.
|
||||||
|
* - If available, `drop###` animations will play when combos are dropped above certain thresholds.
|
||||||
|
* - For example, `drop10` will play when the player drops a combo larger than 10.
|
||||||
|
* - Multiple drop animations can be provided for different thresholds (i.e. dropping larger combos).
|
||||||
|
* - No drop animation will play if one isn't applicable (i.e. if the combo count is too low).
|
||||||
|
*/
|
||||||
GF;
|
GF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OTHER character will only perform the `danceLeft`/`danceRight` or `idle` animation by default, depending on what's available.
|
||||||
|
* Additional behaviors can be performed via scripts.
|
||||||
|
*/
|
||||||
OTHER;
|
OTHER;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class CharacterDataParser
|
||||||
trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
|
trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
|
||||||
for (charCls in scriptedCharClassNames3)
|
for (charCls in scriptedCharClassNames3)
|
||||||
{
|
{
|
||||||
var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID);
|
var character = ScriptedMultiSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||||
if (character == null)
|
if (character == null)
|
||||||
{
|
{
|
||||||
trace(' Failed to instantiate scripted character: ${charCls}');
|
trace(' Failed to instantiate scripted character: ${charCls}');
|
||||||
|
|
@ -223,6 +223,11 @@ class CharacterDataParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function listCharacterIds():Array<String>
|
||||||
|
{
|
||||||
|
return [for (x in characterCache.keys()) x];
|
||||||
|
}
|
||||||
|
|
||||||
static function clearCharacterCache():Void
|
static function clearCharacterCache():Void
|
||||||
{
|
{
|
||||||
if (characterCache != null)
|
if (characterCache != null)
|
||||||
|
|
@ -362,6 +367,7 @@ class CharacterDataParser
|
||||||
input.healthIcon = {
|
input.healthIcon = {
|
||||||
id: null,
|
id: null,
|
||||||
scale: null,
|
scale: null,
|
||||||
|
flipX: null,
|
||||||
offsets: null
|
offsets: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -376,6 +382,11 @@ class CharacterDataParser
|
||||||
input.healthIcon.scale = DEFAULT_SCALE;
|
input.healthIcon.scale = DEFAULT_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.healthIcon.flipX == null)
|
||||||
|
{
|
||||||
|
input.healthIcon.flipX = DEFAULT_FLIPX;
|
||||||
|
}
|
||||||
|
|
||||||
if (input.healthIcon.offsets == null)
|
if (input.healthIcon.offsets == null)
|
||||||
{
|
{
|
||||||
input.healthIcon.offsets = DEFAULT_OFFSETS;
|
input.healthIcon.offsets = DEFAULT_OFFSETS;
|
||||||
|
|
@ -583,6 +594,12 @@ typedef HealthIconData =
|
||||||
*/
|
*/
|
||||||
var scale:Null<Float>;
|
var scale:Null<Float>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to flip the health icon horizontally.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
var flipX:Null<Bool>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The offset of the health icon, in pixels.
|
* The offset of the health icon, in pixels.
|
||||||
* @default [0, 25]
|
* @default [0, 25]
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ import funkin.util.assets.FlxAnimationUtil;
|
||||||
*
|
*
|
||||||
* BaseCharacter has game logic, SparrowCharacter has only rendering logic.
|
* BaseCharacter has game logic, SparrowCharacter has only rendering logic.
|
||||||
* KEEP THEM SEPARATE!
|
* KEEP THEM SEPARATE!
|
||||||
|
*
|
||||||
|
* TODO: Rewrite this to use a single frame collection.
|
||||||
|
* @see https://github.com/HaxeFlixel/flixel/issues/2587#issuecomment-1179620637
|
||||||
*/
|
*/
|
||||||
class MultiSparrowCharacter extends BaseCharacter
|
class MultiSparrowCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
|
|
@ -50,8 +53,6 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||||
buildSpritesheets();
|
buildSpritesheets();
|
||||||
buildAnimations();
|
buildAnimations();
|
||||||
|
|
||||||
playAnimation(_data.startingAnimation);
|
|
||||||
|
|
||||||
if (_data.isPixel)
|
if (_data.isPixel)
|
||||||
{
|
{
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
|
|
@ -124,7 +125,7 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||||
if (members.exists(assetPath))
|
if (members.exists(assetPath))
|
||||||
{
|
{
|
||||||
// Switch to a new set of sprites.
|
// Switch to a new set of sprites.
|
||||||
trace('Loading frames from asset path: ${assetPath}');
|
// trace('Loading frames from asset path: ${assetPath}');
|
||||||
this.frames = members.get(assetPath);
|
this.frames = members.get(assetPath);
|
||||||
this.activeMember = assetPath;
|
this.activeMember = assetPath;
|
||||||
this.setScale(_data.scale);
|
this.setScale(_data.scale);
|
||||||
|
|
@ -176,6 +177,11 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||||
|
|
||||||
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
||||||
{
|
{
|
||||||
|
// Make sure we ignore other animations if we're currently playing a forced one,
|
||||||
|
// unless we're forcing a new animation.
|
||||||
|
if (!this.canPlayOtherAnims && !ignoreOther)
|
||||||
|
return;
|
||||||
|
|
||||||
loadFramesByAnimName(name);
|
loadFramesByAnimName(name);
|
||||||
super.playAnimation(name, restart, ignoreOther);
|
super.playAnimation(name, restart, ignoreOther);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@ class PackerCharacter extends BaseCharacter
|
||||||
loadSpritesheet();
|
loadSpritesheet();
|
||||||
loadAnimations();
|
loadAnimations();
|
||||||
|
|
||||||
playAnimation(_data.startingAnimation);
|
|
||||||
|
|
||||||
super.onCreate(event);
|
super.onCreate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package funkin.play.character;
|
package funkin.play.character;
|
||||||
|
|
||||||
|
import funkin.play.character.MultiSparrowCharacter;
|
||||||
import funkin.play.character.PackerCharacter;
|
import funkin.play.character.PackerCharacter;
|
||||||
import funkin.play.character.SparrowCharacter;
|
import funkin.play.character.SparrowCharacter;
|
||||||
import funkin.play.character.MultiSparrowCharacter;
|
import polymod.hscript.HScriptedClass;
|
||||||
import funkin.modding.IHook;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: Making a scripted class extending BaseCharacter is not recommended.
|
* Note: Making a scripted class extending BaseCharacter is not recommended.
|
||||||
|
|
@ -11,13 +11,13 @@ import funkin.modding.IHook;
|
||||||
* and can't use one of the built-in render modes.
|
* and can't use one of the built-in render modes.
|
||||||
*/
|
*/
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedBaseCharacter extends BaseCharacter implements IHook {}
|
class ScriptedBaseCharacter extends BaseCharacter implements HScriptedClass {}
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedSparrowCharacter extends SparrowCharacter implements IHook {}
|
class ScriptedSparrowCharacter extends SparrowCharacter implements HScriptedClass {}
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements IHook {}
|
class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements HScriptedClass {}
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedPackerCharacter extends PackerCharacter implements IHook {}
|
class ScriptedPackerCharacter extends PackerCharacter implements HScriptedClass {}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ class SparrowCharacter extends BaseCharacter
|
||||||
loadSpritesheet();
|
loadSpritesheet();
|
||||||
loadAnimations();
|
loadAnimations();
|
||||||
|
|
||||||
playAnimation(_data.startingAnimation);
|
|
||||||
|
|
||||||
super.onCreate(event);
|
super.onCreate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
303
source/funkin/play/event/SongEvent.hx
Normal file
303
source/funkin/play/event/SongEvent.hx
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
package funkin.play.event;
|
||||||
|
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import funkin.play.PlayState;
|
||||||
|
import funkin.play.character.BaseCharacter;
|
||||||
|
import funkin.play.song.SongData.RawSongEventData;
|
||||||
|
import haxe.DynamicAccess;
|
||||||
|
|
||||||
|
typedef RawSongEvent =
|
||||||
|
{
|
||||||
|
> RawSongEventData,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the event has been activated or not.
|
||||||
|
*/
|
||||||
|
var a:Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:forward
|
||||||
|
abstract SongEvent(RawSongEvent)
|
||||||
|
{
|
||||||
|
public function new(time:Float, event:String, value:Dynamic = null)
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
t: time,
|
||||||
|
e: event,
|
||||||
|
v: value,
|
||||||
|
a: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public var time(get, set):Float;
|
||||||
|
|
||||||
|
public function get_time():Float
|
||||||
|
{
|
||||||
|
return this.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_time(value:Float):Float
|
||||||
|
{
|
||||||
|
return this.t = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var event(get, set):String;
|
||||||
|
|
||||||
|
public function get_event():String
|
||||||
|
{
|
||||||
|
return this.e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_event(value:String):String
|
||||||
|
{
|
||||||
|
return this.e = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var value(get, set):Dynamic;
|
||||||
|
|
||||||
|
public function get_value():Dynamic
|
||||||
|
{
|
||||||
|
return this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_value(value:Dynamic):Dynamic
|
||||||
|
{
|
||||||
|
return this.v = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getBool():Bool
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getInt():Int
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getFloat():Float
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getString():String
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getArray():Array<Dynamic>
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getMap():DynamicAccess<Dynamic>
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getBoolArray():Array<Bool>
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef SongEventCallback = SongEvent->Void;
|
||||||
|
|
||||||
|
class SongEventHandler
|
||||||
|
{
|
||||||
|
private static final eventCallbacks:Map<String, SongEventCallback> = new Map<String, SongEventCallback>();
|
||||||
|
|
||||||
|
public static function registerCallback(event:String, callback:SongEventCallback):Void
|
||||||
|
{
|
||||||
|
eventCallbacks.set(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unregisterCallback(event:String):Void
|
||||||
|
{
|
||||||
|
eventCallbacks.remove(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clearCallbacks():Void
|
||||||
|
{
|
||||||
|
eventCallbacks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register each of the event callbacks provided by the base game.
|
||||||
|
*/
|
||||||
|
public static function registerBaseEventCallbacks():Void
|
||||||
|
{
|
||||||
|
// TODO: Add a system for mods to easily add their own event callbacks.
|
||||||
|
// Should be easy as creating character or stage scripts.
|
||||||
|
registerCallback('FocusCamera', VanillaEventCallbacks.focusCamera);
|
||||||
|
registerCallback('PlayAnimation', VanillaEventCallbacks.playAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of song events and the current timestamp,
|
||||||
|
* return a list of events that should be activated.
|
||||||
|
*/
|
||||||
|
public static function queryEvents(events:Array<SongEvent>, currentTime:Float):Array<SongEvent>
|
||||||
|
{
|
||||||
|
return events.filter(function(event:SongEvent):Bool
|
||||||
|
{
|
||||||
|
// If the event is already activated, don't activate it again.
|
||||||
|
if (event.a)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the event is in the future, don't activate it.
|
||||||
|
if (event.time > currentTime)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function activateEvents(events:Array<SongEvent>):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
activateEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function activateEvent(event:SongEvent):Void
|
||||||
|
{
|
||||||
|
if (event.a)
|
||||||
|
{
|
||||||
|
trace('Event already activated: ' + event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent the event from being activated again.
|
||||||
|
event.a = true;
|
||||||
|
|
||||||
|
// Perform the action.
|
||||||
|
if (eventCallbacks.exists(event.event))
|
||||||
|
{
|
||||||
|
eventCallbacks.get(event.event)(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resetEvents(events:Array<SongEvent>):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
resetEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resetEvent(event:SongEvent):Void
|
||||||
|
{
|
||||||
|
// TODO: Add a system for mods to easily add their reset callbacks.
|
||||||
|
event.a = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VanillaEventCallbacks
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Event Name: "FocusCamera"
|
||||||
|
* Event Value: Int
|
||||||
|
* 0: Focus on the player.
|
||||||
|
* 1: Focus on the opponent.
|
||||||
|
* 2: Focus on the girlfriend.
|
||||||
|
*/
|
||||||
|
public static function focusCamera(event:SongEvent):Void
|
||||||
|
{
|
||||||
|
// Does nothing if there is no PlayState camera or stage.
|
||||||
|
if (PlayState.instance == null || PlayState.instance.currentStage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (event.getInt())
|
||||||
|
{
|
||||||
|
case 0: // Boyfriend
|
||||||
|
// Focus the camera on the player.
|
||||||
|
trace('[EVENT] Focusing camera on player.');
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x,
|
||||||
|
PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y);
|
||||||
|
case 1: // Dad
|
||||||
|
// Focus the camera on the dad.
|
||||||
|
trace('[EVENT] Focusing camera on dad.');
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getDad().cameraFocusPoint.x,
|
||||||
|
PlayState.instance.currentStage.getDad().cameraFocusPoint.y);
|
||||||
|
case 2: // Girlfriend
|
||||||
|
// Focus the camera on the girlfriend.
|
||||||
|
trace('[EVENT] Focusing camera on girlfriend.');
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x,
|
||||||
|
PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y);
|
||||||
|
default:
|
||||||
|
trace('[EVENT] Unknown camera focus: ' + event.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event Name: "playAnimation"
|
||||||
|
* Event Value: Object
|
||||||
|
* {
|
||||||
|
* target: String, // "player", "dad", "girlfriend", or <stage prop id>
|
||||||
|
* animation: String,
|
||||||
|
* force: Bool // optional
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public static function playAnimation(event:SongEvent):Void
|
||||||
|
{
|
||||||
|
// Does nothing if there is no PlayState camera or stage.
|
||||||
|
if (PlayState.instance == null || PlayState.instance.currentStage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var data:Dynamic = event.value;
|
||||||
|
|
||||||
|
var targetName:String = Reflect.field(data, 'target');
|
||||||
|
var anim:String = Reflect.field(data, 'anim');
|
||||||
|
var force:Null<Bool> = Reflect.field(data, 'force');
|
||||||
|
if (force == null)
|
||||||
|
force = false;
|
||||||
|
|
||||||
|
var target:FlxSprite = null;
|
||||||
|
|
||||||
|
switch (targetName)
|
||||||
|
{
|
||||||
|
case 'boyfriend':
|
||||||
|
trace('[EVENT] Playing animation $anim on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'bf':
|
||||||
|
trace('[EVENT] Playing animation $anim on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'player':
|
||||||
|
trace('[EVENT] Playing animation $anim on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'dad':
|
||||||
|
trace('[EVENT] Playing animation $anim on dad.');
|
||||||
|
target = PlayState.instance.currentStage.getDad();
|
||||||
|
case 'opponent':
|
||||||
|
trace('[EVENT] Playing animation $anim on dad.');
|
||||||
|
target = PlayState.instance.currentStage.getDad();
|
||||||
|
case 'girlfriend':
|
||||||
|
trace('[EVENT] Playing animation $anim on girlfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getGirlfriend();
|
||||||
|
case 'gf':
|
||||||
|
trace('[EVENT] Playing animation $anim on girlfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getGirlfriend();
|
||||||
|
default:
|
||||||
|
target = PlayState.instance.currentStage.getNamedProp(targetName);
|
||||||
|
if (target == null)
|
||||||
|
trace('[EVENT] Unknown animation target: $targetName');
|
||||||
|
else
|
||||||
|
trace('[EVENT] Fetched animation target $targetName from stage.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
if (Std.isOfType(target, BaseCharacter))
|
||||||
|
{
|
||||||
|
var targetChar:BaseCharacter = cast target;
|
||||||
|
targetChar.playAnimation(anim, force, force);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.animation.play(anim, force);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
198
source/funkin/play/scoring/Scoring.hx
Normal file
198
source/funkin/play/scoring/Scoring.hx
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
package funkin.play.scoring;
|
||||||
|
|
||||||
|
enum abstract ScoringSystem(String) {
|
||||||
|
/**
|
||||||
|
* The scoring system used in versions of the game Week 6 and older.
|
||||||
|
* Scores the player based on judgement, represented by a step function.
|
||||||
|
*/
|
||||||
|
var LEGACY;
|
||||||
|
/**
|
||||||
|
* The scoring system used in Week 7. It has tighter scoring windows than Legacy.
|
||||||
|
* Scores the player based on judgement, represented by a step function.
|
||||||
|
*/
|
||||||
|
var WEEK7;
|
||||||
|
/**
|
||||||
|
* Points Based On Timing scoring system, version 1
|
||||||
|
* Scores the player based on the offset based on timing, represented by a sigmoid function.
|
||||||
|
*/
|
||||||
|
var PBOT1;
|
||||||
|
// WIFE1
|
||||||
|
// WIFE3
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static class which holds any functions related to scoring.
|
||||||
|
*/
|
||||||
|
class Scoring {
|
||||||
|
/**
|
||||||
|
* Determine the score a note receives under a given scoring system.
|
||||||
|
* @param msTiming The difference between the note's time and when it was hit.
|
||||||
|
* @param scoringSystem The scoring system to use.
|
||||||
|
* @return The score the note receives.
|
||||||
|
*/
|
||||||
|
public static function scoreNote(msTiming:Float, scoringSystem:ScoringSystem = PBOT1) {
|
||||||
|
switch (scoringSystem) {
|
||||||
|
case LEGACY:
|
||||||
|
return scoreNote_LEGACY(msTiming);
|
||||||
|
case WEEK7:
|
||||||
|
return scoreNote_WEEK7(msTiming);
|
||||||
|
case PBOT1:
|
||||||
|
return scoreNote_PBOT1(msTiming);
|
||||||
|
default:
|
||||||
|
trace('ERROR: Unknown scoring system: ' + scoringSystem);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the judgement a note receives under a given scoring system.
|
||||||
|
* @param msTiming The difference between the note's time and when it was hit.
|
||||||
|
* @return The judgement the note receives.
|
||||||
|
*/
|
||||||
|
public static function judgeNote(msTiming:Float, scoringSystem:ScoringSystem = PBOT1):String {
|
||||||
|
switch (scoringSystem) {
|
||||||
|
case LEGACY:
|
||||||
|
return judgeNote_LEGACY(msTiming);
|
||||||
|
case WEEK7:
|
||||||
|
return judgeNote_WEEK7(msTiming);
|
||||||
|
case PBOT1:
|
||||||
|
return judgeNote_PBOT1(msTiming);
|
||||||
|
default:
|
||||||
|
trace('ERROR: Unknown scoring system: ' + scoringSystem);
|
||||||
|
return 'miss';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum score received.
|
||||||
|
*/
|
||||||
|
public static var PBOT1_MAX_SCORE = 350;
|
||||||
|
/**
|
||||||
|
* The minimum score received.
|
||||||
|
*/
|
||||||
|
public static var PBOT1_MIN_SCORE = 0;
|
||||||
|
/**
|
||||||
|
* The threshold at which a note hit is considered perfect and always given the max score.
|
||||||
|
**/
|
||||||
|
public static var PBOT1_PERFECT_THRESHOLD = 5.0; // 5ms.
|
||||||
|
/**
|
||||||
|
* The threshold at which a note hit is considered missed and always given the min score.
|
||||||
|
**/
|
||||||
|
public static var PBOT1_MISS_THRESHOLD = (10/60) * 1000; // ~166ms
|
||||||
|
|
||||||
|
// Magic numbers used to tweak the shape of the scoring function.
|
||||||
|
public static var PBOT1_SCORING_SLOPE:Float = 0.052;
|
||||||
|
public static var PBOT1_SCORING_OFFSET:Float = 80.0;
|
||||||
|
|
||||||
|
static function scoreNote_PBOT1(msTiming:Float):Int {
|
||||||
|
// Absolute value because otherwise late hits are always given the max score.
|
||||||
|
var absTiming = Math.abs(msTiming);
|
||||||
|
if (absTiming > PBOT1_MISS_THRESHOLD) {
|
||||||
|
return PBOT1_MIN_SCORE;
|
||||||
|
} else if (absTiming < PBOT1_PERFECT_THRESHOLD) {
|
||||||
|
return PBOT1_MAX_SCORE;
|
||||||
|
} else {
|
||||||
|
// Calculate the score based on the timing using a sigmoid function.
|
||||||
|
var factor:Float = 1.0 - (1.0 / (1.0 + Math.exp(-PBOT1_SCORING_SLOPE * (absTiming - PBOT1_SCORING_OFFSET))));
|
||||||
|
|
||||||
|
var score = Std.int(PBOT1_MAX_SCORE * factor);
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function judgeNote_PBOT1(msTiming:Float):String {
|
||||||
|
return judgeNote_WEEK7(msTiming);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The window of time in which a note is considered to be hit, on the Funkin Legacy scoring system.
|
||||||
|
* Currently equal to 10 frames at 60fps, or ~166ms.
|
||||||
|
*/
|
||||||
|
public static var LEGACY_HIT_WINDOW:Float = (10 / 60) * 1000; // 166.67 ms hit window (10 frames at 60fps)
|
||||||
|
/**
|
||||||
|
* The threshold at which a note is considered a "Bad" hit rather than a "Shit" hit.
|
||||||
|
* Represented as a percentage of the total hit window.
|
||||||
|
*/
|
||||||
|
public static var LEGACY_BAD_THRESHOLD:Float = 0.9;
|
||||||
|
public static var LEGACY_GOOD_THRESHOLD:Float = 0.75;
|
||||||
|
public static var LEGACY_SICK_THRESHOLD:Float = 0.2;
|
||||||
|
public static var LEGACY_SHIT_SCORE = 50;
|
||||||
|
public static var LEGACY_BAD_SCORE = 100;
|
||||||
|
public static var LEGACY_GOOD_SCORE = 200;
|
||||||
|
public static var LEGACY_SICK_SCORE = 350;
|
||||||
|
|
||||||
|
static function scoreNote_LEGACY(msTiming:Float):Int {
|
||||||
|
var absTiming = Math.abs(msTiming);
|
||||||
|
if (absTiming < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD) {
|
||||||
|
return LEGACY_SICK_SCORE;
|
||||||
|
} else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD) {
|
||||||
|
return LEGACY_GOOD_SCORE;
|
||||||
|
} else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD) {
|
||||||
|
return LEGACY_BAD_SCORE;
|
||||||
|
} else if (absTiming < LEGACY_HIT_WINDOW) {
|
||||||
|
return LEGACY_SHIT_SCORE;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function judgeNote_LEGACY(msTiming:Float):String {
|
||||||
|
var absTiming = Math.abs(msTiming);
|
||||||
|
if (absTiming < LEGACY_HIT_WINDOW * LEGACY_SICK_THRESHOLD) {
|
||||||
|
return 'sick';
|
||||||
|
} else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_GOOD_THRESHOLD) {
|
||||||
|
return 'good';
|
||||||
|
} else if (absTiming < LEGACY_HIT_WINDOW * LEGACY_BAD_THRESHOLD) {
|
||||||
|
return 'bad';
|
||||||
|
} else if (absTiming < LEGACY_HIT_WINDOW) {
|
||||||
|
return 'shit';
|
||||||
|
} else {
|
||||||
|
return 'miss';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The window of time in which a note is considered to be hit, on the Funkin Classic scoring system.
|
||||||
|
* Same as L 10 frames at 60fps, or ~166ms.
|
||||||
|
*/
|
||||||
|
public static var WEEK7_HIT_WINDOW = LEGACY_HIT_WINDOW;
|
||||||
|
public static var WEEK7_BAD_THRESHOLD = 0.8; // 80% of the hit window, or ~125ms
|
||||||
|
public static var WEEK7_GOOD_THRESHOLD = 0.55; // 55% of the hit window, or ~91ms
|
||||||
|
public static var WEEK7_SICK_THRESHOLD = 0.2; // 20% of the hit window, or ~33ms
|
||||||
|
public static var WEEK7_SHIT_SCORE = 50;
|
||||||
|
public static var WEEK7_BAD_SCORE = 100;
|
||||||
|
public static var WEEK7_GOOD_SCORE = 200;
|
||||||
|
public static var WEEK7_SICK_SCORE = 350;
|
||||||
|
|
||||||
|
static function scoreNote_WEEK7(msTiming:Float):Int {
|
||||||
|
var absTiming = Math.abs(msTiming);
|
||||||
|
if (absTiming < WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD) {
|
||||||
|
return WEEK7_SICK_SCORE;
|
||||||
|
} else if (absTiming < WEEK7_HIT_WINDOW * WEEK7_GOOD_THRESHOLD) {
|
||||||
|
return WEEK7_GOOD_SCORE;
|
||||||
|
} else if (absTiming < WEEK7_HIT_WINDOW * WEEK7_BAD_THRESHOLD) {
|
||||||
|
return WEEK7_BAD_SCORE;
|
||||||
|
} else if (absTiming < WEEK7_HIT_WINDOW) {
|
||||||
|
return WEEK7_SHIT_SCORE;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function judgeNote_WEEK7(msTiming:Float):String {
|
||||||
|
var absTiming = Math.abs(msTiming);
|
||||||
|
if (absTiming < WEEK7_HIT_WINDOW * WEEK7_SICK_THRESHOLD) {
|
||||||
|
return 'sick';
|
||||||
|
} else if (absTiming < WEEK7_HIT_WINDOW * WEEK7_GOOD_THRESHOLD) {
|
||||||
|
return 'good';
|
||||||
|
} else if (absTiming < WEEK7_HIT_WINDOW * WEEK7_BAD_THRESHOLD) {
|
||||||
|
return 'bad';
|
||||||
|
} else if (absTiming < WEEK7_HIT_WINDOW) {
|
||||||
|
return 'shit';
|
||||||
|
} else {
|
||||||
|
return 'miss';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
9
source/funkin/play/song/ScriptedSong.hx
Normal file
9
source/funkin/play/song/ScriptedSong.hx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import funkin.play.song.Song;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedSong extends Song implements HScriptedClass
|
||||||
|
{
|
||||||
|
}
|
||||||
252
source/funkin/play/song/Song.hx
Normal file
252
source/funkin/play/song/Song.hx
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import funkin.VoicesGroup;
|
||||||
|
import funkin.play.event.SongEvent;
|
||||||
|
import funkin.play.song.SongData.SongChartData;
|
||||||
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
|
import funkin.play.song.SongData.SongEventData;
|
||||||
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
import funkin.play.song.SongData.SongPlayableChar;
|
||||||
|
import funkin.play.song.SongData.SongTimeChange;
|
||||||
|
import funkin.play.song.SongData.SongTimeFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a data structure managing information about the current song.
|
||||||
|
* This structure is created when the game starts, and includes all the data
|
||||||
|
* from the `metadata.json` file.
|
||||||
|
* It also includes the chart data, but only when this is the currently loaded song.
|
||||||
|
*
|
||||||
|
* It also receives script events; scripted classes which extend this class
|
||||||
|
* can be used to perform custom gameplay behaviors only on specific songs.
|
||||||
|
*/
|
||||||
|
class Song // implements IPlayStateScriptedClass
|
||||||
|
{
|
||||||
|
public final songId:String;
|
||||||
|
|
||||||
|
final _metadata:Array<SongMetadata>;
|
||||||
|
|
||||||
|
final variations:Array<String>;
|
||||||
|
final difficulties:Map<String, SongDifficulty>;
|
||||||
|
|
||||||
|
public function new(id:String)
|
||||||
|
{
|
||||||
|
this.songId = id;
|
||||||
|
|
||||||
|
variations = [];
|
||||||
|
difficulties = new Map<String, SongDifficulty>();
|
||||||
|
|
||||||
|
_metadata = SongDataParser.parseSongMetadata(songId);
|
||||||
|
if (_metadata == null || _metadata.length == 0)
|
||||||
|
{
|
||||||
|
throw 'Could not find song data for songId: $songId';
|
||||||
|
}
|
||||||
|
|
||||||
|
populateFromMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate the song data from the provided metadata,
|
||||||
|
* including data from individual difficulties. Does not load chart data.
|
||||||
|
*/
|
||||||
|
function populateFromMetadata():Void
|
||||||
|
{
|
||||||
|
// Variations may have different artist, time format, generatedBy, etc.
|
||||||
|
for (metadata in _metadata)
|
||||||
|
{
|
||||||
|
for (diffId in metadata.playData.difficulties)
|
||||||
|
{
|
||||||
|
var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
|
||||||
|
|
||||||
|
variations.push(metadata.variation);
|
||||||
|
|
||||||
|
difficulty.songName = metadata.songName;
|
||||||
|
difficulty.songArtist = metadata.artist;
|
||||||
|
difficulty.timeFormat = metadata.timeFormat;
|
||||||
|
difficulty.divisions = metadata.divisions;
|
||||||
|
difficulty.timeChanges = metadata.timeChanges;
|
||||||
|
difficulty.loop = metadata.loop;
|
||||||
|
difficulty.generatedBy = metadata.generatedBy;
|
||||||
|
|
||||||
|
difficulty.stage = metadata.playData.stage;
|
||||||
|
// difficulty.noteSkin = metadata.playData.noteSkin;
|
||||||
|
|
||||||
|
difficulty.chars = new Map<String, SongPlayableChar>();
|
||||||
|
for (charId in metadata.playData.playableChars.keys())
|
||||||
|
{
|
||||||
|
var char = metadata.playData.playableChars.get(charId);
|
||||||
|
|
||||||
|
difficulty.chars.set(charId, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
difficulties.set(diffId, difficulty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and cache the chart for all difficulties of this song.
|
||||||
|
*/
|
||||||
|
public function cacheCharts(?force:Bool = false):Void
|
||||||
|
{
|
||||||
|
if (force)
|
||||||
|
{
|
||||||
|
clearCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
trace('Caching ${variations.length} chart files for song $songId');
|
||||||
|
for (variation in variations)
|
||||||
|
{
|
||||||
|
var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation);
|
||||||
|
var chartNotes = chartData.notes;
|
||||||
|
|
||||||
|
for (diffId in chartNotes.keys())
|
||||||
|
{
|
||||||
|
// Retrieve the cached difficulty data.
|
||||||
|
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||||
|
if (difficulty == null)
|
||||||
|
{
|
||||||
|
trace('Could not find difficulty $diffId for song $songId');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Add the chart data to the difficulty.
|
||||||
|
difficulty.notes = chartData.notes.get(diffId);
|
||||||
|
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
||||||
|
|
||||||
|
difficulty.events = chartData.events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace('Done caching charts.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
||||||
|
*/
|
||||||
|
public inline function getDifficulty(diffId:String):SongDifficulty
|
||||||
|
{
|
||||||
|
return difficulties.get(diffId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge the cached chart data for each difficulty of this song.
|
||||||
|
*/
|
||||||
|
public function clearCharts():Void
|
||||||
|
{
|
||||||
|
for (diff in difficulties)
|
||||||
|
{
|
||||||
|
diff.clearChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Song($songId)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SongDifficulty
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The parent song for this difficulty.
|
||||||
|
*/
|
||||||
|
public final song:Song;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The difficulty ID, such as `easy` or `hard`.
|
||||||
|
*/
|
||||||
|
public final difficulty:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata file that contains this difficulty.
|
||||||
|
*/
|
||||||
|
public final variation:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The note chart for this difficulty.
|
||||||
|
*/
|
||||||
|
public var notes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event chart for this difficulty.
|
||||||
|
*/
|
||||||
|
public var events:Array<SongEventData>;
|
||||||
|
|
||||||
|
public var songName:String = SongValidator.DEFAULT_SONGNAME;
|
||||||
|
public var songArtist:String = SongValidator.DEFAULT_ARTIST;
|
||||||
|
public var timeFormat:SongTimeFormat = SongValidator.DEFAULT_TIMEFORMAT;
|
||||||
|
public var divisions:Int = SongValidator.DEFAULT_DIVISIONS;
|
||||||
|
public var loop:Bool = SongValidator.DEFAULT_LOOP;
|
||||||
|
public var generatedBy:String = SongValidator.DEFAULT_GENERATEDBY;
|
||||||
|
|
||||||
|
public var timeChanges:Array<SongTimeChange> = [];
|
||||||
|
|
||||||
|
public var stage:String = SongValidator.DEFAULT_STAGE;
|
||||||
|
public var chars:Map<String, SongPlayableChar> = null;
|
||||||
|
|
||||||
|
public var scrollSpeed:Float = SongValidator.DEFAULT_SCROLLSPEED;
|
||||||
|
|
||||||
|
public function new(song:Song, diffId:String, variation:String)
|
||||||
|
{
|
||||||
|
this.song = song;
|
||||||
|
this.difficulty = diffId;
|
||||||
|
this.variation = variation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearChart():Void
|
||||||
|
{
|
||||||
|
notes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStartingBPM():Float
|
||||||
|
{
|
||||||
|
if (timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeChanges[0].bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlayableChar(id:String):SongPlayableChar
|
||||||
|
{
|
||||||
|
return chars.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlayableChars():Array<String>
|
||||||
|
{
|
||||||
|
return [for (i in chars.keys()) i];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEvents():Array<SongEvent>
|
||||||
|
{
|
||||||
|
return cast events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function cacheInst()
|
||||||
|
{
|
||||||
|
FlxG.sound.cache(Paths.inst(this.song.songId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function playInst(volume:Float = 1.0, looped:Bool = false)
|
||||||
|
{
|
||||||
|
FlxG.sound.playMusic(Paths.inst(this.song.songId), volume, looped);
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function cacheVocals()
|
||||||
|
{
|
||||||
|
FlxG.sound.cache(Paths.voices(this.song.songId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildVoiceList():Array<String>
|
||||||
|
{
|
||||||
|
// TODO: Implement.
|
||||||
|
|
||||||
|
return [""];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildVocals(charId:String = "bf"):VoicesGroup
|
||||||
|
{
|
||||||
|
var result:VoicesGroup = new VoicesGroup(this.song.songId, this.buildVoiceList());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
844
source/funkin/play/song/SongData.hx
Normal file
844
source/funkin/play/song/SongData.hx
Normal file
|
|
@ -0,0 +1,844 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import flixel.util.typeLimit.OneOfTwo;
|
||||||
|
import funkin.play.song.ScriptedSong;
|
||||||
|
import funkin.util.assets.DataAssets;
|
||||||
|
import haxe.DynamicAccess;
|
||||||
|
import haxe.Json;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
import thx.semver.Version;
|
||||||
|
|
||||||
|
using StringTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains utilities for loading and parsing stage data.
|
||||||
|
*/
|
||||||
|
class SongDataParser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A list containing all the songs available to the game.
|
||||||
|
*/
|
||||||
|
static final songCache:Map<String, Song> = new Map<String, Song>();
|
||||||
|
|
||||||
|
static final DEFAULT_SONG_ID = 'UNKNOWN';
|
||||||
|
static final SONG_DATA_PATH = 'songs/';
|
||||||
|
static final SONG_DATA_SUFFIX = '-metadata.json';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and preloads the game's song metadata and scripts when the game starts.
|
||||||
|
*
|
||||||
|
* If you want to force song metadata to be reloaded, you can just call this function again.
|
||||||
|
*/
|
||||||
|
public static function loadSongCache():Void
|
||||||
|
{
|
||||||
|
clearSongCache();
|
||||||
|
trace("[SONGDATA] Loading song cache...");
|
||||||
|
|
||||||
|
//
|
||||||
|
// SCRIPTED SONGS
|
||||||
|
//
|
||||||
|
var scriptedSongClassNames:Array<String> = ScriptedSong.listScriptClasses();
|
||||||
|
trace(' Instantiating ${scriptedSongClassNames.length} scripted songs...');
|
||||||
|
for (songCls in scriptedSongClassNames)
|
||||||
|
{
|
||||||
|
var song:Song = ScriptedSong.init(songCls, DEFAULT_SONG_ID);
|
||||||
|
if (song != null)
|
||||||
|
{
|
||||||
|
trace(' Loaded scripted song: ${song.songId}');
|
||||||
|
songCache.set(song.songId, song);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace(' Failed to instantiate scripted song class: ${songCls}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// UNSCRIPTED SONGS
|
||||||
|
//
|
||||||
|
var songIdList:Array<String> = DataAssets.listDataFilesInPath(SONG_DATA_PATH, SONG_DATA_SUFFIX).map(function(songDataPath:String):String
|
||||||
|
{
|
||||||
|
return songDataPath.split('/')[0];
|
||||||
|
});
|
||||||
|
var unscriptedSongIds:Array<String> = songIdList.filter(function(songId:String):Bool
|
||||||
|
{
|
||||||
|
return !songCache.exists(songId);
|
||||||
|
});
|
||||||
|
trace(' Instantiating ${unscriptedSongIds.length} non-scripted songs...');
|
||||||
|
for (songId in unscriptedSongIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var song = new Song(songId);
|
||||||
|
if (song != null)
|
||||||
|
{
|
||||||
|
trace(' Loaded song data: ${song.songId}');
|
||||||
|
songCache.set(song.songId, song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace(' An error occurred while loading song data: ${songId}');
|
||||||
|
trace(e);
|
||||||
|
// Assume error was already logged.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(' Successfully loaded ${Lambda.count(songCache)} stages.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a particular song from the cache.
|
||||||
|
*/
|
||||||
|
public static function fetchSong(songId:String):Null<Song>
|
||||||
|
{
|
||||||
|
if (songCache.exists(songId))
|
||||||
|
{
|
||||||
|
var song:Song = songCache.get(songId);
|
||||||
|
trace('[STAGEDATA] Successfully fetch song: ${songId}');
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[STAGEDATA] Failed to fetch song, not found in cache: ${songId}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function clearSongCache():Void
|
||||||
|
{
|
||||||
|
if (songCache != null)
|
||||||
|
{
|
||||||
|
songCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function listSongIds():Array<String>
|
||||||
|
{
|
||||||
|
return [for (x in songCache.keys()) x];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parseSongMetadata(songId:String):Array<SongMetadata>
|
||||||
|
{
|
||||||
|
var result:Array<SongMetadata> = [];
|
||||||
|
|
||||||
|
var rawJson:String = loadSongMetadataFile(songId);
|
||||||
|
var jsonData:Dynamic = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jsonData = Json.parse(rawJson);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
var songMetadata:SongMetadata = SongMigrator.migrateSongMetadata(jsonData, songId);
|
||||||
|
songMetadata = SongValidator.validateSongMetadata(songMetadata, songId);
|
||||||
|
|
||||||
|
if (songMetadata == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(songMetadata);
|
||||||
|
|
||||||
|
var variations = songMetadata.playData.songVariations;
|
||||||
|
|
||||||
|
for (variation in variations)
|
||||||
|
{
|
||||||
|
var variationRawJson:String = loadSongMetadataFile(songId, variation);
|
||||||
|
var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationRawJson, '${songId}_${variation}');
|
||||||
|
variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}_${variation}');
|
||||||
|
if (variationSongMetadata != null)
|
||||||
|
{
|
||||||
|
variationSongMetadata.variation = variation;
|
||||||
|
result.push(variationSongMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function loadSongMetadataFile(songPath:String, variation:String = ''):String
|
||||||
|
{
|
||||||
|
var songMetadataFilePath:String = (variation != '') ? Paths.json('$SONG_DATA_PATH$songPath/$songPath-metadata-$variation') : Paths.json('$SONG_DATA_PATH$songPath/$songPath-metadata');
|
||||||
|
|
||||||
|
var rawJson:String = Assets.getText(songMetadataFilePath).trim();
|
||||||
|
|
||||||
|
while (!rawJson.endsWith("}"))
|
||||||
|
{
|
||||||
|
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parseSongChartData(songId:String, variation:String = ""):SongChartData
|
||||||
|
{
|
||||||
|
var rawJson:String = loadSongChartDataFile(songId, variation);
|
||||||
|
var jsonData:Dynamic = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jsonData = Json.parse(rawJson);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
var songChartData:SongChartData = SongMigrator.migrateSongChartData(jsonData, songId);
|
||||||
|
songChartData = SongValidator.validateSongChartData(songChartData, songId);
|
||||||
|
|
||||||
|
if (songChartData == null)
|
||||||
|
{
|
||||||
|
trace('Failed to validate song chart data: ${songId}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return songChartData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function loadSongChartDataFile(songPath:String, variation:String = ''):String
|
||||||
|
{
|
||||||
|
var songChartDataFilePath:String = (variation != '') ? Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart-$variation') : Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart');
|
||||||
|
|
||||||
|
var rawJson:String = Assets.getText(songChartDataFilePath).trim();
|
||||||
|
|
||||||
|
while (!rawJson.endsWith("}"))
|
||||||
|
{
|
||||||
|
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RawSongMetadata =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A semantic versioning string for the song data format.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var version:Version;
|
||||||
|
|
||||||
|
var songName:String;
|
||||||
|
var artist:String;
|
||||||
|
var timeFormat:SongTimeFormat;
|
||||||
|
var divisions:Int;
|
||||||
|
var timeChanges:Array<SongTimeChange>;
|
||||||
|
var loop:Bool;
|
||||||
|
var playData:SongPlayData;
|
||||||
|
var generatedBy:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults to `default` or `''`. Populated later.
|
||||||
|
*/
|
||||||
|
var variation:String;
|
||||||
|
};
|
||||||
|
|
||||||
|
@:forward
|
||||||
|
abstract SongMetadata(RawSongMetadata)
|
||||||
|
{
|
||||||
|
public function new(songName:String, artist:String, variation:String = 'default')
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
version: SongMigrator.CHART_VERSION,
|
||||||
|
songName: songName,
|
||||||
|
artist: artist,
|
||||||
|
timeFormat: 'ms',
|
||||||
|
divisions: 96,
|
||||||
|
timeChanges: [new SongTimeChange(-1, 0, 100, 4, 4, [4, 4, 4, 4])],
|
||||||
|
loop: false,
|
||||||
|
playData: {
|
||||||
|
songVariations: [],
|
||||||
|
difficulties: ['normal'],
|
||||||
|
|
||||||
|
playableChars: {
|
||||||
|
bf: new SongPlayableChar('gf', 'dad'),
|
||||||
|
},
|
||||||
|
|
||||||
|
stage: 'mainStage',
|
||||||
|
noteSkin: 'Normal'
|
||||||
|
},
|
||||||
|
generatedBy: SongValidator.DEFAULT_GENERATEDBY,
|
||||||
|
variation: variation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef SongPlayData =
|
||||||
|
{
|
||||||
|
var songVariations:Array<String>;
|
||||||
|
var difficulties:Array<String>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keys are the player characters and the values give info on what opponent/GF/inst to use.
|
||||||
|
*/
|
||||||
|
var playableChars:DynamicAccess<SongPlayableChar>;
|
||||||
|
|
||||||
|
var stage:String;
|
||||||
|
var noteSkin:String;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RawSongPlayableChar =
|
||||||
|
{
|
||||||
|
var g:String;
|
||||||
|
var o:String;
|
||||||
|
var i:String;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RawSongNoteData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The timestamp of the note. The timestamp is in the format of the song's time format.
|
||||||
|
*/
|
||||||
|
var t:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for the note. Represents the index on the strumline.
|
||||||
|
* 0 = left, 1 = down, 2 = up, 3 = right
|
||||||
|
* `floor(direction / strumlineSize)` specifies which strumline the note is on.
|
||||||
|
* 0 = player, 1 = opponent, etc.
|
||||||
|
*/
|
||||||
|
var d:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the note, if applicable.
|
||||||
|
* Defaults to 0 for single notes.
|
||||||
|
*/
|
||||||
|
var l:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The kind of the note.
|
||||||
|
* This can allow the note to include information used for custom behavior.
|
||||||
|
* Defaults to blank or `"normal"`.
|
||||||
|
*/
|
||||||
|
var k:String;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract SongNoteData(RawSongNoteData)
|
||||||
|
{
|
||||||
|
public function new(time:Float, data:Int, length:Float = 0, kind:String = "")
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
t: time,
|
||||||
|
d: data,
|
||||||
|
l: length,
|
||||||
|
k: kind
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public var time(get, set):Float;
|
||||||
|
|
||||||
|
public function get_time():Float
|
||||||
|
{
|
||||||
|
return this.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_time(value:Float):Float
|
||||||
|
{
|
||||||
|
return this.t = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var stepTime(get, never):Float;
|
||||||
|
|
||||||
|
public function get_stepTime():Float
|
||||||
|
{
|
||||||
|
// TODO: Account for changes in BPM.
|
||||||
|
return this.t / Conductor.stepCrochet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw data for the note.
|
||||||
|
*/
|
||||||
|
public var data(get, set):Int;
|
||||||
|
|
||||||
|
public function get_data():Int
|
||||||
|
{
|
||||||
|
return this.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_data(value:Int):Int
|
||||||
|
{
|
||||||
|
return this.d = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The direction of the note, if applicable.
|
||||||
|
* Strips the strumline index from the data.
|
||||||
|
*
|
||||||
|
* 0 = left, 1 = down, 2 = up, 3 = right
|
||||||
|
*/
|
||||||
|
public inline function getDirection(strumlineSize:Int = 4):Int
|
||||||
|
{
|
||||||
|
return this.d % strumlineSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDirectionName(strumlineSize:Int = 4):String
|
||||||
|
{
|
||||||
|
switch (this.d % strumlineSize)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return 'Left';
|
||||||
|
case 1:
|
||||||
|
return 'Down';
|
||||||
|
case 2:
|
||||||
|
return 'Up';
|
||||||
|
case 3:
|
||||||
|
return 'Right';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strumline index of the note, if applicable.
|
||||||
|
* Strips the direction from the data.
|
||||||
|
*
|
||||||
|
* 0 = player, 1 = opponent, etc.
|
||||||
|
*/
|
||||||
|
public inline function getStrumlineIndex(strumlineSize:Int = 4):Int
|
||||||
|
{
|
||||||
|
return Math.floor(this.d / strumlineSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getMustHitNote(strumlineSize:Int = 4):Bool
|
||||||
|
{
|
||||||
|
return getStrumlineIndex(strumlineSize) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var length(get, set):Float;
|
||||||
|
|
||||||
|
public function get_length():Float
|
||||||
|
{
|
||||||
|
return this.l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_length(value:Float):Float
|
||||||
|
{
|
||||||
|
return this.l = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var kind(get, set):String;
|
||||||
|
|
||||||
|
public function get_kind():String
|
||||||
|
{
|
||||||
|
if (this.k == null || this.k == '')
|
||||||
|
return 'normal';
|
||||||
|
|
||||||
|
return this.k;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_kind(value:String):String
|
||||||
|
{
|
||||||
|
if (value == 'normal' || value == '')
|
||||||
|
value = null;
|
||||||
|
return this.k = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A == B)
|
||||||
|
public function op_equals(other:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
if (this.k == '')
|
||||||
|
if (other.kind != '' && other.kind != 'normal')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this.t == other.time && this.d == other.data && this.l == other.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A != B)
|
||||||
|
public function op_notEquals(other:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return this.t != other.time || this.d != other.data || this.l != other.length || this.k != other.kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A > B)
|
||||||
|
public function op_greaterThan(other:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return this.t > other.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A < B)
|
||||||
|
public function op_lessThan(other:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return this.t < other.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A >= B)
|
||||||
|
public function op_greaterThanOrEquals(other:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return this.t >= other.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A <= B)
|
||||||
|
public function op_lessThanOrEquals(other:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return this.t <= other.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RawSongEventData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The timestamp of the event. The timestamp is in the format of the song's time format.
|
||||||
|
*/
|
||||||
|
var t:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The kind of the event.
|
||||||
|
* Examples include "FocusCamera" and "PlayAnimation"
|
||||||
|
* Custom events can be added by scripts with the `ScriptedSongEvent` class.
|
||||||
|
*/
|
||||||
|
var e:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data for the event.
|
||||||
|
* This can allow the event to include information used for custom behavior.
|
||||||
|
* Data type depends on the event kind. It can be anything that's JSON serializable.
|
||||||
|
*/
|
||||||
|
var v:Dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract SongEventData(RawSongEventData)
|
||||||
|
{
|
||||||
|
public function new(time:Float, event:String, value:Dynamic = null)
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
t: time,
|
||||||
|
e: event,
|
||||||
|
v: value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public var time(get, set):Float;
|
||||||
|
|
||||||
|
public function get_time():Float
|
||||||
|
{
|
||||||
|
return this.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_time(value:Float):Float
|
||||||
|
{
|
||||||
|
return this.t = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var event(get, set):String;
|
||||||
|
|
||||||
|
public function get_event():String
|
||||||
|
{
|
||||||
|
return this.e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_event(value:String):String
|
||||||
|
{
|
||||||
|
return this.e = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var value(get, set):Dynamic;
|
||||||
|
|
||||||
|
public function get_value():Dynamic
|
||||||
|
{
|
||||||
|
return this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_value(value:Dynamic):Dynamic
|
||||||
|
{
|
||||||
|
return this.v = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getBool():Bool
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getInt():Int
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getFloat():Float
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getString():String
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getArray():Array<Dynamic>
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function getBoolArray():Array<Bool>
|
||||||
|
{
|
||||||
|
return cast this.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A == B)
|
||||||
|
public function op_equals(other:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return this.t == other.time && this.e == other.event && this.v == other.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A != B)
|
||||||
|
public function op_notEquals(other:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return this.t != other.time || this.e != other.event || this.v != other.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A > B)
|
||||||
|
public function op_greaterThan(other:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return this.t > other.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A < B)
|
||||||
|
public function op_lessThan(other:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return this.t < other.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A >= B)
|
||||||
|
public function op_greaterThanOrEquals(other:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return this.t >= other.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A <= B)
|
||||||
|
public function op_lessThanOrEquals(other:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return this.t <= other.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract SongPlayableChar(RawSongPlayableChar)
|
||||||
|
{
|
||||||
|
public function new(girlfriend:String, opponent:String, inst:String = "")
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
g: girlfriend,
|
||||||
|
o: opponent,
|
||||||
|
i: inst
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public var girlfriend(get, set):String;
|
||||||
|
|
||||||
|
public function get_girlfriend():String
|
||||||
|
{
|
||||||
|
return this.g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_girlfriend(value:String):String
|
||||||
|
{
|
||||||
|
return this.g = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var opponent(get, set):String;
|
||||||
|
|
||||||
|
public function get_opponent():String
|
||||||
|
{
|
||||||
|
return this.o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_opponent(value:String):String
|
||||||
|
{
|
||||||
|
return this.o = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var inst(get, set):String;
|
||||||
|
|
||||||
|
public function get_inst():String
|
||||||
|
{
|
||||||
|
return this.i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_inst(value:String):String
|
||||||
|
{
|
||||||
|
return this.i = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RawSongChartData =
|
||||||
|
{
|
||||||
|
var version:Version;
|
||||||
|
|
||||||
|
var scrollSpeed:DynamicAccess<Float>;
|
||||||
|
var events:Array<SongEventData>;
|
||||||
|
var notes:DynamicAccess<Array<SongNoteData>>;
|
||||||
|
var generatedBy:String;
|
||||||
|
};
|
||||||
|
|
||||||
|
@:forward
|
||||||
|
abstract SongChartData(RawSongChartData)
|
||||||
|
{
|
||||||
|
public function new(scrollSpeed:Float, events:Array<SongEventData>, notes:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
version: SongMigrator.CHART_VERSION,
|
||||||
|
|
||||||
|
events: events,
|
||||||
|
notes: {
|
||||||
|
normal: notes
|
||||||
|
},
|
||||||
|
scrollSpeed: {
|
||||||
|
normal: scrollSpeed
|
||||||
|
},
|
||||||
|
generatedBy: SongValidator.DEFAULT_GENERATEDBY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScrollSpeed(diff:String = 'default'):Float
|
||||||
|
{
|
||||||
|
var result:Float = this.scrollSpeed.get(diff);
|
||||||
|
|
||||||
|
if (result == 0.0 && diff != 'default')
|
||||||
|
return getScrollSpeed('default');
|
||||||
|
|
||||||
|
return (result == 0.0) ? 1.0 : result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RawSongTimeChange =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Timestamp in specified `timeFormat`.
|
||||||
|
*/
|
||||||
|
var t:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time in beats (int). The game will calculate further beat values based on this one,
|
||||||
|
* so it can do it in a simple linear fashion.
|
||||||
|
*/
|
||||||
|
var b:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quarter notes per minute (float). Cannot be empty in the first element of the list,
|
||||||
|
* but otherwise it's optional, and defaults to the value of the previous element.
|
||||||
|
*/
|
||||||
|
var bpm:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time signature numerator (int). Optional, defaults to 4.
|
||||||
|
*/
|
||||||
|
var n:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time signature denominator (int). Optional, defaults to 4. Should only ever be a power of two.
|
||||||
|
*/
|
||||||
|
var d:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beat tuplets (Array<int> or int). This defines how many steps each beat is divided into.
|
||||||
|
* It can either be an array of length `n` (see above) or a single integer number.
|
||||||
|
* Optional, defaults to `[4]`.
|
||||||
|
*/
|
||||||
|
var bt:OneOfTwo<Int, Array<Int>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add aliases to the minimalized property names of the typedef,
|
||||||
|
* to improve readability.
|
||||||
|
*/
|
||||||
|
abstract SongTimeChange(RawSongTimeChange)
|
||||||
|
{
|
||||||
|
public function new(timeStamp:Float, beatTime:Int, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array<Int>)
|
||||||
|
{
|
||||||
|
this = {
|
||||||
|
t: timeStamp,
|
||||||
|
b: beatTime,
|
||||||
|
bpm: bpm,
|
||||||
|
n: timeSignatureNum,
|
||||||
|
d: timeSignatureDen,
|
||||||
|
bt: beatTuplets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var timeStamp(get, set):Float;
|
||||||
|
|
||||||
|
public function get_timeStamp():Float
|
||||||
|
{
|
||||||
|
return this.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_timeStamp(value:Float):Float
|
||||||
|
{
|
||||||
|
return this.t = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var beatTime(get, set):Int;
|
||||||
|
|
||||||
|
public function get_beatTime():Int
|
||||||
|
{
|
||||||
|
return this.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_beatTime(value:Int):Int
|
||||||
|
{
|
||||||
|
return this.b = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var bpm(get, set):Float;
|
||||||
|
|
||||||
|
public function get_bpm():Float
|
||||||
|
{
|
||||||
|
return this.bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_bpm(value:Float):Float
|
||||||
|
{
|
||||||
|
return this.bpm = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var timeSignatureNum(get, set):Int;
|
||||||
|
|
||||||
|
public function get_timeSignatureNum():Int
|
||||||
|
{
|
||||||
|
return this.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_timeSignatureNum(value:Int):Int
|
||||||
|
{
|
||||||
|
return this.n = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var timeSignatureDen(get, set):Int;
|
||||||
|
|
||||||
|
public function get_timeSignatureDen():Int
|
||||||
|
{
|
||||||
|
return this.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_timeSignatureDen(value:Int):Int
|
||||||
|
{
|
||||||
|
return this.d = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var beatTuplets(get, set):Array<Int>;
|
||||||
|
|
||||||
|
public function get_beatTuplets():Array<Int>
|
||||||
|
{
|
||||||
|
if (Std.isOfType(this.bt, Int))
|
||||||
|
{
|
||||||
|
return [this.bt];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.bt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set_beatTuplets(value:Array<Int>):Array<Int>
|
||||||
|
{
|
||||||
|
return this.bt = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract SongTimeFormat(String) from String to String
|
||||||
|
{
|
||||||
|
var TICKS = "ticks";
|
||||||
|
var FLOAT = "float";
|
||||||
|
var MILLISECONDS = "ms";
|
||||||
|
}
|
||||||
184
source/funkin/play/song/SongDataUtils.hx
Normal file
184
source/funkin/play/song/SongDataUtils.hx
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import flixel.util.FlxSort;
|
||||||
|
import funkin.play.song.SongData.SongEventData;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
import funkin.util.ClipboardUtil;
|
||||||
|
import funkin.util.SerializerUtil;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
class SongDataUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Given an array of SongNoteData objects, return a new array of SongNoteData objects
|
||||||
|
* whose timestamps are shifted by the given amount.
|
||||||
|
* Does not mutate the original array.
|
||||||
|
*
|
||||||
|
* @param notes The notes to modify.
|
||||||
|
* @param offset The time difference to apply in milliseconds.
|
||||||
|
*/
|
||||||
|
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Int):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return notes.map(function(note:SongNoteData):SongNoteData
|
||||||
|
{
|
||||||
|
return new SongNoteData(note.time + offset, note.data, note.length, note.kind);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new array without a certain subset of notes from an array of SongNoteData objects.
|
||||||
|
* Does not mutate the original array.
|
||||||
|
*
|
||||||
|
* @param notes The array of notes to be subtracted from.
|
||||||
|
* @param subtrahend The notes to remove from the `notes` array. Yes, subtrahend is a real word.
|
||||||
|
*/
|
||||||
|
public static function subtractNotes(notes:Array<SongNoteData>, subtrahend:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
if (notes.length == 0 || subtrahend.length == 0)
|
||||||
|
return notes;
|
||||||
|
|
||||||
|
var result = notes.filter(function(note:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
for (x in subtrahend)
|
||||||
|
// SongNoteData's == operation has been overridden so that this will work.
|
||||||
|
if (x == note)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new array without a certain subset of events from an array of SongEventData objects.
|
||||||
|
* Does not mutate the original array.
|
||||||
|
*
|
||||||
|
* @param events The array of events to be subtracted from.
|
||||||
|
* @param subtrahend The events to remove from the `events` array. Yes, subtrahend is a real word.
|
||||||
|
*/
|
||||||
|
public static function subtractEvents(events:Array<SongEventData>, subtrahend:Array<SongEventData>)
|
||||||
|
{
|
||||||
|
if (events.length == 0 || subtrahend.length == 0)
|
||||||
|
return events;
|
||||||
|
|
||||||
|
return events.filter(function(event:SongEventData):Bool
|
||||||
|
{
|
||||||
|
// SongEventData's == operation has been overridden so that this will work.
|
||||||
|
return !subtrahend.has(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an array of notes whose note data is flipped (player becomes opponent and vice versa)
|
||||||
|
* Does not mutate the original array.
|
||||||
|
*/
|
||||||
|
public static function flipNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return notes.map(function(note:SongNoteData):SongNoteData
|
||||||
|
{
|
||||||
|
var newData = note.data;
|
||||||
|
|
||||||
|
if (newData < strumlineSize)
|
||||||
|
newData += strumlineSize;
|
||||||
|
else
|
||||||
|
newData -= strumlineSize;
|
||||||
|
|
||||||
|
return new SongNoteData(note.time, newData, note.length, note.kind);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare an array of notes to be used as the clipboard data.
|
||||||
|
*
|
||||||
|
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
||||||
|
*/
|
||||||
|
public static function buildClipboard(notes:Array<SongNoteData>):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return offsetSongNoteData(sortNotes(notes), -Std.int(notes[0].time));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort an array of notes by strum time.
|
||||||
|
*/
|
||||||
|
public static function sortNotes(notes:Array<SongNoteData>, ?desc:Bool = false):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
// TODO: Modifies the array in place. Is this okay?
|
||||||
|
notes.sort(function(a:SongNoteData, b:SongNoteData):Int
|
||||||
|
{
|
||||||
|
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
|
||||||
|
});
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an array of note data and write it to the clipboard.
|
||||||
|
*/
|
||||||
|
public static function writeNotesToClipboard(notes:Array<SongNoteData>):Void
|
||||||
|
{
|
||||||
|
var notesString = SerializerUtil.toJSON(notes);
|
||||||
|
|
||||||
|
ClipboardUtil.setClipboard(notesString);
|
||||||
|
|
||||||
|
trace('Wrote ' + notes.length + ' notes to clipboard.');
|
||||||
|
|
||||||
|
trace(notesString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an array of note data from the clipboard and deserialize it.
|
||||||
|
*/
|
||||||
|
public static function readNotesFromClipboard():Array<SongNoteData>
|
||||||
|
{
|
||||||
|
var notesString = ClipboardUtil.getClipboard();
|
||||||
|
|
||||||
|
trace('Read ' + notesString.length + ' characters from clipboard.');
|
||||||
|
|
||||||
|
var notes:Array<SongNoteData> = SerializerUtil.fromJSON(notesString);
|
||||||
|
|
||||||
|
if (notes == null)
|
||||||
|
{
|
||||||
|
trace('Failed to parse notes from clipboard.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Parsed ' + notes.length + ' notes from clipboard.');
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a list of notes to only include notes that are within the given time range.
|
||||||
|
*/
|
||||||
|
public static function getNotesInTimeRange(notes:Array<SongNoteData>, start:Float, end:Float):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return notes.filter(function(note:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return note.time >= start && note.time <= end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a list of notes to only include notes whose data is within the given range.
|
||||||
|
*/
|
||||||
|
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return notes.filter(function(note:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return note.data >= start && note.data <= end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a list of notes to only include notes whose data is one of the given values.
|
||||||
|
*/
|
||||||
|
public static function getNotesWithData(notes:Array<SongNoteData>, data:Array<Int>):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return notes.filter(function(note:SongNoteData):Bool
|
||||||
|
{
|
||||||
|
return data.indexOf(note.data) != -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
79
source/funkin/play/song/SongMigrator.hx
Normal file
79
source/funkin/play/song/SongMigrator.hx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import funkin.play.song.SongData.SongChartData;
|
||||||
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
|
import funkin.util.VersionUtil;
|
||||||
|
|
||||||
|
class SongMigrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current latest version string for the song data format.
|
||||||
|
* Handle breaking changes by incrementing this value
|
||||||
|
* and adding migration to the SongMigrator class.
|
||||||
|
*/
|
||||||
|
public static final CHART_VERSION:String = "2.0.0";
|
||||||
|
|
||||||
|
public static final CHART_VERSION_RULE:String = "2.0.x";
|
||||||
|
|
||||||
|
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
|
||||||
|
{
|
||||||
|
if (jsonData.version)
|
||||||
|
{
|
||||||
|
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song (${songId}) metadata version (${jsonData.version}) is valid and up-to-date.');
|
||||||
|
|
||||||
|
var songMetadata:SongMetadata = cast jsonData;
|
||||||
|
|
||||||
|
return songMetadata;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song (${songId}) metadata version (${jsonData.version}) is outdated.');
|
||||||
|
switch (jsonData.version)
|
||||||
|
{
|
||||||
|
// TODO: Add migration functions as cases here.
|
||||||
|
default:
|
||||||
|
// Unknown version.
|
||||||
|
trace('[SONGDATA] Song (${songId}) unknown metadata version: ${jsonData.version}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song metadata version is missing.');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
|
||||||
|
{
|
||||||
|
if (jsonData.version)
|
||||||
|
{
|
||||||
|
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.');
|
||||||
|
|
||||||
|
var songChartData:SongChartData = cast jsonData;
|
||||||
|
|
||||||
|
return songChartData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song (${songId}) chart version (${jsonData.version}) is outdated.');
|
||||||
|
switch (jsonData.version)
|
||||||
|
{
|
||||||
|
// TODO: Add migration functions as cases here.
|
||||||
|
default:
|
||||||
|
// Unknown version.
|
||||||
|
trace('[SONGDATA] Song (${songId}) unknown chart version: ${jsonData.version}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song chart version is missing.');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
216
source/funkin/play/song/SongSerializer.hx
Normal file
216
source/funkin/play/song/SongSerializer.hx
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import funkin.play.song.SongData.SongChartData;
|
||||||
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
|
import funkin.util.SerializerUtil;
|
||||||
|
import lime.utils.Bytes;
|
||||||
|
import openfl.events.Event;
|
||||||
|
import openfl.events.IOErrorEvent;
|
||||||
|
import openfl.net.FileReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for exporting a chart to a JSON file.
|
||||||
|
* Primarily used for the chart editor.
|
||||||
|
*/
|
||||||
|
class SongSerializer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Access a SongChartData JSON file from a specific path, then load it.
|
||||||
|
* @param path The file path to read from.
|
||||||
|
*/
|
||||||
|
public static function importSongChartDataSync(path:String):SongChartData
|
||||||
|
{
|
||||||
|
var fileData = readFile(path);
|
||||||
|
|
||||||
|
if (fileData == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var songChartData:SongChartData = SerializerUtil.fromJSON(fileData);
|
||||||
|
|
||||||
|
return songChartData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access a SongMetadata JSON file from a specific path, then load it.
|
||||||
|
* @param path The file path to read from.
|
||||||
|
*/
|
||||||
|
public static function importSongMetadataSync(path:String):SongMetadata
|
||||||
|
{
|
||||||
|
var fileData = readFile(path);
|
||||||
|
|
||||||
|
if (fileData == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var songMetadata:SongMetadata = SerializerUtil.fromJSON(fileData);
|
||||||
|
|
||||||
|
return songMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user to browse for a SongChartData JSON file path, then load it.
|
||||||
|
* @param callback The function to call when the file is loaded.
|
||||||
|
*/
|
||||||
|
public static function importSongChartDataAsync(callback:SongChartData->Void):Void
|
||||||
|
{
|
||||||
|
browseFileReference(function(fileReference:FileReference)
|
||||||
|
{
|
||||||
|
var data = fileReference.data.toString();
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var songChartData:SongChartData = SerializerUtil.fromJSON(data);
|
||||||
|
|
||||||
|
if (songChartData != null)
|
||||||
|
callback(songChartData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user to browse for a SongMetadata JSON file path, then load it.
|
||||||
|
* @param callback The function to call when the file is loaded.
|
||||||
|
*/
|
||||||
|
public static function importSongMetadataAsync(callback:SongMetadata->Void):Void
|
||||||
|
{
|
||||||
|
browseFileReference(function(fileReference:FileReference)
|
||||||
|
{
|
||||||
|
var data = fileReference.data.toString();
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var songMetadata:SongMetadata = SerializerUtil.fromJSON(data);
|
||||||
|
|
||||||
|
if (songMetadata != null)
|
||||||
|
callback(songMetadata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a SongChartData object as a JSON file to an automatically generated path.
|
||||||
|
* Works great on HTML5 and desktop.
|
||||||
|
*/
|
||||||
|
public static function exportSongChartData(data:SongChartData)
|
||||||
|
{
|
||||||
|
var path = 'chart.json';
|
||||||
|
exportSongChartDataAs(path, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a SongMetadata object as a JSON file to an automatically generated path.
|
||||||
|
* Works great on HTML5 and desktop.
|
||||||
|
*/
|
||||||
|
public static function exportSongMetadata(data:SongMetadata)
|
||||||
|
{
|
||||||
|
var path = 'metadata.json';
|
||||||
|
exportSongMetadataAs(path, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a SongChartData object as a JSON file to a specified path.
|
||||||
|
* Works great on HTML5 and desktop.
|
||||||
|
*
|
||||||
|
* @param path The file path to save to.
|
||||||
|
*/
|
||||||
|
public static function exportSongChartDataAs(path:String, data:SongChartData)
|
||||||
|
{
|
||||||
|
var dataString = SerializerUtil.toJSON(data);
|
||||||
|
|
||||||
|
writeFileReference(path, dataString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a SongMetadata object as a JSON file to a specified path.
|
||||||
|
* Works great on HTML5 and desktop.
|
||||||
|
*
|
||||||
|
* @param path The file path to save to.
|
||||||
|
*/
|
||||||
|
public static function exportSongMetadataAs(path:String, data:SongMetadata)
|
||||||
|
{
|
||||||
|
var dataString = SerializerUtil.toJSON(data);
|
||||||
|
|
||||||
|
writeFileReference(path, dataString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the string contents of a file.
|
||||||
|
* Only works on desktop platforms.
|
||||||
|
* @param path The file path to read from.
|
||||||
|
*/
|
||||||
|
static function readFile(path:String):String
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
var fileBytes:Bytes = sys.io.File.getBytes(path);
|
||||||
|
|
||||||
|
if (fileBytes == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return fileBytes.toString();
|
||||||
|
#end
|
||||||
|
|
||||||
|
trace('ERROR: readFile not implemented for this platform');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write string contents to a file.
|
||||||
|
* Only works on desktop platforms.
|
||||||
|
* @param path The file path to read from.
|
||||||
|
*/
|
||||||
|
static function writeFile(path:String, data:String):Void
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
sys.io.File.saveContent(path, data);
|
||||||
|
return;
|
||||||
|
#end
|
||||||
|
trace('ERROR: writeFile not implemented for this platform');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browse for a file to read and execute a callback once we have a file reference.
|
||||||
|
* Works great on HTML5 or desktop.
|
||||||
|
*
|
||||||
|
* @param callback The function to call when the file is loaded.
|
||||||
|
*/
|
||||||
|
static function browseFileReference(callback:FileReference->Void)
|
||||||
|
{
|
||||||
|
var file = new FileReference();
|
||||||
|
|
||||||
|
file.addEventListener(Event.SELECT, function(e)
|
||||||
|
{
|
||||||
|
var selectedFileRef:FileReference = e.target;
|
||||||
|
trace('Selected file: ' + selectedFileRef.name);
|
||||||
|
selectedFileRef.addEventListener(Event.COMPLETE, function(e)
|
||||||
|
{
|
||||||
|
var loadedFileRef:FileReference = e.target;
|
||||||
|
trace('Loaded file: ' + loadedFileRef.name);
|
||||||
|
callback(loadedFileRef);
|
||||||
|
});
|
||||||
|
selectedFileRef.load();
|
||||||
|
});
|
||||||
|
|
||||||
|
file.browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user to save a file to their computer.
|
||||||
|
*/
|
||||||
|
static function writeFileReference(path:String, data:String)
|
||||||
|
{
|
||||||
|
var file = new FileReference();
|
||||||
|
file.addEventListener(Event.COMPLETE, function(e:Event)
|
||||||
|
{
|
||||||
|
trace('Successfully wrote file.');
|
||||||
|
});
|
||||||
|
file.addEventListener(Event.CANCEL, function(e:Event)
|
||||||
|
{
|
||||||
|
trace('Cancelled writing file.');
|
||||||
|
});
|
||||||
|
file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent)
|
||||||
|
{
|
||||||
|
trace('IO error writing file.');
|
||||||
|
});
|
||||||
|
file.save(data, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
131
source/funkin/play/song/SongValidator.hx
Normal file
131
source/funkin/play/song/SongValidator.hx
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
package funkin.play.song;
|
||||||
|
|
||||||
|
import funkin.play.song.SongData.SongChartData;
|
||||||
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
|
import funkin.play.song.SongData.SongPlayData;
|
||||||
|
import funkin.play.song.SongData.SongTimeChange;
|
||||||
|
import funkin.play.song.SongData.SongTimeFormat;
|
||||||
|
import funkin.util.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For SongMetadata and SongChartData objects,
|
||||||
|
* ensures mandatory fields are present and populates optional fields with default values.
|
||||||
|
*/
|
||||||
|
class SongValidator
|
||||||
|
{
|
||||||
|
public static final DEFAULT_SONGNAME:String = "Unknown";
|
||||||
|
public static final DEFAULT_ARTIST:String = "Unknown";
|
||||||
|
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
||||||
|
public static final DEFAULT_DIVISIONS:Int = -1;
|
||||||
|
public static final DEFAULT_LOOP:Bool = false;
|
||||||
|
public static final DEFAULT_STAGE:String = "mainStage";
|
||||||
|
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||||
|
|
||||||
|
public static var DEFAULT_GENERATEDBY(get, null):String;
|
||||||
|
|
||||||
|
static function get_DEFAULT_GENERATEDBY():String
|
||||||
|
{
|
||||||
|
return '${Constants.TITLE} - ${Constants.VERSION}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the fields of a SongMetadata object (excluding the version field).
|
||||||
|
*
|
||||||
|
* @param input The SongMetadata object to validate.
|
||||||
|
* @param songId The ID of the song being validated. Only used for error messages.
|
||||||
|
* @return The validated SongMetadata object.
|
||||||
|
*/
|
||||||
|
public static function validateSongMetadata(input:SongMetadata, songId:String = 'unknown'):SongMetadata
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Could not parse metadata for song ${songId}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.songName == null)
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song ${songId} is missing a songName field. ');
|
||||||
|
input.songName = DEFAULT_SONGNAME;
|
||||||
|
}
|
||||||
|
if (input.artist == null)
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song ${songId} is missing an artist field. ');
|
||||||
|
input.artist = DEFAULT_ARTIST;
|
||||||
|
}
|
||||||
|
if (input.timeFormat == null)
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song ${songId} is missing a timeFormat field. ');
|
||||||
|
input.timeFormat = DEFAULT_TIMEFORMAT;
|
||||||
|
}
|
||||||
|
if (input.generatedBy == null)
|
||||||
|
{
|
||||||
|
input.generatedBy = DEFAULT_GENERATEDBY;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.timeChanges = validateTimeChanges(input.timeChanges, songId);
|
||||||
|
input.playData = validatePlayData(input.playData, songId);
|
||||||
|
|
||||||
|
input.variation = '';
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the fields of a SongPlayData object.
|
||||||
|
*
|
||||||
|
* @param input The SongPlayData object to validate.
|
||||||
|
* @param songId The ID of the song being validated. Only used for error messages.
|
||||||
|
* @return The validated SongPlayData object.
|
||||||
|
*/
|
||||||
|
public static function validatePlayData(input:SongPlayData, songId:String = 'unknown'):SongPlayData
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the fields of a TimeChange object.
|
||||||
|
*
|
||||||
|
* @param input The TimeChange object to validate.
|
||||||
|
* @param songId The ID of the song being validated. Only used for error messages.
|
||||||
|
* @return The validated TimeChange object.
|
||||||
|
*/
|
||||||
|
public static function validateTimeChange(input:SongTimeChange, songId:String = 'unknown'):SongTimeChange
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates multiple TimeChange objects in an array.
|
||||||
|
*/
|
||||||
|
public static function validateTimeChanges(input:Array<SongTimeChange>, songId:String = 'unknown'):Array<SongTimeChange>
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Song ${songId} is missing a timeChanges field. ');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
input = input.map((timeChange) -> validateTimeChange(timeChange, songId));
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the fields of a SongChartData object (excluding the version field).
|
||||||
|
*
|
||||||
|
* @param input The SongChartData object to validate.
|
||||||
|
* @param songId The ID of the song being validated. Only used for error messages.
|
||||||
|
* @return The validated SongChartData object.
|
||||||
|
*/
|
||||||
|
public static function validateSongChartData(input:SongChartData, songId:String = 'unknown'):SongChartData
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
|
{
|
||||||
|
trace('[SONGDATA] Could not parse chart data for song ${songId}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
import flixel.FlxG;
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import flixel.util.FlxTimer;
|
||||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
|
||||||
|
|
@ -43,8 +44,6 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
*/
|
*/
|
||||||
public var shouldBop:Bool = true;
|
public var shouldBop:Bool = true;
|
||||||
|
|
||||||
public var finishCallbackMap:Map<String, Void->Void> = new Map<String, Void->Void>();
|
|
||||||
|
|
||||||
function set_idleSuffix(value:String):String
|
function set_idleSuffix(value:String):String
|
||||||
{
|
{
|
||||||
this.idleSuffix = value;
|
this.idleSuffix = value;
|
||||||
|
|
@ -55,10 +54,28 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
/**
|
/**
|
||||||
* The offset of the character relative to the position specified by the stage.
|
* The offset of the character relative to the position specified by the stage.
|
||||||
*/
|
*/
|
||||||
public var globalOffsets(default, null):Array<Float> = [0, 0];
|
public var globalOffsets(default, set):Array<Float> = [0, 0];
|
||||||
|
|
||||||
|
function set_globalOffsets(value:Array<Float>)
|
||||||
|
{
|
||||||
|
if (globalOffsets == null)
|
||||||
|
globalOffsets = [0, 0];
|
||||||
|
if (globalOffsets == value)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
var xDiff = globalOffsets[0] - value[0];
|
||||||
|
var yDiff = globalOffsets[1] - value[1];
|
||||||
|
|
||||||
|
this.x += xDiff;
|
||||||
|
this.y += yDiff;
|
||||||
|
|
||||||
|
return animOffsets = value;
|
||||||
|
}
|
||||||
|
|
||||||
private var animOffsets(default, set):Array<Float> = [0, 0];
|
private var animOffsets(default, set):Array<Float> = [0, 0];
|
||||||
|
|
||||||
|
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
|
||||||
|
|
||||||
function set_animOffsets(value:Array<Float>)
|
function set_animOffsets(value:Array<Float>)
|
||||||
{
|
{
|
||||||
if (animOffsets == null)
|
if (animOffsets == null)
|
||||||
|
|
@ -85,11 +102,48 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.danceEvery = danceEvery;
|
this.danceEvery = danceEvery;
|
||||||
this.animation.finishCallback = function(name)
|
|
||||||
|
this.animation.callback = this.onAnimationFrame;
|
||||||
|
this.animation.finishCallback = this.onAnimationFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an animation finishes.
|
||||||
|
* @param name The name of the animation that just finished.
|
||||||
|
*/
|
||||||
|
function onAnimationFinished(name:String)
|
||||||
|
{
|
||||||
|
if (!canPlayOtherAnims)
|
||||||
{
|
{
|
||||||
if (finishCallbackMap[name] != null)
|
canPlayOtherAnims = true;
|
||||||
finishCallbackMap[name]();
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the current animation's frame changes.
|
||||||
|
* @param name The name of the current animation.
|
||||||
|
* @param frameNumber The number of the current frame.
|
||||||
|
* @param frameIndex The index of the current frame.
|
||||||
|
*
|
||||||
|
* For example, if an animation was defined as having the indexes [3, 0, 1, 2],
|
||||||
|
* then the first callback would have frameNumber = 0 and frameIndex = 3.
|
||||||
|
*/
|
||||||
|
function onAnimationFrame(name:String = "", frameNumber:Int = -1, frameIndex:Int = -1)
|
||||||
|
{
|
||||||
|
// Do nothing by default.
|
||||||
|
// This can be overridden by, for example, scripted characters.
|
||||||
|
// Try not to do anything expensive here, it runs many times a second.
|
||||||
|
|
||||||
|
// Sometimes this gets called with empty values? IDK why but adding defaults keeps it from crashing.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this Bopper was defined by the stage, return the prop to its original position.
|
||||||
|
*/
|
||||||
|
public function resetPosition()
|
||||||
|
{
|
||||||
|
this.x = originalPosition.x + animOffsets[0];
|
||||||
|
this.y = originalPosition.y + animOffsets[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_shouldAlternate():Void
|
function update_shouldAlternate():Void
|
||||||
|
|
@ -196,10 +250,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
*/
|
*/
|
||||||
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
||||||
{
|
{
|
||||||
if (ignoreOther == null)
|
if (!canPlayOtherAnims && !ignoreOther)
|
||||||
ignoreOther = false;
|
|
||||||
|
|
||||||
if (!canPlayOtherAnims)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var correctName = correctAnimationName(name);
|
var correctName = correctAnimationName(name);
|
||||||
|
|
@ -211,18 +262,38 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
if (ignoreOther)
|
if (ignoreOther)
|
||||||
{
|
{
|
||||||
canPlayOtherAnims = false;
|
canPlayOtherAnims = false;
|
||||||
|
|
||||||
// doing it with this funny map, since overriding the animation.finishCallback is a bit messier IMO
|
|
||||||
finishCallbackMap[name] = function()
|
|
||||||
{
|
|
||||||
canPlayOtherAnims = true;
|
|
||||||
finishCallbackMap[name] = null;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAnimationOffsets(correctName);
|
applyAnimationOffsets(correctName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forceAnimationTimer:FlxTimer = new FlxTimer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name The animation to play.
|
||||||
|
* @param duration The duration in which other (non-forced) animations will be skipped, in seconds (NOT MILLISECONDS).
|
||||||
|
*/
|
||||||
|
public function forceAnimationForDuration(name:String, duration:Float):Void
|
||||||
|
{
|
||||||
|
// TODO: Might be nice to rework this function, maybe have a numbered priority system?
|
||||||
|
|
||||||
|
if (this.animation == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var correctName = correctAnimationName(name);
|
||||||
|
if (correctName == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.animation.play(correctName, false, false);
|
||||||
|
applyAnimationOffsets(correctName);
|
||||||
|
|
||||||
|
canPlayOtherAnims = false;
|
||||||
|
forceAnimationTimer.start(duration, (timer) ->
|
||||||
|
{
|
||||||
|
canPlayOtherAnims = true;
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
function applyAnimationOffsets(name:String)
|
function applyAnimationOffsets(name:String)
|
||||||
{
|
{
|
||||||
var offsets = animationOffsets.get(name);
|
var offsets = animationOffsets.get(name);
|
||||||
|
|
@ -258,39 +329,75 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
return this.animation.curAnim.name;
|
return this.animation.curAnim.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent) {}
|
public function onScriptEvent(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCreate(event:ScriptEvent) {}
|
public function onCreate(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onDestroy(event:ScriptEvent) {}
|
public function onDestroy(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onUpdate(event:UpdateScriptEvent) {}
|
public function onUpdate(event:UpdateScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onPause(event:PauseScriptEvent) {}
|
public function onPause(event:PauseScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onResume(event:ScriptEvent) {}
|
public function onResume(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongStart(event:ScriptEvent) {}
|
public function onSongStart(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongEnd(event:ScriptEvent) {}
|
public function onSongEnd(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onGameOver(event:ScriptEvent) {}
|
public function onGameOver(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent) {}
|
public function onNoteHit(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
public function onStepHit(event:SongTimeScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
public function onCountdownStart(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
public function onCountdownStep(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
public function onCountdownEnd(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongLoaded(eent:SongLoadScriptEvent) {}
|
public function onSongLoaded(event:SongLoadScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongRetry(event:ScriptEvent) {}
|
public function onSongRetry(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
import funkin.modding.IHook;
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedBopper extends Bopper implements IHook {}
|
class ScriptedBopper extends Bopper implements HScriptedClass {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
import funkin.modding.IHook;
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedStage extends Stage implements IHook {}
|
class ScriptedStage extends Stage implements HScriptedClass {}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package funkin.play.stage;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
import flixel.util.FlxSort;
|
import flixel.util.FlxSort;
|
||||||
import funkin.modding.IHook;
|
|
||||||
import funkin.modding.IScriptedClass;
|
import funkin.modding.IScriptedClass;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
|
|
@ -19,7 +19,7 @@ import funkin.util.assets.FlxAnimationUtil;
|
||||||
*
|
*
|
||||||
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
||||||
*/
|
*/
|
||||||
class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScriptedClass
|
class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
public final stageId:String;
|
public final stageId:String;
|
||||||
public final stageName:String;
|
public final stageName:String;
|
||||||
|
|
@ -62,6 +62,49 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
{
|
{
|
||||||
buildStage();
|
buildStage();
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
|
||||||
|
debugIconGroup = new FlxSpriteGroup();
|
||||||
|
debugIconGroup.visible = false;
|
||||||
|
debugIconGroup.zIndex = 1000000;
|
||||||
|
add(debugIconGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetStage():Void
|
||||||
|
{
|
||||||
|
// Reset positions of characters.
|
||||||
|
if (getBoyfriend() != null)
|
||||||
|
{
|
||||||
|
getBoyfriend().resetCharacter(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('STAGE RESET: No boyfriend found.');
|
||||||
|
}
|
||||||
|
if (getGirlfriend() != null)
|
||||||
|
{
|
||||||
|
getGirlfriend().resetCharacter(false);
|
||||||
|
}
|
||||||
|
if (getDad() != null)
|
||||||
|
{
|
||||||
|
getDad().resetCharacter(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset positions of named props.
|
||||||
|
for (dataProp in _data.props)
|
||||||
|
{
|
||||||
|
// Fetch the prop.
|
||||||
|
var prop:FlxSprite = getNamedProp(dataProp.name);
|
||||||
|
|
||||||
|
if (prop != null)
|
||||||
|
{
|
||||||
|
// Reset the position.
|
||||||
|
prop.x = dataProp.position[0];
|
||||||
|
prop.y = dataProp.position[1];
|
||||||
|
prop.zIndex = dataProp.zIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can assume unnamed props are not moving.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,6 +117,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
|
|
||||||
this.camZoom = _data.cameraZoom;
|
this.camZoom = _data.cameraZoom;
|
||||||
|
|
||||||
|
this.debugIconGroup = new FlxSpriteGroup();
|
||||||
|
|
||||||
for (dataProp in _data.props)
|
for (dataProp in _data.props)
|
||||||
{
|
{
|
||||||
trace(' Placing prop: ${dataProp.name} (${dataProp.assetPath})');
|
trace(' Placing prop: ${dataProp.name} (${dataProp.assetPath})');
|
||||||
|
|
@ -129,6 +174,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
propSprite.x = dataProp.position[0];
|
propSprite.x = dataProp.position[0];
|
||||||
propSprite.y = dataProp.position[1];
|
propSprite.y = dataProp.position[1];
|
||||||
|
|
||||||
|
propSprite.alpha = dataProp.alpha;
|
||||||
|
|
||||||
// If pixel, disable antialiasing.
|
// If pixel, disable antialiasing.
|
||||||
propSprite.antialiasing = !dataProp.isPixel;
|
propSprite.antialiasing = !dataProp.isPixel;
|
||||||
|
|
||||||
|
|
@ -166,6 +213,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
{
|
{
|
||||||
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
|
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
|
||||||
}
|
}
|
||||||
|
cast(propSprite, Bopper).originalPosition.x = dataProp.position[0];
|
||||||
|
cast(propSprite, Bopper).originalPosition.y = dataProp.position[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataProp.startingAnimation != null)
|
if (dataProp.startingAnimation != null)
|
||||||
|
|
@ -218,6 +267,14 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setShader(shader:FlxShader)
|
||||||
|
{
|
||||||
|
forEachAlive(function(prop:FlxSprite)
|
||||||
|
{
|
||||||
|
prop.shader = shader;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the position and other properties of the soon-to-be child of this sprite group.
|
* Adjusts the position and other properties of the soon-to-be child of this sprite group.
|
||||||
* Private helper to avoid duplicate code in `add()` and `insert()`.
|
* Private helper to avoid duplicate code in `add()` and `insert()`.
|
||||||
|
|
@ -238,6 +295,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
clipRectTransform(sprite, clipRect);
|
clipRectTransform(sprite, clipRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var debugIconGroup:FlxSpriteGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by the PlayState to add a character to the stage.
|
* Used by the PlayState to add a character to the stage.
|
||||||
*/
|
*/
|
||||||
|
|
@ -251,9 +310,13 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
// Should display at the stage position of the character (before any offsets).
|
// Should display at the stage position of the character (before any offsets).
|
||||||
// TODO: Make this a toggle? It's useful to turn on from time to time.
|
// TODO: Make this a toggle? It's useful to turn on from time to time.
|
||||||
var debugIcon:FlxSprite = new FlxSprite(0, 0);
|
var debugIcon:FlxSprite = new FlxSprite(0, 0);
|
||||||
|
var debugIcon2:FlxSprite = new FlxSprite(0, 0);
|
||||||
debugIcon.makeGraphic(8, 8, 0xffff00ff);
|
debugIcon.makeGraphic(8, 8, 0xffff00ff);
|
||||||
debugIcon.visible = false;
|
debugIcon2.makeGraphic(8, 8, 0xff00ffff);
|
||||||
|
debugIcon.visible = true;
|
||||||
|
debugIcon2.visible = true;
|
||||||
debugIcon.zIndex = 1000000;
|
debugIcon.zIndex = 1000000;
|
||||||
|
debugIcon2.zIndex = 1000000;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// Apply position and z-index.
|
// Apply position and z-index.
|
||||||
|
|
@ -263,20 +326,25 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
case BF:
|
case BF:
|
||||||
this.characters.set("bf", character);
|
this.characters.set("bf", character);
|
||||||
charData = _data.characters.bf;
|
charData = _data.characters.bf;
|
||||||
character.flipX = !character.flipX;
|
character.flipX = !character.getDataFlipX();
|
||||||
// flip offsets if flipX
|
|
||||||
character.initHealthIcon(false);
|
character.initHealthIcon(false);
|
||||||
case GF:
|
case GF:
|
||||||
this.characters.set("gf", character);
|
this.characters.set("gf", character);
|
||||||
charData = _data.characters.gf;
|
charData = _data.characters.gf;
|
||||||
|
character.flipX = character.getDataFlipX();
|
||||||
case DAD:
|
case DAD:
|
||||||
this.characters.set("dad", character);
|
this.characters.set("dad", character);
|
||||||
charData = _data.characters.dad;
|
charData = _data.characters.dad;
|
||||||
// flip offsets if flipX
|
character.flipX = character.getDataFlipX();
|
||||||
character.initHealthIcon(true);
|
character.initHealthIcon(true);
|
||||||
default:
|
default:
|
||||||
this.characters.set(character.characterId, character);
|
this.characters.set(character.characterId, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the character before adding it to the stage.
|
||||||
|
// This ensures positioning is based on the idle animation.
|
||||||
|
character.resetCharacter(true);
|
||||||
|
|
||||||
if (charData != null)
|
if (charData != null)
|
||||||
{
|
{
|
||||||
character.zIndex = charData.zIndex;
|
character.zIndex = charData.zIndex;
|
||||||
|
|
@ -287,17 +355,31 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
||||||
character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
||||||
|
|
||||||
|
character.originalPosition.x = character.x;
|
||||||
|
character.originalPosition.y = character.y;
|
||||||
|
|
||||||
character.cameraFocusPoint.x += charData.cameraOffsets[0];
|
character.cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||||
character.cameraFocusPoint.y += charData.cameraOffsets[1];
|
character.cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||||
|
|
||||||
|
#if debug
|
||||||
// Draw the debug icon at the character's feet.
|
// Draw the debug icon at the character's feet.
|
||||||
debugIcon.x = charData.position[0];
|
if (charType == BF || charType == DAD)
|
||||||
debugIcon.y = charData.position[1];
|
{
|
||||||
|
debugIcon.x = charData.position[0];
|
||||||
|
debugIcon.y = charData.position[1];
|
||||||
|
debugIcon2.x = character.x;
|
||||||
|
debugIcon2.y = character.y;
|
||||||
|
}
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the character to the scene.
|
// Add the character to the scene.
|
||||||
this.add(character);
|
this.add(character);
|
||||||
this.add(debugIcon);
|
|
||||||
|
#if debug
|
||||||
|
debugIconGroup.add(debugIcon);
|
||||||
|
debugIconGroup.add(debugIcon2);
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getGirlfriendPosition():FlxPoint
|
public inline function getGirlfriendPosition():FlxPoint
|
||||||
|
|
@ -460,13 +542,23 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
group.clear();
|
group.clear();
|
||||||
|
if (debugIconGroup != null && debugIconGroup.group != null)
|
||||||
|
{
|
||||||
|
debugIconGroup.kill();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
debugIconGroup = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that gets called once per step in the song.
|
* A function that gets called once per step in the song.
|
||||||
* @param curStep The current step number.
|
* @param curStep The current step number.
|
||||||
*/
|
*/
|
||||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
public function onStepHit(event:SongTimeScriptEvent):Void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that gets called once per beat in the song (once every four steps).
|
* A function that gets called once per beat in the song (once every four steps).
|
||||||
|
|
@ -483,33 +575,67 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent) {}
|
public function onUpdate(event:UpdateScriptEvent)
|
||||||
|
{
|
||||||
|
if (FlxG.keys.justPressed.F3)
|
||||||
|
{
|
||||||
|
debugIconGroup.visible = !debugIconGroup.visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function onPause(event:PauseScriptEvent) {}
|
public function onScriptEvent(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onResume(event:ScriptEvent) {}
|
public function onPause(event:PauseScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongStart(event:ScriptEvent) {}
|
public function onResume(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongEnd(event:ScriptEvent) {}
|
public function onSongStart(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onGameOver(event:ScriptEvent) {}
|
public function onSongEnd(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
public function onGameOver(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
public function onCountdownStart(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
public function onCountdownStep(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onUpdate(event:UpdateScriptEvent) {}
|
public function onCountdownEnd(event:CountdownScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent) {}
|
public function onNoteHit(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongLoaded(eent:SongLoadScriptEvent) {}
|
public function onSongLoaded(event:SongLoadScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function onSongRetry(event:ScriptEvent) {}
|
public function onSongRetry(event:ScriptEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
import flixel.util.typeLimit.OneOfTwo;
|
import flixel.util.typeLimit.OneOfTwo;
|
||||||
|
import funkin.play.stage.ScriptedStage;
|
||||||
|
import funkin.play.stage.Stage;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
import funkin.util.assets.DataAssets;
|
import funkin.util.assets.DataAssets;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
|
|
@ -87,6 +89,7 @@ class StageDataParser
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
|
trace(' An error occurred while loading stage data: ${stageId}');
|
||||||
// Assume error was already logged.
|
// Assume error was already logged.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +141,11 @@ class StageDataParser
|
||||||
return validateStageData(stageId, stageData);
|
return validateStageData(stageId, stageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function listStageIds():Array<String>
|
||||||
|
{
|
||||||
|
return [for (x in stageCache.keys()) x];
|
||||||
|
}
|
||||||
|
|
||||||
static function loadStageFile(stagePath:String):String
|
static function loadStageFile(stagePath:String):String
|
||||||
{
|
{
|
||||||
var stageFilePath:String = Paths.json('stages/${stagePath}');
|
var stageFilePath:String = Paths.json('stages/${stagePath}');
|
||||||
|
|
@ -179,6 +187,7 @@ class StageDataParser
|
||||||
static final DEFAULT_CAMERA_OFFSETS_DAD:Array<Float> = [150, -100];
|
static final DEFAULT_CAMERA_OFFSETS_DAD:Array<Float> = [150, -100];
|
||||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_SCALE:Float = 1.0;
|
static final DEFAULT_SCALE:Float = 1.0;
|
||||||
|
static final DEFAULT_ALPHA:Float = 1.0;
|
||||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_ZINDEX:Int = 0;
|
static final DEFAULT_ZINDEX:Int = 0;
|
||||||
|
|
||||||
|
|
@ -281,6 +290,11 @@ class StageDataParser
|
||||||
inputProp.scroll = DEFAULT_SCROLL;
|
inputProp.scroll = DEFAULT_SCROLL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputProp.alpha == null)
|
||||||
|
{
|
||||||
|
inputProp.alpha = DEFAULT_ALPHA;
|
||||||
|
}
|
||||||
|
|
||||||
if (Std.isOfType(inputProp.scroll, Float))
|
if (Std.isOfType(inputProp.scroll, Float))
|
||||||
{
|
{
|
||||||
inputProp.scroll = [inputProp.scroll, inputProp.scroll];
|
inputProp.scroll = [inputProp.scroll, inputProp.scroll];
|
||||||
|
|
@ -440,6 +454,12 @@ typedef StageDataProp =
|
||||||
*/
|
*/
|
||||||
var scale:OneOfTwo<Float, Array<Float>>;
|
var scale:OneOfTwo<Float, Array<Float>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alpha of the prop, as a float.
|
||||||
|
* @default 1.0
|
||||||
|
*/
|
||||||
|
var alpha:Null<Float>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If not zero, this prop will play an animation every X beats of the song.
|
* If not zero, this prop will play an animation every X beats of the song.
|
||||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package funkin.shaderslmfao;
|
|
||||||
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
|
||||||
|
|
||||||
class BuildingShaders
|
|
||||||
{
|
|
||||||
public var shader(default, null):BuildingShader;
|
|
||||||
public var daAlpha:Float = 1;
|
|
||||||
|
|
||||||
public function new():Void
|
|
||||||
{
|
|
||||||
shader = new BuildingShader();
|
|
||||||
shader.alphaShit.value = [0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(elapsed:Float):Void
|
|
||||||
{
|
|
||||||
shader.alphaShit.value[0] += elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reset()
|
|
||||||
{
|
|
||||||
shader.alphaShit.value[0] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BuildingShader extends FlxShader
|
|
||||||
{
|
|
||||||
@:glFragmentSource('
|
|
||||||
#pragma header
|
|
||||||
|
|
||||||
uniform float alphaShit;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vec4 color = flixel_texture2D(bitmap, openfl_TextureCoordv);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (color.a > 0.0)
|
|
||||||
color -= alphaShit;
|
|
||||||
|
|
||||||
gl_FragColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
')
|
|
||||||
public function new()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
package funkin.shaderslmfao;
|
|
||||||
|
|
||||||
// STOLEN FROM HAXEFLIXEL DEMO LOL
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
|
||||||
|
|
||||||
enum WiggleEffectType
|
|
||||||
{
|
|
||||||
DREAMY;
|
|
||||||
WAVY;
|
|
||||||
HEAT_WAVE_HORIZONTAL;
|
|
||||||
HEAT_WAVE_VERTICAL;
|
|
||||||
FLAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
class WiggleEffect
|
|
||||||
{
|
|
||||||
public var shader(default, null):WiggleShader = new WiggleShader();
|
|
||||||
public var effectType(default, set):WiggleEffectType = DREAMY;
|
|
||||||
public var waveSpeed(default, set):Float = 0;
|
|
||||||
public var waveFrequency(default, set):Float = 0;
|
|
||||||
public var waveAmplitude(default, set):Float = 0;
|
|
||||||
|
|
||||||
public function new(speed:Float, freq:Float, amplitude:Float, ?effect:WiggleEffectType = DREAMY):Void
|
|
||||||
{
|
|
||||||
shader.uTime.value = [0];
|
|
||||||
this.waveSpeed = speed;
|
|
||||||
this.waveFrequency = freq;
|
|
||||||
this.waveAmplitude = amplitude;
|
|
||||||
this.effectType = effect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(elapsed:Float):Void
|
|
||||||
{
|
|
||||||
shader.uTime.value[0] += elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_effectType(v:WiggleEffectType):WiggleEffectType
|
|
||||||
{
|
|
||||||
effectType = v;
|
|
||||||
shader.effectType.value = [WiggleEffectType.getConstructors().indexOf(Std.string(v))];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_waveSpeed(v:Float):Float
|
|
||||||
{
|
|
||||||
waveSpeed = v;
|
|
||||||
shader.uSpeed.value = [waveSpeed];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_waveFrequency(v:Float):Float
|
|
||||||
{
|
|
||||||
waveFrequency = v;
|
|
||||||
shader.uFrequency.value = [waveFrequency];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_waveAmplitude(v:Float):Float
|
|
||||||
{
|
|
||||||
waveAmplitude = v;
|
|
||||||
shader.uWaveAmplitude.value = [waveAmplitude];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toString()
|
|
||||||
{
|
|
||||||
return 'WiggleEffect(${shader.uTime.value[0]})';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WiggleShader extends FlxShader
|
|
||||||
{
|
|
||||||
@:glFragmentSource('
|
|
||||||
#pragma header
|
|
||||||
//uniform float tx, ty; // x,y waves phase
|
|
||||||
uniform float uTime;
|
|
||||||
|
|
||||||
const int EFFECT_TYPE_DREAMY = 0;
|
|
||||||
const int EFFECT_TYPE_WAVY = 1;
|
|
||||||
const int EFFECT_TYPE_HEAT_WAVE_HORIZONTAL = 2;
|
|
||||||
const int EFFECT_TYPE_HEAT_WAVE_VERTICAL = 3;
|
|
||||||
const int EFFECT_TYPE_FLAG = 4;
|
|
||||||
|
|
||||||
uniform int effectType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How fast the waves move over time
|
|
||||||
*/
|
|
||||||
uniform float uSpeed;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of waves over time
|
|
||||||
*/
|
|
||||||
uniform float uFrequency;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How much the pixels are going to stretch over the waves
|
|
||||||
*/
|
|
||||||
uniform float uWaveAmplitude;
|
|
||||||
|
|
||||||
vec2 sineWave(vec2 pt)
|
|
||||||
{
|
|
||||||
float x = 0.0;
|
|
||||||
float y = 0.0;
|
|
||||||
|
|
||||||
if (effectType == EFFECT_TYPE_DREAMY)
|
|
||||||
{
|
|
||||||
|
|
||||||
float w = 1 / openfl_TextureSize.y;
|
|
||||||
float h = 1 / openfl_TextureSize.x;
|
|
||||||
|
|
||||||
// look mom, I know how to write shaders now
|
|
||||||
|
|
||||||
pt.x = floor(pt.x / h) * h;
|
|
||||||
|
|
||||||
float offsetX = sin(pt.x * uFrequency + uTime * uSpeed) * uWaveAmplitude;
|
|
||||||
pt.y += floor(offsetX / w) * w; // * (pt.y - 1.0); // <- Uncomment to stop bottom part of the screen from moving
|
|
||||||
|
|
||||||
|
|
||||||
pt.y = floor(pt.y / w) * w;
|
|
||||||
|
|
||||||
float offsetY = sin(pt.y * (uFrequency / 2.0) + uTime * (uSpeed / 2.0)) * (uWaveAmplitude / 2.0);
|
|
||||||
pt.x += floor(offsetY / h) * h; // * (pt.y - 1.0); // <- Uncomment to stop bottom part of the screen from moving
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (effectType == EFFECT_TYPE_WAVY)
|
|
||||||
{
|
|
||||||
float offsetY = sin(pt.x * uFrequency + uTime * uSpeed) * uWaveAmplitude;
|
|
||||||
pt.y += offsetY; // * (pt.y - 1.0); // <- Uncomment to stop bottom part of the screen from moving
|
|
||||||
}
|
|
||||||
else if (effectType == EFFECT_TYPE_HEAT_WAVE_HORIZONTAL)
|
|
||||||
{
|
|
||||||
x = sin(pt.x * uFrequency + uTime * uSpeed) * uWaveAmplitude;
|
|
||||||
}
|
|
||||||
else if (effectType == EFFECT_TYPE_HEAT_WAVE_VERTICAL)
|
|
||||||
{
|
|
||||||
y = sin(pt.y * uFrequency + uTime * uSpeed) * uWaveAmplitude;
|
|
||||||
}
|
|
||||||
else if (effectType == EFFECT_TYPE_FLAG)
|
|
||||||
{
|
|
||||||
y = sin(pt.y * uFrequency + 10.0 * pt.x + uTime * uSpeed) * uWaveAmplitude;
|
|
||||||
x = sin(pt.x * uFrequency + 5.0 * pt.y + uTime * uSpeed) * uWaveAmplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vec2(pt.x + x, pt.y + y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vec2 uv = sineWave(openfl_TextureCoordv);
|
|
||||||
gl_FragColor = texture2D(bitmap, uv);
|
|
||||||
}')
|
|
||||||
public function new()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
84
source/funkin/shaderslmfao/WiggleEffectRuntime.hx
Normal file
84
source/funkin/shaderslmfao/WiggleEffectRuntime.hx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package funkin.shaderslmfao;
|
||||||
|
|
||||||
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
import openfl.Assets;
|
||||||
|
|
||||||
|
enum WiggleEffectType
|
||||||
|
{
|
||||||
|
DREAMY; // 0
|
||||||
|
WAVY; // 1
|
||||||
|
HEAT_WAVE_HORIZONTAL; // 2
|
||||||
|
HEAT_WAVE_VERTICAL; // 3
|
||||||
|
FLAG; // 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To use:
|
||||||
|
* 1. Create an instance of the class, specifying speed, frequency, and amplitude.
|
||||||
|
* 2. Call `sprite.shader = wiggleEffect` on the target sprite.
|
||||||
|
* 3. Call the update() method on the instance every frame.
|
||||||
|
*/
|
||||||
|
class WiggleEffectRuntime extends FlxRuntimeShader
|
||||||
|
{
|
||||||
|
public static function getEffectTypeId(v:WiggleEffectType):Int
|
||||||
|
{
|
||||||
|
return WiggleEffectType.getConstructors().indexOf(Std.string(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
public var effectType(default, set):WiggleEffectType = DREAMY;
|
||||||
|
|
||||||
|
function set_effectType(v:WiggleEffectType):WiggleEffectType
|
||||||
|
{
|
||||||
|
this.setInt('effectType', getEffectTypeId(v));
|
||||||
|
return effectType = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var waveSpeed(default, set):Float = 0;
|
||||||
|
|
||||||
|
function set_waveSpeed(v:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('uSpeed', v);
|
||||||
|
return waveSpeed = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var waveFrequency(default, set):Float = 0;
|
||||||
|
|
||||||
|
function set_waveFrequency(v:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('uFrequency', v);
|
||||||
|
return waveFrequency = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var waveAmplitude(default, set):Float = 0;
|
||||||
|
|
||||||
|
function set_waveAmplitude(v:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('uWaveAmplitude', v);
|
||||||
|
return waveAmplitude = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
var time(default, set):Float = 0;
|
||||||
|
|
||||||
|
function set_time(v:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('uTime', v);
|
||||||
|
return time = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function new(speed:Float, freq:Float, amplitude:Float, ?effect:WiggleEffectType = DREAMY):Void
|
||||||
|
{
|
||||||
|
super(Assets.getText(Paths.frag('wiggle')));
|
||||||
|
|
||||||
|
// These values may not propagate to the shader until later.
|
||||||
|
this.waveSpeed = speed;
|
||||||
|
this.waveFrequency = freq;
|
||||||
|
this.waveAmplitude = amplitude;
|
||||||
|
this.effectType = effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(elapsed:Float)
|
||||||
|
{
|
||||||
|
// The setter tied to this value automatically propagates the value to the shader.
|
||||||
|
this.time += elapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package funkin.ui;
|
package funkin.ui;
|
||||||
|
|
||||||
import funkin.ui.MenuList;
|
|
||||||
import flixel.graphics.frames.FlxAtlasFrames;
|
import flixel.graphics.frames.FlxAtlasFrames;
|
||||||
|
import funkin.ui.MenuList;
|
||||||
|
|
||||||
typedef AtlasAsset = flixel.util.typeLimit.OneOfTwo<String, FlxAtlasFrames>;
|
typedef AtlasAsset = flixel.util.typeLimit.OneOfTwo<String, FlxAtlasFrames>;
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
|
||||||
{
|
{
|
||||||
super(navControls, wrapMode);
|
super(navControls, wrapMode);
|
||||||
|
|
||||||
if (Std.is(atlas, String))
|
if (Std.isOfType(atlas, String))
|
||||||
this.atlas = Paths.getSparrowAtlas(cast atlas);
|
this.atlas = Paths.getSparrowAtlas(cast atlas);
|
||||||
else
|
else
|
||||||
this.atlas = cast atlas;
|
this.atlas = cast atlas;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class OptionsState extends MusicBeatState
|
||||||
menuBG.scrollFactor.set(0, 0);
|
menuBG.scrollFactor.set(0, 0);
|
||||||
add(menuBG);
|
add(menuBG);
|
||||||
|
|
||||||
var options = addPage(Options, new OptionsMenu(false));
|
var options = addPage(Options, new OptionsMenu());
|
||||||
var preferences = addPage(Preferences, new PreferencesMenu());
|
var preferences = addPage(Preferences, new PreferencesMenu());
|
||||||
var controls = addPage(Controls, new ControlsMenu());
|
var controls = addPage(Controls, new ControlsMenu());
|
||||||
|
|
||||||
|
|
@ -167,22 +167,14 @@ class OptionsMenu extends Page
|
||||||
{
|
{
|
||||||
var items:TextMenuList;
|
var items:TextMenuList;
|
||||||
|
|
||||||
public function new(showDonate:Bool)
|
public function new()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
add(items = new TextMenuList());
|
add(items = new TextMenuList());
|
||||||
createItem("PREFERENCES", function() switchPage(Preferences));
|
createItem("PREFERENCES", function() switchPage(Preferences));
|
||||||
createItem("CONTROLS", function() switchPage(Controls));
|
createItem("CONTROLS", function() switchPage(Controls));
|
||||||
// createItem("COLORS", function() switchPage(Colors));
|
|
||||||
|
|
||||||
#if CAN_OPEN_LINKS
|
|
||||||
if (showDonate)
|
|
||||||
{
|
|
||||||
var hasPopupBlocker = #if web true #else false #end;
|
|
||||||
createItem("DONATE", selectDonate, hasPopupBlocker);
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
if (NGio.isLoggedIn)
|
if (NGio.isLoggedIn)
|
||||||
createItem("LOGOUT", selectLogout);
|
createItem("LOGOUT", selectLogout);
|
||||||
|
|
@ -215,13 +207,6 @@ class OptionsMenu extends Page
|
||||||
return items.length > 2;
|
return items.length > 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CAN_OPEN_LINKS
|
|
||||||
function selectDonate()
|
|
||||||
{
|
|
||||||
WindowUtil.openURL(Constants.URL_ITCH);
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
function selectLogin()
|
function selectLogin()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
95
source/funkin/ui/debug/DebugMenuSubState.hx
Normal file
95
source/funkin/ui/debug/DebugMenuSubState.hx
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
package funkin.ui.debug;
|
||||||
|
|
||||||
|
import flixel.FlxObject;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import funkin.MusicBeatSubstate;
|
||||||
|
import funkin.ui.TextMenuList;
|
||||||
|
import funkin.ui.debug.charting.ChartEditorState;
|
||||||
|
|
||||||
|
class DebugMenuSubState extends MusicBeatSubstate
|
||||||
|
{
|
||||||
|
var items:TextMenuList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Camera focus point
|
||||||
|
*/
|
||||||
|
var camFocusPoint:FlxObject;
|
||||||
|
|
||||||
|
override function create()
|
||||||
|
{
|
||||||
|
super.create();
|
||||||
|
|
||||||
|
// Create an object for the camera to track.
|
||||||
|
camFocusPoint = new FlxObject(0, 0);
|
||||||
|
add(camFocusPoint);
|
||||||
|
|
||||||
|
// Follow the camera focus as we scroll.
|
||||||
|
FlxG.camera.follow(camFocusPoint, null, 0.06);
|
||||||
|
|
||||||
|
// Create the green background.
|
||||||
|
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
||||||
|
menuBG.color = 0xFF4CAF50;
|
||||||
|
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||||
|
menuBG.updateHitbox();
|
||||||
|
menuBG.screenCenter();
|
||||||
|
menuBG.scrollFactor.set(0, 0);
|
||||||
|
add(menuBG);
|
||||||
|
|
||||||
|
// Create the list for menu items.
|
||||||
|
items = new TextMenuList();
|
||||||
|
// Move the camera when the menu is scrolled.
|
||||||
|
items.onChange.add(onMenuChange);
|
||||||
|
add(items);
|
||||||
|
|
||||||
|
// Create each menu item.
|
||||||
|
// Call onMenuChange when the first item is created to move the camera .
|
||||||
|
onMenuChange(createItem("CHART EDITOR", openChartEditor));
|
||||||
|
createItem("ANIMATION EDITOR", openAnimationEditor);
|
||||||
|
createItem("STAGE EDITOR", openStageEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMenuChange(selected:TextMenuItem)
|
||||||
|
{
|
||||||
|
camFocusPoint.setPosition(selected.x + selected.width / 2, selected.y + selected.height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function update(elapsed:Float)
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
if (controls.BACK)
|
||||||
|
{
|
||||||
|
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||||
|
exitDebugMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createItem(name:String, callback:Void->Void, fireInstantly = false)
|
||||||
|
{
|
||||||
|
var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback);
|
||||||
|
item.fireInstantly = fireInstantly;
|
||||||
|
item.screenCenter(X);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openChartEditor()
|
||||||
|
{
|
||||||
|
FlxG.switchState(new ChartEditorState());
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAnimationEditor()
|
||||||
|
{
|
||||||
|
trace('Animation Editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openStageEditor()
|
||||||
|
{
|
||||||
|
trace('Stage Editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitDebugMenu()
|
||||||
|
{
|
||||||
|
// TODO: Add a transition?
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
604
source/funkin/ui/debug/charting/ChartEditorCommand.hx
Normal file
604
source/funkin/ui/debug/charting/ChartEditorCommand.hx
Normal file
|
|
@ -0,0 +1,604 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.play.song.SongData.SongEventData;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
import funkin.play.song.SongDataUtils;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions in the chart editor are backed by the Command pattern
|
||||||
|
* (see Bob Nystrom's book "Game Programming Patterns" for more info)
|
||||||
|
*
|
||||||
|
* To make a function compatible with the undo/redo history, create a new class
|
||||||
|
* that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())`
|
||||||
|
*/
|
||||||
|
interface ChartEditorCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calling this function should perform the action that this command represents.
|
||||||
|
* @param state The ChartEditorState to perform the action on.
|
||||||
|
*/
|
||||||
|
public function execute(state:ChartEditorState):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calling this function should perform the inverse of the action that this command represents,
|
||||||
|
* effectively undoing the action.
|
||||||
|
* @param state The ChartEditorState to undo the action on.
|
||||||
|
*/
|
||||||
|
public function undo(state:ChartEditorState):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a short description of the action (for the UI).
|
||||||
|
* For example, return `Add Left Note` to display `Undo Add Left Note` in the menu.
|
||||||
|
*/
|
||||||
|
public function toString():String;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var appendToSelection:Bool;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>, appendToSelection:Bool = false)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
this.appendToSelection = appendToSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in notes)
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appendToSelection)
|
||||||
|
{
|
||||||
|
state.currentSelection = state.currentSelection.concat(notes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.currentSelection = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
|
state.currentSelection = [];
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
if (notes.length == 1)
|
||||||
|
{
|
||||||
|
var dir:String = notes[0].getDirectionName();
|
||||||
|
return 'Add $dir Note';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Add ${notes.length} Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
|
state.currentSelection = [];
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in notes)
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData.push(note);
|
||||||
|
}
|
||||||
|
state.currentSelection = notes;
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
if (notes.length == 1 && notes[0] != null)
|
||||||
|
{
|
||||||
|
var dir:String = notes[0].getDirectionName();
|
||||||
|
return 'Remove $dir Note';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Remove ${notes.length} Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwitchDifficultyCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var prevDifficulty:String;
|
||||||
|
private var newDifficulty:String;
|
||||||
|
private var prevVariation:String;
|
||||||
|
private var newVariation:String;
|
||||||
|
|
||||||
|
public function new(prevDifficulty:String, newDifficulty:String, prevVariation:String, newVariation:String)
|
||||||
|
{
|
||||||
|
this.prevDifficulty = prevDifficulty;
|
||||||
|
this.newDifficulty = newDifficulty;
|
||||||
|
this.prevVariation = prevVariation;
|
||||||
|
this.newVariation = newVariation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.selectedVariation = newVariation != null ? newVariation : prevVariation;
|
||||||
|
state.selectedDifficulty = newDifficulty != null ? newDifficulty : prevDifficulty;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.selectedVariation = prevVariation != null ? prevVariation : newVariation;
|
||||||
|
state.selectedDifficulty = prevDifficulty != null ? prevDifficulty : newDifficulty;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Switch Difficulty';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds one or more notes to the selection.
|
||||||
|
*/
|
||||||
|
class SelectNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in this.notes)
|
||||||
|
{
|
||||||
|
state.currentSelection.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = SongDataUtils.subtractNotes(state.currentSelection, this.notes);
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
if (notes.length == 1)
|
||||||
|
{
|
||||||
|
var dir:String = notes[0].getDirectionName();
|
||||||
|
return 'Select $dir Note';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Select ${notes.length} Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeselectNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = SongDataUtils.subtractNotes(state.currentSelection, this.notes);
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in this.notes)
|
||||||
|
{
|
||||||
|
state.currentSelection.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
if (notes.length == 1)
|
||||||
|
{
|
||||||
|
var dir:String = notes[0].getDirectionName();
|
||||||
|
return 'Deselect $dir Note';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Deselect ${notes.length} Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the selection rather than appends it.
|
||||||
|
* Deselects any notes that are not in the new selection.
|
||||||
|
*/
|
||||||
|
class SetNoteSelectionCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var previousSelection:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>, ?previousSelection:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = notes;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = previousSelection;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Select ${notes.length} Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectAllNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var previousSelection:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(?previousSelection:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = state.currentSongChartNoteData;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = previousSelection;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Select All Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvertSelectedNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var previousSelection:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(?previousSelection:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousSelection);
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = previousSelection;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Invert Selected Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeselectAllNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var previousSelection:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(?previousSelection:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = [];
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSelection = previousSelection;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Deselect All Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CutNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
// Copy the notes.
|
||||||
|
SongDataUtils.writeNotesToClipboard(SongDataUtils.buildClipboard(notes));
|
||||||
|
|
||||||
|
// Delete the notes.
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
|
state.currentSelection = [];
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||||
|
state.currentSelection = notes;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var len:Int = notes.length;
|
||||||
|
return 'Cut $len Notes to Clipboard';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlipNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var flippedNotes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
// Delete the notes.
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
|
|
||||||
|
// Add the flipped notes.
|
||||||
|
flippedNotes = SongDataUtils.flipNotes(notes);
|
||||||
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
|
||||||
|
|
||||||
|
state.currentSelection = flippedNotes;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
|
||||||
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||||
|
|
||||||
|
state.currentSelection = notes;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var len:Int = notes.length;
|
||||||
|
return 'Flip $len Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasteNotesCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var targetTimestamp:Float;
|
||||||
|
// Notes we added with this command, for undo.
|
||||||
|
private var addedNotes:Array<SongNoteData>;
|
||||||
|
|
||||||
|
public function new(targetTimestamp:Float)
|
||||||
|
{
|
||||||
|
this.targetTimestamp = targetTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
var currentClipboard:Array<SongNoteData> = SongDataUtils.readNotesFromClipboard();
|
||||||
|
|
||||||
|
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard, Std.int(targetTimestamp));
|
||||||
|
|
||||||
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
||||||
|
state.currentSelection = addedNotes.copy();
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
||||||
|
state.currentSelection = [];
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var currentClipboard:Array<SongNoteData> = SongDataUtils.readNotesFromClipboard();
|
||||||
|
return 'Paste ${currentClipboard.length} Notes from Clipboard';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddEventsCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
private var appendToSelection:Bool;
|
||||||
|
|
||||||
|
public function new(events:Array<SongEventData>, ?appendToSelection:Bool = false)
|
||||||
|
{
|
||||||
|
this.events = events;
|
||||||
|
this.appendToSelection = appendToSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
||||||
|
// TODO: Allow selecting events.
|
||||||
|
// state.currentSelection = events;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
|
|
||||||
|
state.currentSelection = [];
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var len:Int = events.length;
|
||||||
|
return 'Add $len Events';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtendNoteLengthCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var note:SongNoteData;
|
||||||
|
private var oldLength:Float;
|
||||||
|
private var newLength:Float;
|
||||||
|
|
||||||
|
public function new(note:SongNoteData, newLength:Float)
|
||||||
|
{
|
||||||
|
this.note = note;
|
||||||
|
this.oldLength = note.length;
|
||||||
|
this.newLength = newLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
note.length = newLength;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
note.length = oldLength;
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Extend Note Length';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
class ChartEditorDialogHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
296
source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx
Normal file
296
source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.graphics.frames.FlxFramesCollection;
|
||||||
|
import flixel.graphics.frames.FlxTileFrames;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A note sprite that can be used to display a note in a chart.
|
||||||
|
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||||
|
*/
|
||||||
|
class ChartEditorNoteSprite extends FlxSprite
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The note data that this sprite represents.
|
||||||
|
* You can set this to null to kill the sprite and flag it for recycling.
|
||||||
|
*/
|
||||||
|
public var noteData(default, set):SongNoteData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The note skin that this sprite displays.
|
||||||
|
*/
|
||||||
|
public var noteSkin(default, set):String = 'Normal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This note is the previous sprite in a sustain chain.
|
||||||
|
*/
|
||||||
|
public var parentNoteSprite(default, set):ChartEditorNoteSprite = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This note is the next sprite in a sustain chain.
|
||||||
|
*/
|
||||||
|
public var childNoteSprite(default, set):ChartEditorNoteSprite = null;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (noteFrameCollection == null)
|
||||||
|
{
|
||||||
|
initFrameCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frames = noteFrameCollection;
|
||||||
|
|
||||||
|
// Initialize all the animations, not just the one we're going to use immediately,
|
||||||
|
// so that later we can reuse the sprite without having to initialize more animations during scrolling.
|
||||||
|
this.animation.addByPrefix('tapLeftNormal', 'purple instance');
|
||||||
|
this.animation.addByPrefix('tapDownNormal', 'blue instance');
|
||||||
|
this.animation.addByPrefix('tapUpNormal', 'green instance');
|
||||||
|
this.animation.addByPrefix('tapRightNormal', 'red instance');
|
||||||
|
|
||||||
|
this.animation.addByPrefix('holdLeftNormal', 'LeftHoldPiece');
|
||||||
|
this.animation.addByPrefix('holdDownNormal', 'DownHoldPiece');
|
||||||
|
this.animation.addByPrefix('holdUpNormal', 'UpHoldPiece');
|
||||||
|
this.animation.addByPrefix('holdRightNormal', 'RightHoldPiece');
|
||||||
|
|
||||||
|
this.animation.addByPrefix('holdEndLeftNormal', 'LeftHoldEnd');
|
||||||
|
this.animation.addByPrefix('holdEndDownNormal', 'DownHoldEnd');
|
||||||
|
this.animation.addByPrefix('holdEndUpNormal', 'UpHoldEnd');
|
||||||
|
this.animation.addByPrefix('holdEndRightNormal', 'RightHoldEnd');
|
||||||
|
|
||||||
|
this.animation.addByPrefix('tapLeftPixel', 'pixel4');
|
||||||
|
this.animation.addByPrefix('tapDownPixel', 'pixel5');
|
||||||
|
this.animation.addByPrefix('tapUpPixel', 'pixel6');
|
||||||
|
this.animation.addByPrefix('tapRightPixel', 'pixel7');
|
||||||
|
}
|
||||||
|
|
||||||
|
static var noteFrameCollection:FlxFramesCollection = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We load all the note frames once, then reuse them.
|
||||||
|
*/
|
||||||
|
static function initFrameCollection():Void
|
||||||
|
{
|
||||||
|
noteFrameCollection = new FlxFramesCollection(null, ATLAS, null);
|
||||||
|
|
||||||
|
// TODO: Automatically iterate over the list of note skins.
|
||||||
|
|
||||||
|
// Normal notes
|
||||||
|
var frameCollectionNormal = Paths.getSparrowAtlas('NOTE_assets');
|
||||||
|
|
||||||
|
for (frame in frameCollectionNormal.frames)
|
||||||
|
{
|
||||||
|
noteFrameCollection.pushFrame(frame);
|
||||||
|
}
|
||||||
|
var frameCollectionNormal2 = Paths.getSparrowAtlas('NoteHoldNormal');
|
||||||
|
|
||||||
|
for (frame in frameCollectionNormal2.frames)
|
||||||
|
{
|
||||||
|
noteFrameCollection.pushFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pixel notes
|
||||||
|
var graphicPixel = FlxG.bitmap.add(Paths.image('weeb/pixelUI/arrows-pixels', 'week6'), false, null);
|
||||||
|
if (graphicPixel == null)
|
||||||
|
trace('ERROR: Could not load graphic: ' + Paths.image('weeb/pixelUI/arrows-pixels', 'week6'));
|
||||||
|
var frameCollectionPixel = FlxTileFrames.fromGraphic(graphicPixel, new FlxPoint(17, 17));
|
||||||
|
for (i in 0...frameCollectionPixel.frames.length)
|
||||||
|
{
|
||||||
|
var frame = frameCollectionPixel.frames[i];
|
||||||
|
|
||||||
|
frame.name = 'pixel' + i;
|
||||||
|
noteFrameCollection.pushFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_noteData(value:SongNoteData):SongNoteData
|
||||||
|
{
|
||||||
|
this.noteData = value;
|
||||||
|
|
||||||
|
if (this.noteData == null)
|
||||||
|
{
|
||||||
|
// Disown parent.
|
||||||
|
this.parentNoteSprite = null;
|
||||||
|
if (this.childNoteSprite != null)
|
||||||
|
{
|
||||||
|
// Kill all children and disown them.
|
||||||
|
this.childNoteSprite.noteData = null;
|
||||||
|
this.childNoteSprite = null;
|
||||||
|
}
|
||||||
|
this.kill();
|
||||||
|
return this.noteData;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
// Update the animation to match the note data.
|
||||||
|
// Animation is updated first so size is correct before updating position.
|
||||||
|
playNoteAnimation();
|
||||||
|
|
||||||
|
// Update the position to match the note data.
|
||||||
|
setNotePosition();
|
||||||
|
|
||||||
|
return this.noteData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_noteSkin(value:String):String
|
||||||
|
{
|
||||||
|
// Don't update if the skin hasn't changed.
|
||||||
|
if (value == this.noteSkin)
|
||||||
|
return this.noteSkin;
|
||||||
|
|
||||||
|
this.noteSkin = value;
|
||||||
|
|
||||||
|
// Make sure to update the graphic to match the note skin.
|
||||||
|
playNoteAnimation();
|
||||||
|
|
||||||
|
return this.noteSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNotePosition()
|
||||||
|
{
|
||||||
|
var cursorColumn:Int = this.noteData.data;
|
||||||
|
|
||||||
|
if (cursorColumn < 0)
|
||||||
|
cursorColumn = 0;
|
||||||
|
if (cursorColumn >= (ChartEditorState.STRUMLINE_SIZE * 2 + 1))
|
||||||
|
{
|
||||||
|
cursorColumn = (ChartEditorState.STRUMLINE_SIZE * 2 + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invert player and opponent columns.
|
||||||
|
if (cursorColumn >= ChartEditorState.STRUMLINE_SIZE)
|
||||||
|
{
|
||||||
|
cursorColumn -= ChartEditorState.STRUMLINE_SIZE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorColumn += ChartEditorState.STRUMLINE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentNoteSprite == null)
|
||||||
|
{
|
||||||
|
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
|
||||||
|
|
||||||
|
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
|
||||||
|
// TODO: stepTime doesn't account for fluctuating BPMs.
|
||||||
|
this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If this is a hold note, we need to adjust the position to be centered.
|
||||||
|
if (parentNoteSprite.parentNoteSprite == null)
|
||||||
|
{
|
||||||
|
this.x = parentNoteSprite.x;
|
||||||
|
this.x += (ChartEditorState.GRID_SIZE / 2);
|
||||||
|
this.x -= this.width / 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.x = parentNoteSprite.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.y = parentNoteSprite.y;
|
||||||
|
if (parentNoteSprite.parentNoteSprite == null)
|
||||||
|
{
|
||||||
|
this.y += parentNoteSprite.height / 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.y += parentNoteSprite.height - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_parentNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
|
||||||
|
{
|
||||||
|
this.parentNoteSprite = value;
|
||||||
|
|
||||||
|
if (this.parentNoteSprite != null)
|
||||||
|
{
|
||||||
|
this.noteData = this.parentNoteSprite.noteData;
|
||||||
|
this.noteSkin = this.parentNoteSprite.noteSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.parentNoteSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_childNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
|
||||||
|
{
|
||||||
|
this.childNoteSprite = value;
|
||||||
|
|
||||||
|
if (this.parentNoteSprite != null)
|
||||||
|
{
|
||||||
|
this.noteData = this.parentNoteSprite.noteData;
|
||||||
|
this.noteSkin = this.parentNoteSprite.noteSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.childNoteSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
function playNoteAnimation()
|
||||||
|
{
|
||||||
|
// Decide whether to display a note or a sustain.
|
||||||
|
var baseAnimationName:String = 'tap';
|
||||||
|
if (this.parentNoteSprite != null)
|
||||||
|
baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
|
||||||
|
|
||||||
|
// Play the appropriate animation for the type, direction, and skin.
|
||||||
|
var animationName = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteSkin}';
|
||||||
|
|
||||||
|
this.animation.play(animationName);
|
||||||
|
|
||||||
|
// Resize note.
|
||||||
|
|
||||||
|
switch (baseAnimationName)
|
||||||
|
{
|
||||||
|
case 'tap':
|
||||||
|
this.setGraphicSize(0, ChartEditorState.GRID_SIZE);
|
||||||
|
case 'hold':
|
||||||
|
if (parentNoteSprite.parentNoteSprite == null)
|
||||||
|
{
|
||||||
|
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), ChartEditorState.GRID_SIZE);
|
||||||
|
}
|
||||||
|
case 'holdEnd':
|
||||||
|
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
|
||||||
|
}
|
||||||
|
this.updateHitbox();
|
||||||
|
|
||||||
|
// TODO: Make this an attribute of the note skin.
|
||||||
|
this.antialiasing = (noteSkin != 'Pixel');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether this note (or its parent) is currently visible.
|
||||||
|
*/
|
||||||
|
public function isNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
||||||
|
{
|
||||||
|
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
|
||||||
|
|
||||||
|
if (!outsideViewArea)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if this note's parent or child is visible.
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBaseNoteSprite()
|
||||||
|
{
|
||||||
|
if (this.parentNoteSprite == null)
|
||||||
|
return this;
|
||||||
|
else
|
||||||
|
return this.parentNoteSprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
2552
source/funkin/ui/debug/charting/ChartEditorState.hx
Normal file
2552
source/funkin/ui/debug/charting/ChartEditorState.hx
Normal file
File diff suppressed because it is too large
Load diff
172
source/funkin/ui/debug/charting/ChartEditorThemeHandler.hx
Normal file
172
source/funkin/ui/debug/charting/ChartEditorThemeHandler.hx
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import flixel.addons.display.FlxGridOverlay;
|
||||||
|
import flixel.addons.display.FlxSliceSprite;
|
||||||
|
import flixel.math.FlxRect;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
import openfl.geom.Rectangle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available themes for the chart editor state.
|
||||||
|
*/
|
||||||
|
enum ChartEditorTheme
|
||||||
|
{
|
||||||
|
Light;
|
||||||
|
Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static functions which handle building themed UI elements for a provided ChartEditorState.
|
||||||
|
*/
|
||||||
|
class ChartEditorThemeHandler
|
||||||
|
{
|
||||||
|
// TODO: There's probably a better system of organization for these colors.
|
||||||
|
// An enum of typedefs or something?
|
||||||
|
// ================================
|
||||||
|
static final BACKGROUND_COLOR_LIGHT:FlxColor = 0xFF673AB7;
|
||||||
|
static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF673AB7;
|
||||||
|
|
||||||
|
// Color 1 of the grid pattern. Alternates with Color 2.
|
||||||
|
static final GRID_COLOR_1_LIGHT:FlxColor = 0xFFE7E6E6;
|
||||||
|
static final GRID_COLOR_1_DARK:FlxColor = 0xFF181919;
|
||||||
|
|
||||||
|
// Color 2 of the grid pattern. Alternates with Color 1.
|
||||||
|
static final GRID_COLOR_2_LIGHT:FlxColor = 0xFFD9D5D5;
|
||||||
|
static final GRID_COLOR_2_DARK:FlxColor = 0xFF262A2A;
|
||||||
|
|
||||||
|
// Vertical divider between characters.
|
||||||
|
static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF000000;
|
||||||
|
static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
|
||||||
|
static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = 2;
|
||||||
|
|
||||||
|
// Horizontal divider between measures.
|
||||||
|
static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF000000;
|
||||||
|
static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
|
||||||
|
static final GRID_MEASURE_DIVIDER_WIDTH:Float = 2;
|
||||||
|
|
||||||
|
// Border on the square highlighting selected notes.
|
||||||
|
static final SELECTION_SQUARE_BORDER_COLOR_LIGHT:FlxColor = 0xFF339933;
|
||||||
|
static final SELECTION_SQUARE_BORDER_COLOR_DARK:FlxColor = 0xFF339933;
|
||||||
|
static final SELECTION_SQUARE_BORDER_WIDTH:Int = 1;
|
||||||
|
|
||||||
|
// Fill on the square highlighting selected notes.
|
||||||
|
// Make sure this is transparent so you can see the notes underneath.
|
||||||
|
static final SELECTION_SQUARE_FILL_COLOR_LIGHT:FlxColor = 0x4033FF33;
|
||||||
|
static final SELECTION_SQUARE_FILL_COLOR_DARK:FlxColor = 0x4033FF33;
|
||||||
|
|
||||||
|
// TODO: Un-hardcode these to be based on time signature.
|
||||||
|
static final STEPS_PER_BEAT:Int = 4;
|
||||||
|
static final BEATS_PER_MEASURE:Int = 4;
|
||||||
|
|
||||||
|
public static function updateTheme(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
updateBackground(state);
|
||||||
|
updateGridBitmap(state);
|
||||||
|
updateSelectionSquare(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function updateBackground(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.menuBG.color = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case ChartEditorTheme.Light: BACKGROUND_COLOR_LIGHT;
|
||||||
|
case ChartEditorTheme.Dark: BACKGROUND_COLOR_DARK;
|
||||||
|
default: BACKGROUND_COLOR_LIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the checkerboard background image of the chart editor, and adds dividing lines to it.
|
||||||
|
* @param dark Whether to draw the grid in a dark color instead of a light one.
|
||||||
|
*/
|
||||||
|
static function updateGridBitmap(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
var gridColor1:FlxColor = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case Light: GRID_COLOR_1_LIGHT;
|
||||||
|
case Dark: GRID_COLOR_1_DARK;
|
||||||
|
default: GRID_COLOR_1_LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
var gridColor2:FlxColor = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case Light: GRID_COLOR_2_LIGHT;
|
||||||
|
case Dark: GRID_COLOR_2_DARK;
|
||||||
|
default: GRID_COLOR_2_LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw the base grid.
|
||||||
|
|
||||||
|
// 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall.
|
||||||
|
// This gets reused to fill the screen.
|
||||||
|
var gridWidth = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2 + 1);
|
||||||
|
var gridHeight = ChartEditorState.GRID_SIZE * (STEPS_PER_BEAT * BEATS_PER_MEASURE);
|
||||||
|
state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1,
|
||||||
|
gridColor2);
|
||||||
|
|
||||||
|
// Draw dividers between the strumlines.
|
||||||
|
|
||||||
|
var gridStrumlineDividerColor:FlxColor = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case Light: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT;
|
||||||
|
case Dark: GRID_STRUMLINE_DIVIDER_COLOR_DARK;
|
||||||
|
default: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Divider at 1 * (Strumline Size)
|
||||||
|
var dividerLineAX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
|
||||||
|
state.gridBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
|
||||||
|
// Divider at 2 * (Strumline Size)
|
||||||
|
var dividerLineBX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
|
||||||
|
state.gridBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
|
||||||
|
|
||||||
|
// Draw dividers between the measures.
|
||||||
|
|
||||||
|
var gridMeasureDividerColor:FlxColor = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case Light: GRID_MEASURE_DIVIDER_COLOR_LIGHT;
|
||||||
|
case Dark: GRID_MEASURE_DIVIDER_COLOR_DARK;
|
||||||
|
default: GRID_MEASURE_DIVIDER_COLOR_LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Divider at top
|
||||||
|
state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
|
||||||
|
// Divider at bottom
|
||||||
|
var dividerLineBY = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2);
|
||||||
|
state.gridBitmap.fillRect(new Rectangle(0, dividerLineBY, GRID_MEASURE_DIVIDER_WIDTH / 2, state.gridBitmap.height), gridMeasureDividerColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function updateSelectionSquare(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
var selectionSquareBorderColor:FlxColor = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case Light: SELECTION_SQUARE_BORDER_COLOR_LIGHT;
|
||||||
|
case Dark: SELECTION_SQUARE_BORDER_COLOR_DARK;
|
||||||
|
default: SELECTION_SQUARE_BORDER_COLOR_LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
var selectionSquareFillColor:FlxColor = switch (state.currentTheme)
|
||||||
|
{
|
||||||
|
case Light: SELECTION_SQUARE_FILL_COLOR_LIGHT;
|
||||||
|
case Dark: SELECTION_SQUARE_FILL_COLOR_DARK;
|
||||||
|
default: SELECTION_SQUARE_FILL_COLOR_LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
state.selectionSquareBitmap = new BitmapData(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, true);
|
||||||
|
|
||||||
|
state.selectionSquareBitmap.fillRect(new Rectangle(0, 0, ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE), selectionSquareBorderColor);
|
||||||
|
state.selectionSquareBitmap.fillRect(new Rectangle(SELECTION_SQUARE_BORDER_WIDTH, SELECTION_SQUARE_BORDER_WIDTH,
|
||||||
|
ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2), ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2)),
|
||||||
|
selectionSquareFillColor);
|
||||||
|
|
||||||
|
state.selectionBoxSprite = new FlxSliceSprite(state.selectionSquareBitmap,
|
||||||
|
new FlxRect(SELECTION_SQUARE_BORDER_WIDTH
|
||||||
|
+ 4, SELECTION_SQUARE_BORDER_WIDTH
|
||||||
|
+ 4,
|
||||||
|
ChartEditorState.GRID_SIZE
|
||||||
|
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 8), ChartEditorState.GRID_SIZE
|
||||||
|
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 8)),
|
||||||
|
32, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
184
source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
Normal file
184
source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import haxe.ui.components.DropDown;
|
||||||
|
import haxe.ui.containers.Group;
|
||||||
|
import haxe.ui.containers.dialogs.Dialog;
|
||||||
|
import haxe.ui.events.UIEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available tools for the chart editor state.
|
||||||
|
*/
|
||||||
|
enum ChartEditorToolMode
|
||||||
|
{
|
||||||
|
Select;
|
||||||
|
Place;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChartEditorToolboxHandler
|
||||||
|
{
|
||||||
|
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
|
||||||
|
{
|
||||||
|
if (shown)
|
||||||
|
showToolbox(state, id);
|
||||||
|
else
|
||||||
|
hideToolbox(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function showToolbox(state:ChartEditorState, id:String)
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
|
if (toolbox == null)
|
||||||
|
toolbox = initToolbox(state, id);
|
||||||
|
|
||||||
|
if (toolbox != null)
|
||||||
|
{
|
||||||
|
toolbox.showDialog(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('ChartEditorToolboxHandler.showToolbox() - Could not retrieve toolbox: $id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function hideToolbox(state:ChartEditorState, id:String):Void
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
|
if (toolbox == null)
|
||||||
|
toolbox = initToolbox(state, id);
|
||||||
|
|
||||||
|
if (toolbox != null)
|
||||||
|
{
|
||||||
|
toolbox.hideDialog(DialogButton.CANCEL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('ChartEditorToolboxHandler.hideToolbox() - Could not retrieve toolbox: $id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function minimizeToolbox(state:ChartEditorState, id:String):Void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maximizeToolbox(state:ChartEditorState, id:String):Void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function initToolbox(state:ChartEditorState, id:String):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = null;
|
||||||
|
switch (id)
|
||||||
|
{
|
||||||
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||||
|
toolbox = buildToolboxToolsLayout(state);
|
||||||
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
|
||||||
|
toolbox = buildToolboxNoteDataLayout(state);
|
||||||
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
|
||||||
|
toolbox = buildToolboxEventDataLayout(state);
|
||||||
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_SONGDATA_LAYOUT:
|
||||||
|
toolbox = buildToolboxSongDataLayout(state);
|
||||||
|
default:
|
||||||
|
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
|
||||||
|
toolbox = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.activeToolboxes.set(id, toolbox);
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxToolsLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 50;
|
||||||
|
toolbox.y = 50;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUISelected('menubarItemToggleToolboxTools', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolsGroup:Group = toolbox.findComponent("toolboxToolsGroup", Group);
|
||||||
|
|
||||||
|
toolsGroup.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
|
switch (event.target.id)
|
||||||
|
{
|
||||||
|
case 'toolboxToolsGroupSelect':
|
||||||
|
state.currentToolMode = ChartEditorToolMode.Select;
|
||||||
|
case 'toolboxToolsGroupPlace':
|
||||||
|
state.currentToolMode = ChartEditorToolMode.Place;
|
||||||
|
default:
|
||||||
|
trace('ChartEditorToolboxHandler.buildToolboxToolsLayout() - Unknown toolbox tool selected: $event.target.id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxNoteDataLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 75;
|
||||||
|
toolbox.y = 100;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUISelected('menubarItemToggleToolboxNotes', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolboxNotesNoteKind:DropDown = toolbox.findComponent("toolboxNotesNoteKind", DropDown);
|
||||||
|
|
||||||
|
toolboxNotesNoteKind.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
|
state.selectedNoteKind = event.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxEventDataLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 100;
|
||||||
|
toolbox.y = 150;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUISelected('menubarItemToggleToolboxEvents', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxSongDataLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_SONGDATA_LAYOUT);
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 950;
|
||||||
|
toolbox.y = 50;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUISelected('menubarItemToggleToolboxSong', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildDialog(state:ChartEditorState, id:String):Dialog
|
||||||
|
{
|
||||||
|
var dialog:Dialog = cast state.buildComponent(id);
|
||||||
|
dialog.destroyOnClose = false;
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
112
source/funkin/ui/haxeui/HaxeUIState.hx
Normal file
112
source/funkin/ui/haxeui/HaxeUIState.hx
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
package funkin.ui.haxeui;
|
||||||
|
|
||||||
|
import haxe.ui.RuntimeComponentBuilder;
|
||||||
|
import haxe.ui.core.Component;
|
||||||
|
import haxe.ui.core.Screen;
|
||||||
|
import haxe.ui.events.MouseEvent;
|
||||||
|
import lime.app.Application;
|
||||||
|
|
||||||
|
class HaxeUIState extends MusicBeatState
|
||||||
|
{
|
||||||
|
public var component:Component;
|
||||||
|
|
||||||
|
var _componentKey:String;
|
||||||
|
|
||||||
|
public function new(key:String)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
_componentKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function create()
|
||||||
|
{
|
||||||
|
super.create();
|
||||||
|
|
||||||
|
if (component == null)
|
||||||
|
component = buildComponent(_componentKey);
|
||||||
|
if (component != null)
|
||||||
|
add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildComponent(assetPath:String)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return RuntimeComponentBuilder.fromAsset(assetPath);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
Application.current.window.alert('Error building component "$assetPath": $e', 'HaxeUI Parsing Error');
|
||||||
|
// trace('[ERROR] Failed to build component from asset: ' + assetPath);
|
||||||
|
// trace(e);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active context menu.
|
||||||
|
*/
|
||||||
|
public var contextMenu:Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when right clicking on a component, to display a context menu.
|
||||||
|
*/
|
||||||
|
function showContextMenu(assetPath:String, xPos:Float, yPos:Float):Component
|
||||||
|
{
|
||||||
|
if (contextMenu != null)
|
||||||
|
contextMenu.destroy();
|
||||||
|
|
||||||
|
contextMenu = buildComponent(assetPath);
|
||||||
|
|
||||||
|
if (contextMenu != null)
|
||||||
|
{
|
||||||
|
// Move the context menu to the mouse position.
|
||||||
|
contextMenu.left = xPos;
|
||||||
|
contextMenu.top = yPos;
|
||||||
|
Screen.instance.addComponent(contextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a context menu to display when right clicking.
|
||||||
|
* @param component Only display the menu when clicking this component. If null, display the menu when right clicking anywhere.
|
||||||
|
* @param assetPath The asset path to the context menu XML.
|
||||||
|
*/
|
||||||
|
public function registerContextMenu(target:Null<Component>, assetPath:String):Void
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
Screen.instance.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent)
|
||||||
|
{
|
||||||
|
showContextMenu(assetPath, e.screenX, e.screenY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent)
|
||||||
|
{
|
||||||
|
showContextMenu(assetPath, e.screenX, e.screenY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findComponent<T:Component>(criteria:String = null, type:Class<T> = null, recursive:Null<Bool> = null, searchType:String = "id"):Null<T>
|
||||||
|
{
|
||||||
|
if (component == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return component.findComponent(criteria, type, recursive, searchType);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function destroy()
|
||||||
|
{
|
||||||
|
if (component != null)
|
||||||
|
remove(component);
|
||||||
|
component = null;
|
||||||
|
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
93
source/funkin/ui/haxeui/HaxeUISubState.hx
Normal file
93
source/funkin/ui/haxeui/HaxeUISubState.hx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package funkin.ui.haxeui;
|
||||||
|
|
||||||
|
import haxe.ui.RuntimeComponentBuilder;
|
||||||
|
import haxe.ui.core.Component;
|
||||||
|
|
||||||
|
class HaxeUISubState extends MusicBeatSubstate
|
||||||
|
{
|
||||||
|
// The component representing the main UI.
|
||||||
|
public var component:Component;
|
||||||
|
|
||||||
|
var _componentKey:String;
|
||||||
|
|
||||||
|
public function new(key:String)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
_componentKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function create()
|
||||||
|
{
|
||||||
|
super.create();
|
||||||
|
|
||||||
|
refreshComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a component from a given XML file.
|
||||||
|
* Call this in your code to load additional components at runtime.
|
||||||
|
*/
|
||||||
|
public function buildComponent(assetPath:String)
|
||||||
|
{
|
||||||
|
trace('Building component $assetPath');
|
||||||
|
return RuntimeComponentBuilder.fromAsset(assetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function update(elapsed:Float)
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
// Force quit.
|
||||||
|
if (FlxG.keys.justPressed.F4)
|
||||||
|
FlxG.switchState(new MainMenuState());
|
||||||
|
|
||||||
|
// Refresh the component.
|
||||||
|
if (FlxG.keys.justPressed.F5)
|
||||||
|
{
|
||||||
|
refreshComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshComponent()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
remove(component);
|
||||||
|
component = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
trace('Success!');
|
||||||
|
add(component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Failed to build component $_componentKey');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (component == null)
|
||||||
|
{
|
||||||
|
component = buildComponent(_componentKey);
|
||||||
|
add(component);
|
||||||
|
trace(component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var component2 = buildComponent(_componentKey);
|
||||||
|
component2.x += 100;
|
||||||
|
add(component2);
|
||||||
|
trace(component2);
|
||||||
|
remove(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function destroy()
|
||||||
|
{
|
||||||
|
if (component != null)
|
||||||
|
remove(component);
|
||||||
|
component = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
source/funkin/ui/haxeui/components/README.md
Normal file
3
source/funkin/ui/haxeui/components/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# funkin.ui.haxeui.components
|
||||||
|
|
||||||
|
Since there is a line in `source/module.xml` pointing to this folder, all components in this folder will automatically be accessible in any HaxeUI layouts.
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package funkin.ui.stageBuildShit;
|
package funkin.ui.stageBuildShit;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.input.mouse.FlxMouseEventManager;
|
import flixel.input.mouse.FlxMouseEvent;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
class SprStage extends FlxSprite
|
class SprStage extends FlxSprite
|
||||||
|
|
@ -18,7 +18,7 @@ class SprStage extends FlxSprite
|
||||||
{
|
{
|
||||||
super(x, y);
|
super(x, y);
|
||||||
|
|
||||||
FlxMouseEventManager.add(this, dragShitFunc, null, function(spr:SprStage)
|
FlxMouseEvent.add(this, dragShitFunc, null, function(spr:SprStage)
|
||||||
{
|
{
|
||||||
if (isSelected() || StageBuilderState.curTool == SELECT)
|
if (isSelected() || StageBuilderState.curTool == SELECT)
|
||||||
alpha = 0.5;
|
alpha = 0.5;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import flixel.FlxSprite;
|
||||||
import flixel.addons.display.FlxGridOverlay;
|
import flixel.addons.display.FlxGridOverlay;
|
||||||
import flixel.group.FlxGroup;
|
import flixel.group.FlxGroup;
|
||||||
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
|
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
|
||||||
import flixel.input.mouse.FlxMouseEventManager;
|
import flixel.input.mouse.FlxMouseEvent;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
import flixel.ui.FlxButton;
|
import flixel.ui.FlxButton;
|
||||||
|
|
@ -93,8 +93,6 @@ class StageBuilderState extends MusicBeatState
|
||||||
var saveSceneBtn:FlxButton = new FlxButton(20, 50, "Save Scene", saveScene);
|
var saveSceneBtn:FlxButton = new FlxButton(20, 50, "Save Scene", saveScene);
|
||||||
hudGrp.add(saveSceneBtn);
|
hudGrp.add(saveSceneBtn);
|
||||||
|
|
||||||
FlxMouseEventManager.init();
|
|
||||||
|
|
||||||
#if desktop
|
#if desktop
|
||||||
FlxG.stage.window.onDropFile.add(function(path:String)
|
FlxG.stage.window.onDropFile.add(function(path:String)
|
||||||
{
|
{
|
||||||
|
|
@ -424,7 +422,7 @@ class StageBuilderState extends MusicBeatState
|
||||||
{
|
{
|
||||||
sprGrp.sort(daLayerSorting, FlxSort.ASCENDING);
|
sprGrp.sort(daLayerSorting, FlxSort.ASCENDING);
|
||||||
|
|
||||||
FlxMouseEventManager.reorder();
|
FlxMouseEvent.reorder();
|
||||||
}
|
}
|
||||||
|
|
||||||
function daLayerSorting(order:Int = FlxSort.ASCENDING, layer1:SprStage, layer2:SprStage):Int
|
function daLayerSorting(order:Int = FlxSort.ASCENDING, layer1:SprStage, layer2:SprStage):Int
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
package funkin.ui.stageBuildShit;
|
package funkin.ui.stageBuildShit;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
import flixel.input.mouse.FlxMouseEvent;
|
||||||
import flixel.input.mouse.FlxMouseEventManager;
|
import flixel.input.mouse.FlxMouseEventManager;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.ui.FlxButton;
|
import flixel.ui.FlxButton;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.play.character.BaseCharacter;
|
import funkin.play.stage.StageData.StageDataParser;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import haxe.Json;
|
|
||||||
import haxe.ui.ComponentBuilder;
|
|
||||||
import haxe.ui.RuntimeComponentBuilder;
|
import haxe.ui.RuntimeComponentBuilder;
|
||||||
import haxe.ui.Toolkit;
|
|
||||||
import haxe.ui.components.Button;
|
|
||||||
import haxe.ui.containers.HBox;
|
|
||||||
import haxe.ui.containers.VBox;
|
import haxe.ui.containers.VBox;
|
||||||
import haxe.ui.core.Component;
|
import haxe.ui.core.Component;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
|
|
@ -46,7 +42,7 @@ class StageOffsetSubstate extends MusicBeatSubstate
|
||||||
|
|
||||||
for (thing in PlayState.instance.currentStage)
|
for (thing in PlayState.instance.currentStage)
|
||||||
{
|
{
|
||||||
FlxMouseEventManager.add(thing, spr ->
|
FlxMouseEvent.add(thing, spr ->
|
||||||
{
|
{
|
||||||
char = cast thing;
|
char = cast thing;
|
||||||
trace("JUST PRESSED!");
|
trace("JUST PRESSED!");
|
||||||
|
|
@ -94,7 +90,7 @@ class StageOffsetSubstate extends MusicBeatSubstate
|
||||||
{
|
{
|
||||||
for (thing in PlayState.instance.currentStage)
|
for (thing in PlayState.instance.currentStage)
|
||||||
{
|
{
|
||||||
FlxMouseEventManager.remove(thing);
|
FlxMouseEvent.remove(thing);
|
||||||
thing.alpha = 1;
|
thing.alpha = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
77
source/funkin/util/BezierUtil.hx
Normal file
77
source/funkin/util/BezierUtil.hx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package funkin.util;
|
||||||
|
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
|
class BezierUtil {
|
||||||
|
/**
|
||||||
|
* Linearly interpolate between two values.
|
||||||
|
* Depending on p, 0 = a, 1 = b, 0.5 = halfway between a and b.
|
||||||
|
*/
|
||||||
|
static inline function mix2(p:Float, a:Float, b:Float):Float {
|
||||||
|
return a * (1 - p) + (b * p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linearly interpolate between three values.
|
||||||
|
* Depending on p, 0 = a, 0.5 = b, 1 = c, 0.25 = halfway between a and c, etc.
|
||||||
|
*/
|
||||||
|
static inline function mix3(p:Float, a:Float, b:Float, c:Float):Float {
|
||||||
|
return mix2(p, mix2(p, a, b), mix2(p, b, c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function mix4(p:Float, a:Float, b:Float, c:Float, d:Float):Float {
|
||||||
|
return mix2(p, mix3(p, a, b, c), mix3(p, b, c, d));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function mix5(p:Float, a:Float, b:Float, c:Float, d:Float, e:Float):Float {
|
||||||
|
return mix2(p, mix4(p, a, b, c, d), mix4(p, b, c, d, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function mix6(p:Float, a:Float, b:Float, c:Float, d:Float, e:Float, f:Float):Float {
|
||||||
|
return mix2(p, mix5(p, a, b, c, d, e), mix5(p, b, c, d, e, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bezier curve with two points.
|
||||||
|
* This is really just linear interpolation but whatever.
|
||||||
|
*/
|
||||||
|
public static function bezier2(p:Float, a:FlxPoint, b:FlxPoint):FlxPoint {
|
||||||
|
return new FlxPoint(mix2(p, a.x, b.x), mix2(p, a.y, b.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bezier curve with three points.
|
||||||
|
* @param p The percentage of the way through the curve.
|
||||||
|
* @param a The start point.
|
||||||
|
* @param b The control point.
|
||||||
|
* @param c The end point.
|
||||||
|
*/
|
||||||
|
public static function bezier3(p:Float, a:FlxPoint, b:FlxPoint, c:FlxPoint):FlxPoint {
|
||||||
|
return new FlxPoint(mix3(p, a.x, b.x, c.x), mix3(p, a.y, b.y, c.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bezier curve with four points.
|
||||||
|
* @param p The percentage of the way through the curve.
|
||||||
|
* @param a The start point.
|
||||||
|
* @param b The first control point.
|
||||||
|
* @param c The second control point.
|
||||||
|
* @param d The end point.
|
||||||
|
*/
|
||||||
|
public static function bezier4(p:Float, a:FlxPoint, b:FlxPoint, c:FlxPoint, d:FlxPoint):FlxPoint {
|
||||||
|
return new FlxPoint(mix4(p, a.x, b.x, c.x, d.x), mix4(p, a.y, b.y, c.y, d.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bezier curve with four points.
|
||||||
|
* @param p The percentage of the way through the curve.
|
||||||
|
* @param a The start point.
|
||||||
|
* @param b The first control point.
|
||||||
|
* @param c The second control point.
|
||||||
|
* @param c The third control point.
|
||||||
|
* @param d The end point.
|
||||||
|
*/
|
||||||
|
public static function bezier5(p:Float, a:FlxPoint, b:FlxPoint, c:FlxPoint, d:FlxPoint, e:FlxPoint):FlxPoint {
|
||||||
|
return new FlxPoint(mix5(p, a.x, b.x, c.x, d.x, e.x), mix5(p, a.y, b.y, c.y, d.y, e.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
51
source/funkin/util/ClipboardUtil.hx
Normal file
51
source/funkin/util/ClipboardUtil.hx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package funkin.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions for working with the system clipboard.
|
||||||
|
* On platforms that don't support interacting with the clipboard,
|
||||||
|
* an internal clipboard is used (neat!).
|
||||||
|
*/
|
||||||
|
class ClipboardUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add an event listener callback which executes next time the system clipboard is updated.
|
||||||
|
*
|
||||||
|
* @param callback The callback to execute when the clipboard is updated.
|
||||||
|
* @param once If true, the callback will only execute once and then be deleted.
|
||||||
|
* @param priority Set the priority at which the callback will be executed. Higher values execute first.
|
||||||
|
*/
|
||||||
|
public static function addListener(callback:Void->Void, ?once:Bool = false, ?priority:Int = 0):Void
|
||||||
|
{
|
||||||
|
lime.system.Clipboard.onUpdate.add(callback, once, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener callback from the system clipboard.
|
||||||
|
*
|
||||||
|
* @param callback The callback to remove.
|
||||||
|
*/
|
||||||
|
public static function removeListener(callback:Void->Void):Void
|
||||||
|
{
|
||||||
|
lime.system.Clipboard.onUpdate.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current contents of the system clipboard.
|
||||||
|
*
|
||||||
|
* @return The current contents of the system clipboard.
|
||||||
|
*/
|
||||||
|
public static function getClipboard():String
|
||||||
|
{
|
||||||
|
return lime.system.Clipboard.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the contents of the system clipboard.
|
||||||
|
*
|
||||||
|
* @param text The text to set the system clipboard to.
|
||||||
|
*/
|
||||||
|
public static function setClipboard(text:String):String
|
||||||
|
{
|
||||||
|
return lime.system.Clipboard.text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,24 +6,29 @@ import lime.app.Application;
|
||||||
class Constants
|
class Constants
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The scale factor to use when increasing the size of pixel art graphics.
|
* ENGINE AND VERSION DATA
|
||||||
*/
|
*/
|
||||||
public static final PIXEL_ART_SCALE = 6;
|
// ==============================
|
||||||
|
|
||||||
public static final HEALTH_BAR_RED:FlxColor = 0xFFFF0000;
|
/**
|
||||||
public static final HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
|
* The title of the game, for debug printing purposes.
|
||||||
|
* Change this if you're making an engine.
|
||||||
|
*/
|
||||||
|
public static final TITLE = "Friday Night Funkin'";
|
||||||
|
|
||||||
public static final COUNTDOWN_VOLUME = 0.6;
|
/**
|
||||||
|
* The current version number of the game.
|
||||||
public static final VERSION_SUFFIX = ' PROTOTYPE';
|
* Modify this in the `project.xml` file.
|
||||||
|
*/
|
||||||
public static var VERSION(get, null):String;
|
public static var VERSION(get, null):String;
|
||||||
|
|
||||||
public static final FREAKY_MENU_BPM = 102;
|
/**
|
||||||
|
* A suffix to add to the game version.
|
||||||
|
* Add a suffix to prototype builds and remove it for releases.
|
||||||
|
*/
|
||||||
|
public static final VERSION_SUFFIX = ' PROTOTYPE';
|
||||||
|
|
||||||
#if debug
|
#if debug
|
||||||
public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash();
|
|
||||||
public static final GIT_BRANCH = funkin.util.macro.GitCommit.getGitBranch();
|
|
||||||
|
|
||||||
static function get_VERSION():String
|
static function get_VERSION():String
|
||||||
{
|
{
|
||||||
return 'v${Application.current.meta.get('version')} (${GIT_BRANCH} : ${GIT_HASH})' + VERSION_SUFFIX;
|
return 'v${Application.current.meta.get('version')} (${GIT_BRANCH} : ${GIT_HASH})' + VERSION_SUFFIX;
|
||||||
|
|
@ -35,6 +40,74 @@ class Constants
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL DATA
|
||||||
|
*/
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to download the game on Itch.io.
|
||||||
|
*/
|
||||||
|
public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin/purchase";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to the game's page on Kickstarter.
|
||||||
|
*/
|
||||||
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/";
|
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/";
|
||||||
public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin";
|
|
||||||
|
/**
|
||||||
|
* GIT REPO DATA
|
||||||
|
*/
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
#if debug
|
||||||
|
/**
|
||||||
|
* The current Git branch.
|
||||||
|
*/
|
||||||
|
public static final GIT_BRANCH = funkin.util.macro.GitCommit.getGitBranch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current Git commit hash.
|
||||||
|
*/
|
||||||
|
public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash();
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COLORS
|
||||||
|
*/
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color used by the enemy health bar.
|
||||||
|
*/
|
||||||
|
public static final COLOR_HEALTH_BAR_RED:FlxColor = 0xFFFF0000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color used by the player health bar.
|
||||||
|
*/
|
||||||
|
public static final COLOR_HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTHER
|
||||||
|
*/
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scale factor to use when increasing the size of pixel art graphics.
|
||||||
|
*/
|
||||||
|
public static final PIXEL_ART_SCALE = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BPM of the title screen and menu music.
|
||||||
|
* TODO: Move to metadata file.
|
||||||
|
*/
|
||||||
|
public static final FREAKY_MENU_BPM = 102;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The volume at which to play the countdown before the song starts.
|
||||||
|
*/
|
||||||
|
public static final COUNTDOWN_VOLUME = 0.6;
|
||||||
|
|
||||||
|
public static final DEFAULT_VARIATION = 'default';
|
||||||
|
public static final DEFAULT_DIFFICULTY = 'normal';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
source/funkin/util/SerializerUtil.hx
Normal file
58
source/funkin/util/SerializerUtil.hx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package funkin.util;
|
||||||
|
|
||||||
|
import haxe.Json;
|
||||||
|
import thx.semver.Version;
|
||||||
|
|
||||||
|
class SerializerUtil
|
||||||
|
{
|
||||||
|
static final INDENT_CHAR = "\t";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Haxe object to a JSON string.
|
||||||
|
**/
|
||||||
|
public static function toJSON(input:Dynamic, ?pretty:Bool = true):String
|
||||||
|
{
|
||||||
|
return Json.stringify(input, replacer, pretty ? INDENT_CHAR : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a JSON string to a Haxe object of the chosen type.
|
||||||
|
*/
|
||||||
|
public static function fromJSONTyped<T>(input:String, type:Class<T>):T
|
||||||
|
{
|
||||||
|
return cast Json.parse(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a JSON string to a Haxe object.
|
||||||
|
*/
|
||||||
|
public static function fromJSON(input:String):Dynamic
|
||||||
|
{
|
||||||
|
return Json.parse(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize how certain types are serialized when converting to JSON.
|
||||||
|
*/
|
||||||
|
static function replacer(key:String, value:Dynamic):Dynamic
|
||||||
|
{
|
||||||
|
// Hacky because you can't use `isOfType` on a struct.
|
||||||
|
if (key == "version")
|
||||||
|
{
|
||||||
|
if (Std.isOfType(value, String))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
// Stringify Version objects.
|
||||||
|
var valueVersion:thx.semver.Version = cast value;
|
||||||
|
var result = '${valueVersion.major}.${valueVersion.minor}.${valueVersion.patch}';
|
||||||
|
if (valueVersion.hasPre)
|
||||||
|
result += '-${valueVersion.pre}';
|
||||||
|
if (valueVersion.hasBuild)
|
||||||
|
result += '+${valueVersion.build}';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, return the value as-is.
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ class DataAssets
|
||||||
return 'assets/data/${path}';
|
return 'assets/data/${path}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function listDataFilesInPath(path:String, ?ext:String = '.json'):Array<String>
|
public static function listDataFilesInPath(path:String, ?suffix:String = '.json'):Array<String>
|
||||||
{
|
{
|
||||||
var textAssets = openfl.utils.Assets.list();
|
var textAssets = openfl.utils.Assets.list();
|
||||||
var queryPath = buildDataPath(path);
|
var queryPath = buildDataPath(path);
|
||||||
|
|
@ -17,9 +17,9 @@ class DataAssets
|
||||||
var results:Array<String> = [];
|
var results:Array<String> = [];
|
||||||
for (textPath in textAssets)
|
for (textPath in textAssets)
|
||||||
{
|
{
|
||||||
if (textPath.startsWith(queryPath) && textPath.endsWith(ext))
|
if (textPath.startsWith(queryPath) && textPath.endsWith(suffix))
|
||||||
{
|
{
|
||||||
var pathNoSuffix = textPath.substring(0, textPath.length - ext.length);
|
var pathNoSuffix = textPath.substring(0, textPath.length - suffix.length);
|
||||||
var pathNoPrefix = pathNoSuffix.substring(queryPath.length);
|
var pathNoPrefix = pathNoSuffix.substring(queryPath.length);
|
||||||
|
|
||||||
// No duplicates! Why does this happen?
|
// No duplicates! Why does this happen?
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue