diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..2f5488a9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +ko_fi: codename_engine diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1efcfcfa..2d2dc145 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,7 +7,7 @@ jobs: build: name: Linux Build permissions: write-all - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Pulling the source uses: actions/checkout@v2 @@ -35,13 +35,13 @@ jobs: - name: Building the game run: | haxelib run lime build linux - - name: Tar files - run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin . + # - name: Tar files + # run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin . - name: Uploading artifact (entire build) uses: actions/upload-artifact@v4 with: name: Codename Engine - path: CodenameEngine.tar.gz + path: export/release/linux/bin/ # - name: Uploading artifact (executable) # uses: actions/upload-artifact@v4 # with: diff --git a/.gitignore b/.gitignore index 7a1c38d1..77035890 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ export/ dump/ -latest/windows/obj/ -latest/windows/haxe/ +latest/* .vscode/* !.vscode/settings.json !.vscode/tasks.json @@ -9,5 +8,8 @@ latest/windows/haxe/ pages/ docs/doc.xml mods/* +addons/* !mods/readme.txt +addons/* +!addons/readme.txt dev-libs/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3caecb..4a694405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ This changelog lists every change that has been added since the last update (inc ## Flags System - Added a brand new flags system, which allows you to modify the game constants using a simple `flags.ini` file ([f8d1cf0](https://github.com/CodenameCrew/CodenameEngine/commit/f8d1cf08ad9f6cdda4e971a3d8adff6fce5130d2)) -- Added the ability to change the way the mod songs/weeks are added to the menu ([cb1d6fc](https://github.com/CodenameCrew/CodenameEngine/commit/cb1d6fcacf4ad0e2888ed7e76d16a6f85c09b021)) +- Added the ability to change the way the mod songs/weeks are added into the menu ([cb1d6fc](https://github.com/CodenameCrew/CodenameEngine/commit/cb1d6fcacf4ad0e2888ed7e76d16a6f85c09b021)) - Added the ability to disable editors, transtions and beta warning state ([90a8c27](https://github.com/CodenameCrew/CodenameEngine/commit/90a8c2711ce36c9eaf85e705d551d46876991aa9)) - Added the ability to set the default Transition, Pause, Game Over scripts ([fd0c188](https://github.com/CodenameCrew/CodenameEngine/commit/fd0c188577cf58e34e8ed706e59903a989ee2d11)) - Added the ability to set the default glsl version ([cbbb598](https://github.com/CodenameCrew/CodenameEngine/commit/cbbb598f52b3f7c0f156cf72cd03e165aa38a4f1)) @@ -16,13 +16,14 @@ This changelog lists every change that has been added since the last update (inc - Added a function ``WindowUtils.resetAffixes()`` to reset the prefix and suffix of the engine's window title, and fixed a typo ([1d94945](https://github.com/CodenameCrew/CodenameEngine/commit/1d94945faeea86af02bcaecd715f5bea22ef48fd)) - Added a field `extra` to FunkinSprite, which can be used to store any extra data you want (e.g. sprite.extra.set("name", value)) ([fb48570](https://github.com/CodenameCrew/CodenameEngine/commit/fb485701b451d6a7a22a1e696bfc081699f1c6d2)) - Added default icon colors to every base game character ([814bd71](https://github.com/CodenameCrew/CodenameEngine/commit/814bd712fe2ea2dd21116e9903d9c2babe5341b1)) -- Added more features to cne.bat/cne.sh: ([4ef43df](https://github.com/CodenameCrew/CodenameEngine/commit/4ef43dfbecc79283d038d939f94671966802039e), [0dc166c](https://github.com/CodenameCrew/CodenameEngine/commit/0dc166c601b25e3167a1d04542840265484576b4)) +- Added more features to cne.bat/cne.sh: ([4ef43df](https://github.com/CodenameCrew/CodenameEngine/commit/4ef43dfbecc79283d038d939f94671966802039e), [0dc166c](https://github.com/CodenameCrew/CodenameEngine/commit/0dc166c601b25e3167a1d04542840265484576b4), [21bba98](https://github.com/CodenameCrew/CodenameEngine/commit/21bba98923307fe75667e1395781d068a7ec47ad)) - Setup - `--reinstall` to reinstall all libraries - `--no-vscheck` to skip the Visual Studio check - `--silent` to not print the download progress - - project file has conditionals now + - libs file has conditionals now - `--lib=path.xml` to use a specific setup file + - `--fast` for quickly downloading git libraries, but requires you to reinstall it fully when updates happen - Alphabet now allows multiple lines in the text ([d519db0](https://github.com/CodenameCrew/CodenameEngine/commit/d519db011a14aac0ddfc052089836c6b445f7f44)) - Allow importing mod root level modules in hscript, instead of only source/ ([7e66ee8](https://github.com/CodenameCrew/CodenameEngine/commit/7e66ee81e24204cd152de20046fa1500bb9d204b)) - Added a way to force animations in the `Play Animation` event ([d7b6fbe](https://github.com/CodenameCrew/CodenameEngine/commit/d7b6fbe510edeb832112ab5adc8ab19dfe97d77f)) @@ -63,7 +64,6 @@ This changelog lists every change that has been added since the last update (inc - Added `bf` shorthand for `boyfriend` in playstate ([2b81220](https://github.com/CodenameCrew/CodenameEngine/commit/2b81220c15ed77acecd17c09a975ed401677165f)) - Added character script extensions ([682358c](https://github.com/CodenameCrew/CodenameEngine/commit/682358c93a0c17469d1a481de0523e4c4e19c6ec)) - Folder support in the character editor selection ([68c2fea](https://github.com/CodenameCrew/CodenameEngine/commit/68c2feab373db69d4a00b6f274f98afb3d8e3803)) -- cne.bat/cne.sh setup --fast for quickly downloading git libraries ([21bba98](https://github.com/CodenameCrew/CodenameEngine/commit/21bba98923307fe75667e1395781d068a7ec47ad)) - Allow hold notes to be force deleted ([25b5630](https://github.com/CodenameCrew/CodenameEngine/commit/25b563066fc53ac92ebe3064175c80b5f1ae64bc)) - Allow tweening shaders and array values using FlxTween ([3de3c3f](https://github.com/CodenameCrew/CodenameEngine/commit/3de3c3f8286b2b07c9690334e2d85ce052a909c5)) - Made the advanced info show `Mod State: {name}` instead of `State: funkin.backend.scripting.ModState` ([3de3c3f](https://github.com/CodenameCrew/CodenameEngine/commit/3de3c3f8286b2b07c9690334e2d85ce052a909c5)) @@ -91,6 +91,9 @@ This changelog lists every change that has been added since the last update (inc - Improved readability of the events macro ([47cf72d](https://github.com/CodenameCrew/CodenameEngine/commit/47cf72d53b985bcc818286a90fdbbaa92f4a9396)) - Changed how editor icons are stored ([16672e9](https://github.com/CodenameCrew/CodenameEngine/commit/16672e9f597fd4e621d7ee2c000ba559d3837662), [18ea916](https://github.com/CodenameCrew/CodenameEngine/commit/18ea916f1af4c03db765ba55841acc13d7108766)) - Changed `WindowUtils.winTitle` to `WindowUtils.title` ([a6c9975](https://github.com/CodenameCrew/CodenameEngine/commit/a6c99754fd1bfe56db61b82b9749f073a1a0a170)) +- Added property parsing and cloning property values ([503ff87](https://github.com/CodenameCrew/CodenameEngine/commit/503ff87ab3f6ccd4b0ab74e2403a29fff70cc1e1)) +- Added syncing booleans between strums and notes ([1af6931](https://github.com/CodenameCrew/CodenameEngine/commit/1af69313bb30a1d8faf91bc34b29c319290e0ea5)) +- Added ``onReturnToMenu`` event in GameOverSubstate ([cda9311](https://github.com/CodenameCrew/CodenameEngine/commit/cda9311a905a8b9d226f14b8f1e3430f54e70ee5)) ## Removals @@ -152,10 +155,12 @@ This changelog lists every change that has been added since the last update (inc - Fixed pasting and added copying in `UITextBox` ([c4abac4](https://github.com/CodenameCrew/CodenameEngine/commit/c4abac4ba1ef0692325a736f1123a511965368a6)) - Made the Freeplay Autoplay change the bpm ([f90782d](https://github.com/CodenameCrew/CodenameEngine/commit/f90782d7123970215324ace99c55584ad78ca028)) - Prevent accidental mouse inputs when pressing on the unfocused window ([300f38d](https://github.com/CodenameCrew/CodenameEngine/commit/300f38d290bb8c31a5c1e67afdf981c644b27f8a)) -- Fix notes not being destroyed when the strumline is destroyed ([1f224be](https://github.com/CodenameCrew/CodenameEngine/commit/1f224bef228c496d789aeed1f44c6517668107a9)) +- Fixed notes not being destroyed when the strumline is destroyed ([1f224be](https://github.com/CodenameCrew/CodenameEngine/commit/1f224bef228c496d789aeed1f44c6517668107a9)) - Fixed the credits and options menu backgorunds not being animated if the background is animated ([1facc82](https://github.com/CodenameCrew/CodenameEngine/commit/1facc82073b9a06b861d1987a162f8aecbec46f1)) - Fixed duplicated line messages in the log ([1aa1364](https://github.com/CodenameCrew/CodenameEngine/commit/1aa136469d1ecadb4e9c49c04d6d6287d4b39884)) - Selection fixes in Editors ([0102cbb](https://github.com/CodenameCrew/CodenameEngine/commit/0102cbbdb6812f315697f6e389760ce948ff1993), [accc197](https://github.com/CodenameCrew/CodenameEngine/commit/accc197407c86a6fe9aaf1c557c93148674879ff)) +- Fixed Hscript Call event not calling public and static functions (also can now call character scripts) ([650c0f4](https://github.com/CodenameCrew/CodenameEngine/commit/650c0f4aed546ff80a0a590365b59427e4e45db6)) +- Made some menus remember the current selected item ([d36f4fa](https://github.com/CodenameCrew/CodenameEngine-Dev/commit/d36f4fa48430ac73d7f2c8701894283041898ce6), [6bed0a7](https://github.com/CodenameCrew/CodenameEngine-Dev/commit/6bed0a789f2b809dc6311c8227c8307c0f2fe522)) ## Crash fixes diff --git a/README.md b/README.md index 4ba32a47..fb3918bb 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ If you don't have a github account please go onto https://fnf-cne-devs.github.io

Credits

-- Credits to [Ne_Eo](https://twitter.com/Ne_Eo_Twitch) and the [3D-HaxeFlixel](https://github.com/lunarcleint/3D-HaxeFlixel) repository for Away3D Flixel support - Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support - Credits to Smokey555 for the backup Animate Atlas to spritesheet code - Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) (video cutscene/mp4 support) and [hxdiscord_rpc](https://github.com/MAJigsaw77/hxdiscord_rpc) (discord rpc integration) diff --git a/addons/readme.txt b/addons/readme.txt new file mode 100644 index 00000000..df84a4b2 --- /dev/null +++ b/addons/readme.txt @@ -0,0 +1,22 @@ +╔═══════════════════════════════════════╗ +║ HOW TO USE ║ +╚═══════════════════════════════════════╝ +DRAG AND DROP YOUR ADDON FOLDER HERE! + +╔═══════════════════════════════════════╗ +║ ZIP ADDONS ║ +╚═══════════════════════════════════════╝ +Zip files are also supported at condition it follows this hierarchy: + +📁addon.zip/ + 📁data + 📁images + 📁... + +Instead of this one + +📁addon.zip + 📁my addon + 📁data + 📁images + 📁... \ No newline at end of file diff --git a/art/FMOD Logo Black - White Background.png b/art/FMOD Logo Black - White Background.png index 273ef6f0..b7cf1d1c 100644 Binary files a/art/FMOD Logo Black - White Background.png and b/art/FMOD Logo Black - White Background.png differ diff --git a/art/FMOD Logo White - Black Background.png b/art/FMOD Logo White - Black Background.png index 1780488d..3e23026c 100644 Binary files a/art/FMOD Logo White - Black Background.png and b/art/FMOD Logo White - Black Background.png differ diff --git a/art/burns.png b/art/burns.png index 89b08ba6..bc004e04 100644 Binary files a/art/burns.png and b/art/burns.png differ diff --git a/art/design ideas/charter events/1.png b/art/design ideas/charter events/1.png index 3340e41f..d63b0d7d 100644 Binary files a/art/design ideas/charter events/1.png and b/art/design ideas/charter events/1.png differ diff --git a/art/design ideas/charter events/2.png b/art/design ideas/charter events/2.png index ac451a03..8e5d1939 100644 Binary files a/art/design ideas/charter events/2.png and b/art/design ideas/charter events/2.png differ diff --git a/art/github/windows-installation-details.png b/art/github/windows-installation-details.png index 5ff9dc19..c1291faf 100644 Binary files a/art/github/windows-installation-details.png and b/art/github/windows-installation-details.png differ diff --git a/art/icon16.png b/art/icon16.png index 2adacd69..ab61bcbe 100644 Binary files a/art/icon16.png and b/art/icon16.png differ diff --git a/art/iconOG.png b/art/iconOG.png index b0268e51..6ec3bcd5 100644 Binary files a/art/iconOG.png and b/art/iconOG.png differ diff --git a/art/preloaderArt.png b/art/preloaderArt.png index 176e51b3..86ddbdf6 100644 Binary files a/art/preloaderArt.png and b/art/preloaderArt.png differ diff --git a/art/teaser discord.png b/art/teaser discord.png index 45fa51ef..58a2e421 100644 Binary files a/art/teaser discord.png and b/art/teaser discord.png differ diff --git a/art/teaser layers/teaser bg.png b/art/teaser layers/teaser bg.png index b4e6af05..a65e5ffe 100644 Binary files a/art/teaser layers/teaser bg.png and b/art/teaser layers/teaser bg.png differ diff --git a/art/teaser layers/teaser checkerboard.png b/art/teaser layers/teaser checkerboard.png index f515995f..7a9e6150 100644 Binary files a/art/teaser layers/teaser checkerboard.png and b/art/teaser layers/teaser checkerboard.png differ diff --git a/art/teaser layers/teaser funkin logo.png b/art/teaser layers/teaser funkin logo.png index 557b740a..102cb82c 100644 Binary files a/art/teaser layers/teaser funkin logo.png and b/art/teaser layers/teaser funkin logo.png differ diff --git a/art/teaser layers/teaser question mark.png b/art/teaser layers/teaser question mark.png index 0cbbec86..30f040df 100644 Binary files a/art/teaser layers/teaser question mark.png and b/art/teaser layers/teaser question mark.png differ diff --git a/art/teaser.png b/art/teaser.png index b185f7ac..82e424ad 100644 Binary files a/art/teaser.png and b/art/teaser.png differ diff --git a/art/thumbnailNewer.png b/art/thumbnailNewer.png index 73f381fc..daea6f62 100644 Binary files a/art/thumbnailNewer.png and b/art/thumbnailNewer.png differ diff --git a/art/veryimportant.png b/art/veryimportant.png index 89910647..2fe31d88 100644 Binary files a/art/veryimportant.png and b/art/veryimportant.png differ diff --git a/assets/data/characters/bf-holding-gf-dead.xml b/assets/data/characters/bf-holding-gf-dead.xml index 52fa52eb..3a2981ac 100644 --- a/assets/data/characters/bf-holding-gf-dead.xml +++ b/assets/data/characters/bf-holding-gf-dead.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/characters/bf-pixel-dead.xml b/assets/data/characters/bf-pixel-dead.xml index 52aedd58..8b205eb9 100644 --- a/assets/data/characters/bf-pixel-dead.xml +++ b/assets/data/characters/bf-pixel-dead.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/characters/gf-car.xml b/assets/data/characters/gf-car.xml index 02fe7e1b..359a8eea 100644 --- a/assets/data/characters/gf-car.xml +++ b/assets/data/characters/gf-car.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/assets/data/characters/gf-christmas.xml b/assets/data/characters/gf-christmas.xml index d9a05715..f27cdcbd 100644 --- a/assets/data/characters/gf-christmas.xml +++ b/assets/data/characters/gf-christmas.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/characters/gf-tankmen.xml b/assets/data/characters/gf-tankmen.xml index a332b124..c498cb57 100644 --- a/assets/data/characters/gf-tankmen.xml +++ b/assets/data/characters/gf-tankmen.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/characters/gf.xml b/assets/data/characters/gf.xml index 54a8ad59..5981b647 100644 --- a/assets/data/characters/gf.xml +++ b/assets/data/characters/gf.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/characters/senpai-angry.xml b/assets/data/characters/senpai-angry.xml index b9450a24..a0fab601 100644 --- a/assets/data/characters/senpai-angry.xml +++ b/assets/data/characters/senpai-angry.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/characters/tankman.xml b/assets/data/characters/tankman.xml index c06aa6c4..d02f5a41 100644 --- a/assets/data/characters/tankman.xml +++ b/assets/data/characters/tankman.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/config/discord.json b/assets/data/config/discord.json index 582e510b..f6ad9636 100644 --- a/assets/data/config/discord.json +++ b/assets/data/config/discord.json @@ -1,4 +1,4 @@ { - "clientID": "1027994136193810442", + "clientID": "1383853614589673472", "logoKey": "icon" } \ No newline at end of file diff --git a/assets/data/discord.hx b/assets/data/discord.hx index 9d5f0f6f..8c9e3a48 100644 --- a/assets/data/discord.hx +++ b/assets/data/discord.hx @@ -8,7 +8,7 @@ function onDiscordPresenceUpdate(e) { var data = e.presence; if(data.button1Label == null) - data.button1Label = "Codename Engine Discord"; + data.button1Label = "meow"; // "Codename Engine Discord" if(data.button1Url == null) data.button1Url = "https://discord.gg/2NTCdsQvx4"; } @@ -33,8 +33,8 @@ function onEditorTreeLoaded(name:String) { DiscordUtil.changePresenceSince("Choosing a Character", null); case "Chart Editor": DiscordUtil.changePresenceSince("Choosing a Chart", null); - case "Stage Editor": - DiscordUtil.changePresenceSince("Choosing a Stage", null); + //case "Stage Editor": // secret for now + // DiscordUtil.changePresenceSince("Choosing a Stage", null); } } @@ -44,7 +44,7 @@ function onEditorLoaded(name:String, editingThing:String) { DiscordUtil.changePresenceSince("Editing a Character", editingThing); case "Chart Editor": DiscordUtil.changePresenceSince("Editing a Chart", editingThing); - case "Stage Editor": - DiscordUtil.changePresenceSince("Editing a Stage", editingThing); + //case "Stage Editor": + // DiscordUtil.changePresenceSince("Editing a Stage", editingThing); } } \ No newline at end of file diff --git a/assets/data/editors/layouts/stage/characterEditScreen.xml b/assets/data/editors/layouts/stage/characterEditScreen.xml new file mode 100644 index 00000000..2658f4ac --- /dev/null +++ b/assets/data/editors/layouts/stage/characterEditScreen.xml @@ -0,0 +1,126 @@ + + + + + + + function col(c:Int) { + return 20 + (150 + XOFFSET) * c; + } + previewSprite = null; + if(char.animateAtlas == null) { + previewSprite = new FunkinSprite(0, 0); + previewSprite.frame = char.frame; + + self.add(previewSprite); + } + + + + + <section> + <set name="curY" value="title.y + TO" /> + + <stepper name="xStepper" label="X" value="char.x" x="col(0)" y="curY" step="1" width="100" /> + <stepper name="yStepper" label="Y" value="char.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" step="1" width="100" /> + + <stepper name="scrollXStepper" label="Scroll X" value="char.scrollFactor.x" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scrollYStepper" label="Scroll Y" value="char.scrollFactor.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <stepper name="scaleXStepper" label="Scale X" value="char.scale.x / charScale" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scaleYStepper" label="Scale Y" value="char.scale.y / charScale" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> + <set name="curY" value="xStepper.y + xStepper.bHeight + YOFFSET" /> + + <stepper name="camxStepper" label="Cam X" value="getEx('camX')" x="col(0)" y="curY" step="1" width="100" /> + <stepper name="camyStepper" label="Cam Y" value="getEx('camY')" x="last.x + last.bWidth + XOFFSET - 20" y="curY" step="1" width="100" /> + + <label name="spacingLabel" text="Spacing (X, Y)" x="col(1)" y="curY" size="15" /> + <stepper name="spacingXStepper" value="getEx('spacingX')" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="spacingYStepper" value="getEx('spacingY')" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <stepper name="skewXStepper" label="Skew X" value="char.skew.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="skewYStepper" label="Skew Y" value="char.skew.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> + <set name="curY" value="camxStepper.y + camxStepper.bHeight + YOFFSET" /> + + <stepper name="alphaStepper" label="Alpha" value="char.alpha / 0.75" x="col(0)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="angleStepper" label="Angle" value="char.angle" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <stepper name="zoomFactorStepper" label="Zoom Factor" value="char.zoomFactor" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + + <section if="!StringTools.startsWith(char.name, 'NO_DELETE_')"> + <textbox name="charName" label="Position Name" value="char.name" x="col(2)" y="curY" width="100"/> + </section> + </section> + + <section> + <set name="curY" value="alphaStepper.y + alphaStepper.bHeight + YOFFSET - 20" /> + + <checkbox name="flipXCheckbox" text="Flip X" value="char.isPlayer" x="col(0)" y="curY" /> + + <radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="getEx('highMemory')" x="last.field.x + last.field.width + XOFFSET * 2" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="getEx('lowMemory')" x="last.field.x + last.field.width + XOFFSET * 2" y="curY" /> + </section> + + <section if="previewSprite != null"> + <title name="previewTitle" x="20" y="self.winHeight - 220" text="Preview Transform:" size="15" /> + </section> + <section if="previewSprite == null"> + <exec> + self.windowSpr.bHeight -= 170; + </exec> + </section> + + <exec> + function onUpdate() { + if(previewSprite == null) return; + previewSprite.skew.x = skewXStepper.value; + previewSprite.skew.y = skewYStepper.value; + previewSprite.angle = angleStepper.value; + previewSprite.antialiasing = char.antialiasing; + var ratio = char.frameWidth / char.frameHeight; + previewSprite.setGraphicSize(145 * scaleXStepper.value * ratio, 145 * scaleYStepper.value); + previewSprite.updateHitbox(); + if(flipXCheckbox.checked != char.playerOffsets) + previewSprite.scale.x *= -1; + + previewSprite.x = 30; + previewSprite.y = self.winHeight - previewSprite.height - 30; + } + + function onSave() { + setEx("highMemory", highMemoryRadio.checked); + setEx("lowMemory", lowMemoryRadio.checked); + + if (!StringTools.startsWith(char.name, 'NO_DELETE_')) + char.name = charName.field.text; + + char.x = xStepper.value; + char.y = yStepper.value; + char.scrollFactor.set(scrollXStepper.value, scrollYStepper.value); + char.scale.set(scaleXStepper.value * charScale, scaleYStepper.value * charScale); + char.cameraOffset.x -= getEx("camX"); + char.cameraOffset.x += camxStepper.value; + setEx("camX", camxStepper.value); + char.cameraOffset.y -= getEx("camY"); + char.cameraOffset.y += camyStepper.value; + setEx("camY", camyStepper.value); + char.skew.x = skewXStepper.value; + char.skew.y = skewYStepper.value; + char.alpha = alphaStepper.value * 0.75; + setEx("spacingX", spacingXStepper.value); + setEx("spacingY", spacingYStepper.value); + if (flipXCheckbox.checked != char.isPlayer) { + char.isPlayer = flipXCheckbox.checked; + char.swapLeftRightAnimations(); + } + char.zoomFactor = zoomFactorStepper.value; + char.angle = angleStepper.value; + } + </exec> +</window> \ No newline at end of file diff --git a/assets/data/editors/layouts/stage/spriteEditScreen.xml b/assets/data/editors/layouts/stage/spriteEditScreen.xml new file mode 100644 index 00000000..651756a0 --- /dev/null +++ b/assets/data/editors/layouts/stage/spriteEditScreen.xml @@ -0,0 +1,185 @@ +<window width="1056" height="520" title="Editing Sprite ${button.getName()}"> + <set name="MARGIN" value="20" /> + <set name="XOFFSET" value="50" /> + <set name="YOFFSET" value="40" /> + <set name="TO" value="64" /> + + <exec> + function col(c:Int) { + return 20 + (200 + XOFFSET) * c; + } + + if(sprite.animateAtlas == null) { + previewSprite = new FunkinSprite(50, self.winHeight - 100); + previewSprite.frame = sprite.frame; + + self.add(previewSprite); + } + </exec> + + <title name="title" x="20" y="30 + 16" text="Sprite Data" /> + + <section> <!-- ROW 1 --> + <set name="curY" value="title.y + TO" /> + + <textbox name="nameTextBox" label="Name Identifier" value="button.getName()" x="col(0)" y="curY" width="200" height="32" /> + <textbox name="spriteTextBox" value="getEx('imageFile')" x="last.x + last.bWidth + XOFFSET" y="curY" width="200" height="32" /> + <stepper name="alphaStepper" label="Alpha" value="sprite.alpha" x="spriteTextBox.x + spriteTextBox.bWidth + XOFFSET" y="curY" precision="2" step="0.01" width="100" /> + <label name="imageFile" text="Image File (WIP: needs reload) In (${stage.spritesParentFolder})" x="spriteTextBox.x" y="spriteTextBox.y - 14" size="15" /> + </section> + + <section> <!-- ROW 2 --> + <set name="curY" value="nameTextBox.y + nameTextBox.bHeight + YOFFSET" /> + + <stepper name="xStepper" label="X" value="sprite.x" x="col(0)" y="curY" step="1" width="100" /> + <stepper name="yStepper" label="Y" value="sprite.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" step="1" width="100" /> + + <stepper name="scrollXStepper" label="Scroll X" value="sprite.scrollFactor.x" x="spriteTextBox.x" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scrollYStepper" label="Scroll Y" value="sprite.scrollFactor.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <stepper name="scaleXStepper" label="Scale X" value="sprite.scale.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scaleYStepper" label="Scale Y" value="sprite.scale.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> <!-- ROW 3 --> + <set name="curY" value="xStepper.y + xStepper.bHeight + YOFFSET" /> + + <stepper name="angleStepper" label="Angle" value="sprite.angle" x="col(0)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="zoomFactorStepper" label="Zoom Factor" value="sprite.zoomFactor" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <!-- TODO: Simplify this --> + <dropdown name="animType" label="Animation Type" value="sprite.spriteAnimType" x="col(1)" y="curY" width="150" height="32"> + <item label="NONE" value="0" /> + <item label="BEAT" value="1" /> + <item label="LOOP" value="2" /> + </dropdown> + + <stepper name="skewXStepper" label="Skew X" value="sprite.skew.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="skewYStepper" label="Skew Y" value="sprite.skew.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> <!-- ROW 4 --> + <set name="curY" value="angleStepper.y + angleStepper.bHeight + YOFFSET" /> + + <checkbox name="antialiasingCheckbox" text="Antialiasing" value="sprite.antialiasing" x="col(0)" y="curY" /> + <radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="getEx('highMemory')" x="col(1)" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="getEx('lowMemory')" x="col(2)" y="curY" /> + + <!-- TODO: Animation list --> + </section> + + <section> <!-- RIGHT SIDE --> + <set name="curY" value="title.y + TO" /> + <set name="listWidth" value="250" /> + + <buttonlist name="animations" label="Animations (TODO)" x="col(3)" y="curY" width="listWidth" height="330" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5"> + <texture path="editors/ui/inputbox" /> + <camSpacing value="0" /> + + <code> + buttonlist.addButton.exists = false; + </code> + + <!-- <code> + import funkin.editors.extra.PropertyButton; + + var buttonList = buttonlist; // important for now, since it gets reset after the code is run + + var width = buttonList.buttonSize.x; + var height = buttonList.buttonSize.y; + + buttonList.addButton.callback = function() { + buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 150, 220, 25)); + } + + //var defaultAttribs = Stage.DEFAULT_ATTRIBUTES; + //for (i in stage.stageXML.attributes()) { + // trace(i, defaultAttribs); + // if (!defaultAttribs.contains(i)) + // buttonList.add(new PropertyButton(i, stage.stageXML.get(i), buttonList, width, height, 150, 220, 25)); + //} + for(key => val in stage.extra) + buttonList.add(new PropertyButton(key, val, buttonList, width, height, 150, 220, 25)); + </code> --> + </buttonlist> + + <text text="Tip: To access custom properties, hold shift when clicking edit" x="col(2) - 60" y="self.winHeight - 50" size="15" /> + </section> + + + <!-- Preview Transform --> + + <section if="previewSprite != null"> + <title name="previewTitle" x="20" y="self.winHeight - 150" text="Preview Transform:" size="15" /> + </section> + + <exec> + function onUpdate() { + if(previewSprite == null) return; + previewSprite.color = 0xFFFFFFFF; // TODO: Color Wheel + previewSprite.skew.x = skewXStepper.value; + previewSprite.skew.y = skewYStepper.value; + previewSprite.angle = angleStepper.value; + previewSprite.alpha = alphaStepper.value; + previewSprite.antialiasing = antialiasingCheckbox.checked; + var ratio = sprite.width / sprite.height; + previewSprite.setGraphicSize(50 * scaleXStepper.value * ratio, 50 * scaleYStepper.value); + previewSprite.updateHitbox(); + } + + function onSave() { + sprite.name = nameTextBox.label.text; + setEx("imageFile", spriteTextBox.label.text); + + setEx("highMemory", highMemoryRadio.checked); + setEx("lowMemory", lowMemoryRadio.checked); + + //sprite.x = xStepper.value; + //sprite.y = yStepper.value; + //sprite.zoomFactor = zoomFactorStepper.value; + //sprite.antialiasing = antialiasingCheckbox.checked; + //sprite.color = colorWheel.color; + //sprite.spriteAnimType = animType.value; + sprite.angle = angleStepper.value; + sprite.alpha = alphaStepper.value; + + function saveXY(obj:Dynamic, name:String, x:UINumericStepper, y:UINumericStepper) { + button.xml.remove(name + ""); + button.xml.remove(name + "x"); + button.xml.remove(name + "y"); + + obj.x = x.value; + obj.y = y.value; + + if(x.value == y.value) + button.xml.set(name, x.value); + else { + button.xml.set(name + "x", x.value); + button.xml.set(name + "y", y.value); + } + } + function save(name:String, value:Dynamic) { + button.xml.remove(name); + button.xml.set(name, value); + } + + // TODO: remove old attributes to prevent possible errors + // button.xml.remove(); + + button.xml.set("name", nameTextBox.label.text); + button.xml.set("sprite", spriteTextBox.label.text); + button.xml.set("x", xStepper.value); + button.xml.set("y", yStepper.value); + button.xml.set("zoom", zoomFactorStepper.value); + button.xml.set("type", animType.key); + button.xml.set("antialiasing", antialiasingCheckbox.checked); + button.xml.set("angle", angleStepper.value); + button.xml.set("alpha", alphaStepper.value); + + saveXY(sprite.scrollFactor, "scroll", scrollXStepper, scrollYStepper); + saveXY(sprite.scale, "scale", scaleXStepper, scaleYStepper); + saveXY(sprite.skew, "skew", skewXStepper, skewYStepper); + XMLUtil.loadSpriteFromXML(sprite, button.xml, stage.spritesParentFolder); + } + </exec> +</window> \ No newline at end of file diff --git a/assets/data/editors/layouts/stage/stageInfoScreen.xml b/assets/data/editors/layouts/stage/stageInfoScreen.xml new file mode 100644 index 00000000..1a477da8 --- /dev/null +++ b/assets/data/editors/layouts/stage/stageInfoScreen.xml @@ -0,0 +1,110 @@ +<window width="470" height="620" title="Editing Stage Info '${stage.stageName}'"> + <set name="MARGIN" value="20" /> + <set name="XOFFSET" value="50" /> + <set name="YOFFSET" value="40" /> + <set name="TO" value="64" /> + <set name="storedProps" value="[]" /> + + <exec> + import haxe.ds.StringMap; + var oldInfo = { + name: stage.stageName, + folder: stage.spritesParentFolder, + startCamX: stage.startCam.x, + startCamY: stage.startCam.y, + zoom: stage.defaultZoom, + attrib: new StringMap() + }; + + function col(c:Int) { + return MARGIN + (170 + XOFFSET) * c; + } + + for(prop => val in stage.extra) { + if(!StringTools.startsWith(prop, "stageEditor.")) { + storedProps.push(prop); + oldInfo.attrib.set(prop, val); + } + } + </exec> + + <title name="title" x="MARGIN" y="30 + 16" text="Stage Info" /> + + <section> <!-- ROW 1 --> + <set name="curY" value="title.y + TO" /> + + <textbox name="nameTextBox" label="Stage Name" value="stage.stageName" x="col(0)" y="curY" width="200" height="32" /> + <textbox name="spritePathTextBox" value="stage.spritesParentFolder" x="col(1)" y="curY" width="200" height="32" /> + <label text="Sprite Path (Needs Refresh)" x="spritePathTextBox.x" y="spritePathTextBox.y - 14" size="15" /> + </section> + + <section> <!-- ROW 2 --> + <set name="curY" value="nameTextBox.y + nameTextBox.bHeight + YOFFSET" /> + + <label text="Start Cam Pos (X, Y)" x="col(0)" y="curY" /> + <stepper name="camxStepper" value="stage.startCam.x" x="col(0)" y="curY" step="1" width="120" /> + <stepper name="camyStepper" value="stage.startCam.y" x="last.x + last.bWidth + XOFFSET - 30" y="curY" step="1" width="120" /> + + <stepper name="zoomStepper" label="Zoom" value="stage.defaultZoom" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> + <set name="curY" value="camxStepper.y + camxStepper.bHeight + YOFFSET" /> + <set name="listWidth" value="self.winWidth - MARGIN * 2" /> + + <buttonlist name="attributesList" label="Custom Attributes" x="col(0)" y="curY" width="listWidth" height="290" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5"> + <texture path="editors/ui/inputbox" /> + <camSpacing value="0" /> + + <code> + import funkin.editors.extra.PropertyButton; + + var buttonList = buttonlist; // important for now, since it gets reset after the code is run + + var width = buttonList.buttonSize.x; + var height = buttonList.buttonSize.y; + + buttonList.addButton.callback = function() { + buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 150, 220, 25)); + } + + //var defaultAttribs = Stage.DEFAULT_ATTRIBUTES; + //for (i in stage.stageXML.attributes()) { + // trace(i, defaultAttribs); + // if (!defaultAttribs.contains(i)) + // buttonList.add(new PropertyButton(i, stage.stageXML.get(i), buttonList, width, height, 150, 220, 25)); + //} + for(key => val in stage.extra) + buttonList.add(new PropertyButton(key, val, buttonList, width, height, 150, 220, 25)); + </code> + </buttonlist> + </section> + + <exec> + function onSave() { + stage.startCam.x = camxStepper.value; + stage.startCam.y = camyStepper.value; + stage.defaultZoom = zoomStepper.value; + stage.stageName = nameTextBox.label.text; + stage.spritesParentFolder = spritePathTextBox.label.text; + + for(prop in storedProps) + stage.extra.remove(prop); + + var newInfo = { + name: stage.stageName, + folder: stage.spritesParentFolder, + startCamX: stage.startCam.x, + startCamY: stage.startCam.y, + zoom: stage.defaultZoom, + attrib: new StringMap() + }; + for(prop in attributesList.buttons.members) { + stage.extra.set(prop.propertyText.label.text, prop.valueText.label.text); + newInfo.attrib.set(prop.propertyText.label.text, prop.valueText.label.text); + } + + FlxG.state.undos.addToUndo(CEditInfo(oldInfo, newInfo)); + } + </exec> +</window> \ No newline at end of file diff --git a/assets/data/editors/layouts/stage/unknownEditScreen.xml b/assets/data/editors/layouts/stage/unknownEditScreen.xml new file mode 100644 index 00000000..ec6f3d71 --- /dev/null +++ b/assets/data/editors/layouts/stage/unknownEditScreen.xml @@ -0,0 +1,66 @@ +<window width="350" height="570" title="Editing XML <${button.getName()} />"> + <set name="MARGIN" value="20" /> + <set name="XOFFSET" value="30" /> + <set name="YOFFSET" value="40" /> + <set name="TO" value="64" /> + + <exec> + function col(c:Int) { + return MARGIN + (150 + XOFFSET) * c; + } + </exec> + + <title name="title" x="MARGIN" y="30 + 16" text="Xml Data" /> + + <section> + <set name="curY" value="title.y + TO" /> + + <textbox name="nodeNameTextBox" label="Element Name" value="button.getName()" x="col(0)" y="curY" width="280" height="32" /> + </section> + <section> + <set name="curY" value="nodeNameTextBox.y + nodeNameTextBox.bHeight + YOFFSET - 30" /> + + <radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="button.highMemory" x="col(0)" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="button.lowMemory" x="col(0)" y="last.y + last.check.height + YOFFSET - 30" /> + </section> + + <section> + <set name="curY" value="lowMemoryRadio.y + lowMemoryRadio.check.height + YOFFSET" /> + <set name="listWidth" value="self.winWidth - MARGIN * 2" /> + + <buttonlist name="attributesList" label="Attributes" x="col(0)" y="curY" width="listWidth" height="260" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5"> + <texture path="editors/ui/inputbox" /> + <camSpacing value="0" /> + + <code> + import funkin.editors.extra.PropertyButton; + + var buttonList = buttonlist; // important for now, since it gets reset after the code is run + + var width = buttonList.buttonSize.x; + var height = buttonList.buttonSize.y; + + buttonList.addButton.callback = function() { + buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 110, 145, 25)); + } + + for (i in xml.attributes()) + buttonList.add(new PropertyButton(i, xml.get(i), buttonList, width, height, 110, 145, 25)); + </code> + </buttonlist> + </section> + + <exec> + function onSave() { + button.xml.nodeName = nodeNameTextBox.label.text; + button.highMemory = highMemoryRadio.checked; + button.lowMemory = lowMemoryRadio.checked; + + for (i in xml.attributes()) + xml.remove(i); + + for (val in attributesList.buttons.members) + xml.set(val.propertyText.label.text, val.valueText.label.text); + } + </exec> +</window> \ No newline at end of file diff --git a/assets/data/editors/layouts/stage/xmlEditScreen.xml b/assets/data/editors/layouts/stage/xmlEditScreen.xml new file mode 100644 index 00000000..7a7ac956 --- /dev/null +++ b/assets/data/editors/layouts/stage/xmlEditScreen.xml @@ -0,0 +1,51 @@ +<window width="450" height="570" title="Editing XML ${type}"> + <!-- BASED ON unknownEditScreen.xml --> + <set name="MARGIN" value="20" /> + <set name="XOFFSET" value="30" /> + <set name="YOFFSET" value="40" /> + <set name="TO" value="64" /> + + <exec> + function col(c:Int) { + return MARGIN + (150 + XOFFSET) * c; + } + </exec> + + <title name="title" x="MARGIN" y="30 + 16" text="Xml Data" /> + + <section> + <set name="curY" value="title.y + TO" /> + <set name="listWidth" value="self.winWidth - MARGIN * 2" /> + + <buttonlist name="attributesList" label="Attributes" x="col(0)" y="curY" width="listWidth" height="400" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5"> + <texture path="editors/ui/inputbox" /> + <camSpacing value="0" /> + + <code> + import funkin.editors.extra.PropertyButton; + + var buttonList = buttonlist; // important for now, since it gets reset after the code is run + + var width = buttonList.buttonSize.x; + var height = buttonList.buttonSize.y; + + buttonList.addButton.callback = function() { + buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 160, 195, 25)); + } + + for (i in xml.attributes()) + buttonList.add(new PropertyButton(i, xml.get(i), buttonList, width, height, 160, 195, 25)); + </code> + </buttonlist> + </section> + + <exec> + function onSave() { + for (i in xml.attributes()) + xml.remove(i); + + for (val in attributesList.buttons.members) + xml.set(val.propertyText.label.text, val.valueText.label.text); + } + </exec> +</window> \ No newline at end of file diff --git a/assets/data/global.hx b/assets/data/global.hx deleted file mode 100644 index 4c49ef69..00000000 --- a/assets/data/global.hx +++ /dev/null @@ -1,3 +0,0 @@ -// -function update(elapsed:Float) - if (FlxG.keys.justPressed.F5) FlxG.resetState(); \ No newline at end of file diff --git a/assets/data/notes/Alt Anim Note.hx b/assets/data/notes/Alt Anim Note.hx index b44b1398..ebc3e361 100644 --- a/assets/data/notes/Alt Anim Note.hx +++ b/assets/data/notes/Alt Anim Note.hx @@ -1,2 +1,2 @@ function onNoteHit(event) if (event.noteType == "Alt Anim Note") event.animSuffix = "-alt"; -function onPlayerMiss(event) if (event.noteType == "Alt Anim Note") event.animSuffix = "-alt"; \ No newline at end of file +function onPlayerMiss(event) if (event.noteType == "Alt Anim Note") event.animSuffix = "miss-alt"; \ No newline at end of file diff --git a/assets/data/scripts/pixel.hx b/assets/data/scripts/pixel.hx index 22c4e611..7031838e 100644 --- a/assets/data/scripts/pixel.hx +++ b/assets/data/scripts/pixel.hx @@ -18,44 +18,47 @@ public var daPixelZoom = PlayState.daPixelZoom; * UI */ function onNoteCreation(event) { - if (event.note.strumLine == playerStrums && !pixelNotesForBF) return; - if (event.note.strumLine == cpuStrums && !pixelNotesForDad) return; - + if ((event.note.strumLine == playerStrums && !pixelNotesForBF) || (event.note.strumLine == cpuStrums && !pixelNotesForDad)) return; event.cancel(); var note = event.note; + var strumID = event.strumID; if (event.note.isSustainNote) { note.loadGraphic(Paths.image('stages/school/ui/arrowEnds'), true, 7, 6); - note.animation.add("hold", [event.strumID]); - note.animation.add("holdend", [4 + event.strumID]); + var maxCol = Math.floor(note.graphic.width / 7); + note.animation.add("hold", [strumID%maxCol]); + note.animation.add("holdend", [maxCol + strumID%maxCol]); } else { note.loadGraphic(Paths.image('stages/school/ui/arrows-pixels'), true, 17, 17); - note.animation.add("scroll", [4 + event.strumID]); + var maxCol = Math.floor(note.graphic.width / 17); + note.animation.add("scroll", [maxCol + strumID%maxCol]); } - note.scale.set(daPixelZoom, daPixelZoom); + var strumScale = event.note.strumLine.strumScale; + note.scale.set(daPixelZoom*strumScale, daPixelZoom*strumScale); note.updateHitbox(); + note.antialiasing = false; } -function onPostNoteCreation(event) { - var splashes = event.note; - if (pixelSplashes) - splashes.splash = "pixel-default"; -} +function onPostNoteCreation(event) if (pixelSplashes) + event.note.splash = "pixel-default"; function onStrumCreation(event) { - if (event.player == 1 && !pixelNotesForBF) return; - if (event.player == 0 && !pixelNotesForDad) return; - + if ((event.player == 1 && !pixelNotesForBF) || (event.player == 0 && !pixelNotesForDad)) return; event.cancel(); var strum = event.strum; strum.loadGraphic(Paths.image('stages/school/ui/arrows-pixels'), true, 17, 17); - strum.animation.add("static", [event.strumID]); - strum.animation.add("pressed", [4 + event.strumID, 8 + event.strumID], 12, false); - strum.animation.add("confirm", [12 + event.strumID, 16 + event.strumID], 24, false); + var maxCol = Math.floor(strum.graphic.width / 17); + var strumID = event.strumID % maxCol; - strum.scale.set(daPixelZoom, daPixelZoom); + strum.animation.add("static", [strumID]); + strum.animation.add("pressed", [maxCol + strumID, (maxCol*2) + strumID], 12, false); + strum.animation.add("confirm", [(maxCol*3) + strumID, (maxCol*4) + strumID], 24, false); + + var strumScale = strumLines.members[event.player].strumScale; + strum.scale.set(daPixelZoom*strumScale, daPixelZoom*strumScale); strum.updateHitbox(); + strum.antialiasing = false; } function onCountdown(event) { @@ -86,9 +89,9 @@ function onPlayerHit(event:NoteHitEvent) { * CAMERA HACKS!! */ function postCreate() { - if (enablePauseMenu) { + if (enablePauseMenu) PauseSubState.script = 'data/scripts/week6-pause'; - } + if (enableCameraHacks) { camGame.pixelPerfectRender = true; camGame.antialiasing = false; @@ -97,9 +100,6 @@ function postCreate() { defaultCamZoom /= daPixelZoom; } - iconP1.antialiasing = false; - iconP2.antialiasing = false; - if (enablePixelGameOver) { gameOverSong = "pixel/gameOver"; lossSFX = "pixel/gameOverSFX"; @@ -107,8 +107,8 @@ function postCreate() { } } -function onStartCountdown() { - /*var newNoteCamera = new HudCamera(); +/*function onStartCountdown() { + var newNoteCamera = new HudCamera(); newNoteCamera.bgColor = 0; // transparent FlxG.cameras.add(newNoteCamera, false); @@ -122,8 +122,8 @@ function onStartCountdown() { i++; } } - makeCameraPixely(newNoteCamera);*/ -} + makeCameraPixely(newNoteCamera); +}*/ /** * Use this to make any camera pixelly (you wont be able to zoom with it anymore!) @@ -148,33 +148,29 @@ function destroy() { FlxG.game.stage.quality = oldStageQuality; } -function pixelCam(cam) { +function pixelCam(cam) makeCameraPixely(cam); -} var pixellyCameras = []; var pixellyShaders = []; -function postUpdate(elapsed) { - for(e in pixellyCameras) { - if (Std.isOfType(e, HudCamera)) - e.downscroll = camHUD.downscroll; - } - if (enableCameraHacks) { - for(p in strumLines) - p.notes.forEach(function(n) { - if(n.isSustainNote) return; // hacky fix for hold - n.y -= n.y % daPixelZoom; - n.x -= n.x % daPixelZoom; - }); +function postUpdate() { + for (e in pixellyCameras) if (Std.isOfType(e, HudCamera)) + e.downscroll = camHUD.downscroll; + + if (enableCameraHacks) for (p in strumLines) { + p.notes.forEach(function(n) { + if(n.isSustainNote) return; // hacky fix for hold + n.y -= n.y % daPixelZoom; + n.x -= n.x % daPixelZoom; + }); } var zoom = 1 / daPixelZoom / Math.min(FlxG.scaleMode.scale.x, FlxG.scaleMode.scale.y); - for(e in pixellyCameras) { + for (e in pixellyCameras) { if (!e.exists) continue; e.zoom = zoom; } - for(e in pixellyShaders) { + for (e in pixellyShaders) e.pixelZoom = zoom; - } -} \ No newline at end of file +} diff --git a/assets/data/scripts/week6-pause.hx b/assets/data/scripts/week6-pause.hx index 4eb71349..1538cff7 100644 --- a/assets/data/scripts/week6-pause.hx +++ b/assets/data/scripts/week6-pause.hx @@ -3,7 +3,7 @@ import flixel.text.FlxText; import flixel.text.FlxTextBorderStyle; import flixel.util.FlxAxes; -var pixelScript:Script; +var pixelScript:Script = game.scripts.getByName("pixel.hx"); var pauseCam = new FlxCamera(); var bg:FlxSprite; @@ -19,47 +19,40 @@ function create(event) { event.music = isThorns ? "pixel/LunchboxScary" : "pixel/breakfast"; - cameras = []; - - pixelScript = game.scripts.getByName("pixel.hx"); - pixelScript.call("pixelCam", [pauseCam]); + // allowing this pause script even if the pixel script is not loaded - Nex + pixelScript?.call("pixelCam", [pauseCam]); FlxG.cameras.add(pauseCam, false); + cameras = [pauseCam]; pauseCam.bgColor = isThorns ? 0x88000000 : 0x88FF99CC; pauseCam.alpha = 0; - bg = new FlxSprite(44 * 6, 14 * 6); - bg.loadGraphic(Paths.image('stages/school/pause/bg')); - if (isThorns) - bg.color = 0xFF000000; + bg = new FlxSprite(44 * 6, 14 * 6, Paths.image('stages/school/pause/bg')); + bg.antialiasing = false; + if (isThorns) bg.color = 0xFF000000; bg.scale.set(6, 6); bg.updateHitbox(); bg.scale.y = 4; - bg.cameras = [pauseCam]; add(bg); songText = new FlxText(0, 22 * 6, 0, "Pause", 8, false); confText(songText); add(songText); - var i = 2; - for(e in menuItems) { - text = new FlxText(0, (22 * 6) + (i * 9 * 6), 0, e, 8, false); + for (i=>e in menuItems) { + text = new FlxText(0, (22 * 6) + ((i+2) * 9 * 6), 0, e, 8, false); confText(text); - add(text); - texts.push(text); - i++; } hand = new FlxSprite().loadGraphic(Paths.image('stages/school/ui/hand_textbox')); + hand.antialiasing = false; hand.scale.set(6, 6); hand.updateHitbox(); add(hand); - FlxTween.tween(bg, {"scale.y": 6}, 0.75, {ease: FlxEase.elasticOut}); + FlxTween.tween(bg.scale, {y: 6}, 0.75, {ease: FlxEase.elasticOut}); - cameras = [pauseCam]; FlxG.sound.play(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText')); } @@ -70,55 +63,50 @@ function confText(text) { text.screenCenter(FlxAxes.X); text.borderStyle = FlxTextBorderStyle.OUTLINE; if (!isThorns) text.borderColor = 0xFF953E3E; + + text.antialiasing = false; + texts.push(text); + add(text); } -function destroy() { - if(FlxG.cameras.list.contains(pauseCam)) - FlxG.cameras.remove(pauseCam); -} +function destroy() if (FlxG.cameras.list.contains(pauseCam)) + FlxG.cameras.remove(pauseCam); var canDoShit = true; var time:Float = 0; function update(elapsed) { - pixelScript.call("postUpdate", [elapsed]); + pixelScript?.call("postUpdate", [elapsed]); pauseCam.alpha = lerp(pauseCam.alpha, 1, 0.25); time += elapsed; - var curText = texts[curSelected]; + var curText = texts[curSelected + 1]; hand.setPosition(curText.x - hand.width - 18 + (Math.sin(time * Math.PI * 2) * 12), curText.y + (text.height - hand.height) - 6); hand.x -= hand.x % 6; hand.y -= hand.y % 6; - if (!canDoShit) return; - var oldSec = curSelected; - changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel); - if (oldSec != curSelected) - FlxG.sound.play(Paths.sound(isThorns ? 'pixel/type' : 'pixel/pixelText')); + if (controls.ACCEPT) enterOption(); +} - if (controls.ACCEPT) { - FlxG.sound.play(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText')); - var option = menuItems[curSelected]; - if (option == "Resume" || option == "Exit to menu") { +var scrollSFX = FlxG.sound.load(Paths.sound(isThorns ? 'pixel/type' : 'pixel/pixelText')); +function changeSelection(change) if (canDoShit) { // this overrides the function inside of the normal pause btw, so no event gets called - Nex + curSelected = FlxMath.wrap(curSelected + change, 0, menuItems.length - 1); + if (change != 0) scrollSFX.play(); +} + +var enterSFX = FlxG.sound.load(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText')); +function enterOption() if (canDoShit) { + var option = menuItems[curSelected]; + enterSFX.play(); + + switch(option) { + case "Resume", "Exit to menu": canDoShit = false; for(t in texts) t.visible = false; hand.visible = songText.visible = false; - FlxTween.tween(bg, {"scale.y": 0}, 0.125, {ease: FlxEase.cubeOut, onComplete: function() { - selectOption(); - }}); - } else { - selectOption(); - } + FlxTween.tween(bg.scale, {y: 0}, 0.125, {ease: FlxEase.cubeOut, onComplete: selectOption}); + default: selectOption(); } } - -function changeSelection(change) { // this overrides the function inside of the normal pause btw, so no event gets called - Nex - curSelected += change; - - if (curSelected < 0) - curSelected = menuItems.length - 1; - if (curSelected >= menuItems.length) - curSelected = 0; -} \ No newline at end of file diff --git a/assets/data/scripts/week7-balledLines.hx b/assets/data/scripts/week7-balledLines.hx index 677d3791..d6049a48 100644 --- a/assets/data/scripts/week7-balledLines.hx +++ b/assets/data/scripts/week7-balledLines.hx @@ -3,13 +3,7 @@ var sound:Sound; function create() sound = Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, !Options.naughtyness ? [1, 3, 8, 13, 17, 21] : null)); -function beatHit(cur:Int) { - if(cur != 0) return; - +function beatHit(cur:Int) if (cur == 0) { + FlxG.sound.play(sound, 1, false, null, true, () -> if (!isEnding) FlxG.sound.music.fadeIn(4, 0.2, 1)); FlxG.sound.music.volume = 0.2; - FlxG.sound.play(sound, 1, false, null, true, function() - { - if (!isEnding) - FlxG.sound.music.fadeIn(4, 0.2, 1); - }); } \ No newline at end of file diff --git a/assets/data/stages/tank.hx b/assets/data/stages/tank.hx index 03e213c6..e74b04d9 100644 --- a/assets/data/stages/tank.hx +++ b/assets/data/stages/tank.hx @@ -1,7 +1,7 @@ var tankAngle:Float = FlxG.random.int(-90, 45); var tankSpeed:Float = FlxG.random.float(5, 7); -function postCreate() if(PlayState.isStoryMode) +function postCreate() if (PlayState.isStoryMode) GameOverSubstate.script = 'data/scripts/week7-balledLines'; function update(elapsed:Float) { @@ -12,7 +12,7 @@ function update(elapsed:Float) { } function onPostCountdown(event) { - if(!playCutscenes || event.sprite == null) return; + if (!playCutscenes || event.sprite == null) return; var lastIndex:Int = members.indexOf(event.sprite); remove(event.sprite); insert(lastIndex + 4, event.sprite); diff --git a/assets/data/stages/test.xml b/assets/data/stages/test.xml new file mode 100644 index 00000000..697711bb --- /dev/null +++ b/assets/data/stages/test.xml @@ -0,0 +1,12 @@ +<!-- praying to god we remove this file later on --> + +<stage zoom="0.9" name="stage" folder="stages/default/" startCamPosY="600" startCamPosX="1000" example="value"> + <sprite name="normBG" sprite="stagefront" x="-500" y="100" scale="0.75" updateHitbox="true" /> + <sprite name="noUpdateHitbox" sprite="stagefront" x="-500" y="300" scale="0.75" /> + <sprite name="ro-tat-ay" sprite="stagefront" x="-500" y="700" scale="0.75" updateHitbox="true" angle="37" /> + <box name="box" x="0" y="0" width="200" height="200" color="0xFF0000" /> + <solid name="box2" x="0" y="300" width="200" height="200" color="0x00FF00" /> + <unknown-sprite /> + + <unknown-sprite-1-attr name="test" /> +</stage> \ No newline at end of file diff --git a/assets/data/states/StageEditor.hx b/assets/data/states/StageEditor.hx new file mode 100644 index 00000000..af9f6cbe --- /dev/null +++ b/assets/data/states/StageEditor.hx @@ -0,0 +1,328 @@ +/** +* THIS FILE IS TEMPORARY UNTIL TRANSFORMS ARE AT A USEABLE STATE. +* TODO: +* - !!!! MOVE BACK TO SOURCE !!!! +* - Rotation +* - Skewing +* - Angle + Scale (like already at a angle) +*/ + +import funkin.editors.stage.StageEditor; +import funkin.editors.ui.UIWarningSubstate; +import funkin.backend.utils.WindowUtils; +import funkin.backend.utils.MatrixUtil; +import flixel.math.FlxAngle; +import openfl.Lib; + +var exID = StageEditor.exID; + +function tryUpdateHitbox(sprite) { + var spriteNode = sprite.extra.get(exID("node")); + if (spriteNode.exists("updateHitbox") && spriteNode.get("updateHitbox") == "true") { + sprite.updateHitbox(); + return true; + } + + if (!FlxG.keys.pressed.ALT) { + sprite.x = storedPos.x - (sprite.frameWidth * (storedScale.x - sprite.scale.x) * 0.5); + sprite.y = storedPos.y - (sprite.frameHeight * (storedScale.y - sprite.scale.y) * 0.5); + } + return false; +} + +var allowClose = false; +function create() { + WindowUtils.preventClosing = true; + WindowUtils.resetClosing(); + WindowUtils.onClosing = function() { + if(!allowClose) + Lib.application.window.onClose.cancel(); + + var substate; + substate = new UIWarningSubstate("nuh uh", "you dont need to close goofus doofus", [ + { + label: "Actually Exit", + onClick: function(t) { + allowClose = true; + Lib.application.window.close(); + } + }, + { + label: "Reload State", + onClick: function(t) { + FlxG.switchState(new StageEditor(StageEditor.__stage)); + } + }, + { + label: "oh whoops", + onClick: function(t) { + substate.close(); + } + } + ]); + + openSubState(substate); + } +} + +function destroy() { + WindowUtils.preventClosing = false; + WindowUtils.resetClosing(); + WindowUtils.onClosing = null; +} + +function update() { + if (FlxG.mouse.justPressed) + lastRelative.set(); + + if(FlxG.keys.justPressed.R) { + trace("reloading"); + FlxG.switchState(new StageEditor(StageEditor.__stage)); + } +} + +function genericScale(sprite, relative, doX, doY) { + + var relativeMult = 1 / (FlxMath.lerp(1, stageCamera.zoom, sprite.zoomFactor) / stageCamera.zoom) * (FlxG.keys.pressed.ALT ? 2 : 1); + relative.x *= relativeMult; + relative.y *= relativeMult; + + var width = sprite.frameWidth * storedScale.x; + var height = sprite.frameHeight * storedScale.y; + if(doX) width -= relative.x; + if(doY) height -= relative.y; + CoolUtil.setGraphicSizeFloat(sprite, width, height); + + if(FlxG.keys.pressed.SHIFT) { + var nscale = Math.max(sprite.scale.x, sprite.scale.y); + sprite.scale.set(nscale, nscale); + } + + var updatedHitbox = tryUpdateHitbox(sprite); + if (FlxG.keys.pressed.ALT) { + sprite.x = storedPos.x; + sprite.y = storedPos.y; + if(updatedHitbox) { + sprite.x += (sprite.frameWidth * (storedScale.x - sprite.scale.x) * 0.5); + sprite.y += (sprite.frameHeight * (storedScale.y - sprite.scale.y) * 0.5); + } + return true; + } + return !updatedHitbox; +} + +function genericOppositeScale(sprite, relative, scaleX, scaleY, repositionX, repositionY) { + if(repositionX) relative.x *= -1; + if(repositionY) relative.y *= -1; + var repositioned = genericScale(sprite, relative, scaleX, scaleY); + if (!repositioned) { + if(repositionX) sprite.x = storedPos.x + (sprite.frameWidth * (storedScale.x - sprite.scale.x)); + if(repositionY) sprite.y = storedPos.y + (sprite.frameHeight * (storedScale.y - sprite.scale.y)); + } else if (!FlxG.keys.pressed.ALT) { + if(repositionX) sprite.x += (sprite.frameWidth * (storedScale.x - sprite.scale.x)); + if(repositionY) sprite.y += (sprite.frameHeight * (storedScale.y - sprite.scale.y)); + } +} + +var oldSpritePos = FlxPoint.get(); +function SCALE_BOTTOM_RIGHT(sprite, relative) { + preRotBullshit(sprite, relative); + genericScale(sprite, relative, true, true); + postRotBullshit(sprite, relative); +} + +/** + ROTATION +**/ +function preRotBullshit(sprite, relative) { + if (sprite.angle != 0) + relative = rotateByDegrees(relative, -sprite.angle); + + if (FlxG.mouse.justPressed) { + oldSpritePos.x = sprite.x; + oldSpritePos.y = sprite.y; + } +} + +function postRotBullshit(sprite, relative) { + if (sprite.angle != 0) { + var p = rotateAround(FlxPoint.get(sprite.x, sprite.y), oldSpritePos, sprite.angle); + sprite.x = p.x; + sprite.y = p.y; + } +} + +function rotateAround(p, origin, angle) { + var rel = FlxPoint.get(p.x - origin.x, p.y - origin.y); + rotateByDegrees(rel, angle); + p.x = origin.x + rel.x; + p.y = origin.y + rel.y; + rel.put(); + return p; +} +function rotateByDegrees(p, angle) { + var rads = angle * FlxAngle.TO_RAD; + var s:Float = Math.sin(rads); + var c:Float = Math.cos(rads); + var tempX:Float = p.x; + + p.x = tempX * c - p.y * s; + p.y = tempX * s + p.y * c; +} + /** + END OF ROTATION + **/ + + +function SCALE_TOP_RIGHT(sprite, relative) { + preRotBullshit(sprite, relative); + genericOppositeScale(sprite, relative, true, true, false, true); + postRotBullshit(sprite, relative); +} + +function MOVE_CENTER(sprite, relative) { + sprite.x = storedPos.x-relative.x; + sprite.y = storedPos.y-relative.y; +} + +function SCALE_TOP_LEFT(sprite, relative) { + preRotBullshit(sprite, relative); + genericOppositeScale(sprite, relative, true, true, true, true); + postRotBullshit(sprite, relative); +} + +function SCALE_BOTTOM_LEFT(sprite, relative) { + preRotBullshit(sprite, relative); + genericOppositeScale(sprite, relative, true, true, true, false); + postRotBullshit(sprite, relative); +} + +function SCALE_LEFT(sprite, relative) { + preRotBullshit(sprite, relative); + genericOppositeScale(sprite, relative, true, false, true, false); + postRotBullshit(sprite, relative); +} + +function SCALE_RIGHT(sprite, relative) { + preRotBullshit(sprite, relative); + genericScale(sprite, relative, true, false); + postRotBullshit(sprite, relative); +} + +function SCALE_TOP(sprite, relative) { + preRotBullshit(sprite, relative); + genericOppositeScale(sprite, relative, false, true, false, true); + postRotBullshit(sprite, relative); +} + +function SCALE_BOTTOM(sprite, relative) { + preRotBullshit(sprite, relative); + genericScale(sprite, relative, false, true); + postRotBullshit(sprite, relative); +} + +var lastRelative = FlxPoint.get(); +var skewPoint1 = FlxPoint.get(); +var skewPoint2 = FlxPoint.get(); + +function SKEW_LEFT(sprite, relative) { + preRotBullshit(sprite, relative); + if (!FlxG.keys.pressed.SHIFT) { + genericOppositeScale(sprite, relative, true, false, true, false); + postRotBullshit(sprite, relative); + } + + skewPoint1.set(0, 0); + skewPoint2.set(1, 0); + gimmeSkewCorners(sprite, 0, 1); + + sprite.y = storedPos.y - relative.y * 0.5; + sprite.skew.y += ((relative.y - lastRelative.y) * 0.02); + + lastRelative.set((FlxG.keys.pressed.SHIFT ? lastRelative.x : relative.x), relative.y); +} + +function SKEW_BOTTOM(sprite, relative) { + preRotBullshit(sprite, relative); + if (!FlxG.keys.pressed.SHIFT) { + genericScale(sprite, relative, false, true); + postRotBullshit(sprite, relative); + } + + skewPoint1.set(0, 0); + skewPoint2.set(0, 1); + gimmeSkewCorners(sprite, 0, 2); + + sprite.x = storedPos.x - relative.x * 0.4; + sprite.skew.x -= ((relative.x - lastRelative.x) * 0.03); + + lastRelative.set(relative.x, (FlxG.keys.pressed.SHIFT ? lastRelative.y : relative.y)); +} + +function SKEW_TOP(sprite, relative) { + preRotBullshit(sprite, relative); + if (!FlxG.keys.pressed.SHIFT) { + genericOppositeScale(sprite, relative, false, true, false, true); + postRotBullshit(sprite, relative); + } + + skewPoint1.set(0, 0); + skewPoint2.set(0, 1); + gimmeSkewCorners(sprite, 0, 2); + + sprite.x = storedPos.x - relative.x * 0.4; + sprite.skew.x += ((relative.x - lastRelative.x) * 0.03); + + lastRelative.set(relative.x, (FlxG.keys.pressed.SHIFT ? lastRelative.y : relative.y)); +} + +function SKEW_RIGHT(sprite, relative) { + preRotBullshit(sprite, relative); + if (!FlxG.keys.pressed.SHIFT) { + genericScale(sprite, relative, true, false); + postRotBullshit(sprite, relative); + } + + skewPoint1.set(0, 0); + skewPoint2.set(1, 0); + gimmeSkewCorners(sprite, 0, 1); + + sprite.y = storedPos.y - relative.y * 0.5; + sprite.skew.y -= ((relative.y - lastRelative.y) * 0.02); + + lastRelative.set((FlxG.keys.pressed.SHIFT ? lastRelative.x : relative.x), relative.y); +} + +function ROTATE(sprite, relative) { + var buttonBoxes:Array<FlxPoint> = sprite.extra.get(exID("buttonBoxes")); + var p:FlxPoint = buttonBoxes[8]; + + FlxG.mouse.getWorldPosition(stageCamera, _point); + + var dx:Float = _point.x - p.x; + var dy:Float = _point.y - p.y; + var angle = FlxAngle.angleFromOrigin(dx, dy, true) + angleOffset; + if(FlxG.keys.pressed.SHIFT) angle = Std.int(angle / 45) * 45; + sprite.angle = angle; +} + + +/*function postDraw() { + if(storedCenter.x != 0 || storedCenter.y != 0) { + drawLine(storedCenter, storedRelative, 0.3); + } +}*/ + +function gimmeSkewCorners(sprite, index1, index2) { + if (sprite.angle == 0) { + var corners = sprite.extra.get(exID("buttonBoxes")); + skewPoint1.set(corners[index1].x, corners[index1].y); + skewPoint2.set(corners[index2].x, corners[index2].y); + return; + } + + var ogAngle = sprite.angle; + sprite.angle = 0; + MatrixUtil.getMatrixPosition(sprite, [skewPoint1, skewPoint2], sprite.camera, sprite.frameWidth, sprite.frameHeight); + sprite.angle = ogAngle; +} \ No newline at end of file diff --git a/assets/data/weeks/characters/spooky.xml b/assets/data/weeks/characters/spooky.xml index e4955b1f..3b32cd36 100644 --- a/assets/data/weeks/characters/spooky.xml +++ b/assets/data/weeks/characters/spooky.xml @@ -1,2 +1,2 @@ <!DOCTYPE codename-engine-week-character> -<char scale="0.5" x="80" y="-50"/> \ No newline at end of file +<char scale="0.5" x="80" y="-50" beatInterval="1"/> \ No newline at end of file diff --git a/assets/images/characters/bf-car.png b/assets/images/characters/bf-car.png index 41d64ad2..6623e0ea 100644 Binary files a/assets/images/characters/bf-car.png and b/assets/images/characters/bf-car.png differ diff --git a/assets/images/characters/bf-christmas.png b/assets/images/characters/bf-christmas.png index 98fc69b9..9b6c1649 100644 Binary files a/assets/images/characters/bf-christmas.png and b/assets/images/characters/bf-christmas.png differ diff --git a/assets/images/characters/bf-dead.png b/assets/images/characters/bf-dead.png index 6d129a65..1cff4b78 100644 Binary files a/assets/images/characters/bf-dead.png and b/assets/images/characters/bf-dead.png differ diff --git a/assets/images/characters/bf-holding-gf-dead.png b/assets/images/characters/bf-holding-gf-dead.png index 1d6f3844..7d15df94 100644 Binary files a/assets/images/characters/bf-holding-gf-dead.png and b/assets/images/characters/bf-holding-gf-dead.png differ diff --git a/assets/images/characters/bf-holding-gf.png b/assets/images/characters/bf-holding-gf.png index 0549ae76..d6482460 100644 Binary files a/assets/images/characters/bf-holding-gf.png and b/assets/images/characters/bf-holding-gf.png differ diff --git a/assets/images/characters/bf-pixel-dead.png b/assets/images/characters/bf-pixel-dead.png index ddb688a2..bd8dbcb9 100644 Binary files a/assets/images/characters/bf-pixel-dead.png and b/assets/images/characters/bf-pixel-dead.png differ diff --git a/assets/images/characters/bf-pixel.png b/assets/images/characters/bf-pixel.png index eb78553d..7abde04c 100644 Binary files a/assets/images/characters/bf-pixel.png and b/assets/images/characters/bf-pixel.png differ diff --git a/assets/images/characters/bf.png b/assets/images/characters/bf.png index 2722d1c1..ce924dd2 100644 Binary files a/assets/images/characters/bf.png and b/assets/images/characters/bf.png differ diff --git a/assets/images/characters/da.png b/assets/images/characters/da.png index 9a1a2da5..e711edb9 100644 Binary files a/assets/images/characters/da.png and b/assets/images/characters/da.png differ diff --git a/assets/images/characters/dad.png b/assets/images/characters/dad.png index cd37de1c..5f678a27 100644 Binary files a/assets/images/characters/dad.png and b/assets/images/characters/dad.png differ diff --git a/assets/images/characters/gf-car/spritemap1.png b/assets/images/characters/gf-car/spritemap1.png index c7f64af5..25fbeae7 100644 Binary files a/assets/images/characters/gf-car/spritemap1.png and b/assets/images/characters/gf-car/spritemap1.png differ diff --git a/assets/images/characters/gf-christmas/spritemap1.png b/assets/images/characters/gf-christmas/spritemap1.png index 9b28e8ab..0e81d9b9 100644 Binary files a/assets/images/characters/gf-christmas/spritemap1.png and b/assets/images/characters/gf-christmas/spritemap1.png differ diff --git a/assets/images/characters/gf-pixel.png b/assets/images/characters/gf-pixel.png index fb3d596c..aaaf95ee 100644 Binary files a/assets/images/characters/gf-pixel.png and b/assets/images/characters/gf-pixel.png differ diff --git a/assets/images/characters/gf-tankmen/spritemap1.png b/assets/images/characters/gf-tankmen/spritemap1.png index 7511af39..81f91c24 100644 Binary files a/assets/images/characters/gf-tankmen/spritemap1.png and b/assets/images/characters/gf-tankmen/spritemap1.png differ diff --git a/assets/images/characters/gf/spritemap1.png b/assets/images/characters/gf/spritemap1.png index ac70fc2d..226f417f 100644 Binary files a/assets/images/characters/gf/spritemap1.png and b/assets/images/characters/gf/spritemap1.png differ diff --git a/assets/images/characters/mom-car.png b/assets/images/characters/mom-car.png index 3c9405ac..738df30f 100644 Binary files a/assets/images/characters/mom-car.png and b/assets/images/characters/mom-car.png differ diff --git a/assets/images/characters/mom.png b/assets/images/characters/mom.png index 033929a5..6cc21a8a 100644 Binary files a/assets/images/characters/mom.png and b/assets/images/characters/mom.png differ diff --git a/assets/images/characters/monster-christmas.png b/assets/images/characters/monster-christmas.png index 7dd02243..6537b944 100644 Binary files a/assets/images/characters/monster-christmas.png and b/assets/images/characters/monster-christmas.png differ diff --git a/assets/images/characters/monster.png b/assets/images/characters/monster.png index 451d21b1..98c3eec0 100644 Binary files a/assets/images/characters/monster.png and b/assets/images/characters/monster.png differ diff --git a/assets/images/characters/parents-christmas.png b/assets/images/characters/parents-christmas.png index e13fee01..ced08d00 100644 Binary files a/assets/images/characters/parents-christmas.png and b/assets/images/characters/parents-christmas.png differ diff --git a/assets/images/characters/pico-speakers.png b/assets/images/characters/pico-speakers.png index f20d1a80..b4a2bc26 100644 Binary files a/assets/images/characters/pico-speakers.png and b/assets/images/characters/pico-speakers.png differ diff --git a/assets/images/characters/pico.png b/assets/images/characters/pico.png index 12c083d2..9a211b81 100644 Binary files a/assets/images/characters/pico.png and b/assets/images/characters/pico.png differ diff --git a/assets/images/characters/senpai.png b/assets/images/characters/senpai.png index 1c7f8f52..99600632 100644 Binary files a/assets/images/characters/senpai.png and b/assets/images/characters/senpai.png differ diff --git a/assets/images/characters/spooky.png b/assets/images/characters/spooky.png index f941ec2d..b79f6c8f 100644 Binary files a/assets/images/characters/spooky.png and b/assets/images/characters/spooky.png differ diff --git a/assets/images/characters/tankman.png b/assets/images/characters/tankman.png index edfda54a..b981a173 100644 Binary files a/assets/images/characters/tankman.png and b/assets/images/characters/tankman.png differ diff --git a/assets/images/credits/credit icon example.png b/assets/images/credits/credit icon example.png index adbf906d..cdc0034a 100644 Binary files a/assets/images/credits/credit icon example.png and b/assets/images/credits/credit icon example.png differ diff --git a/assets/images/dialogue/boxes/default.png b/assets/images/dialogue/boxes/default.png index 1c22d73b..3960d1ff 100644 Binary files a/assets/images/dialogue/boxes/default.png and b/assets/images/dialogue/boxes/default.png differ diff --git a/assets/images/dialogue/boxes/pixel/dialogueBox-evil.png b/assets/images/dialogue/boxes/pixel/dialogueBox-evil.png index fcbbfec3..18dcec5a 100644 Binary files a/assets/images/dialogue/boxes/pixel/dialogueBox-evil.png and b/assets/images/dialogue/boxes/pixel/dialogueBox-evil.png differ diff --git a/assets/images/dialogue/boxes/pixel/dialogueBox.png b/assets/images/dialogue/boxes/pixel/dialogueBox.png index 73afeea9..09d4c490 100644 Binary files a/assets/images/dialogue/boxes/pixel/dialogueBox.png and b/assets/images/dialogue/boxes/pixel/dialogueBox.png differ diff --git a/assets/images/dialogue/characters/boyfriend.png b/assets/images/dialogue/characters/boyfriend.png index 341ba166..6ccc2829 100644 Binary files a/assets/images/dialogue/characters/boyfriend.png and b/assets/images/dialogue/characters/boyfriend.png differ diff --git a/assets/images/dialogue/characters/dad.png b/assets/images/dialogue/characters/dad.png index de81cf3c..be0e5b77 100644 Binary files a/assets/images/dialogue/characters/dad.png and b/assets/images/dialogue/characters/dad.png differ diff --git a/assets/images/dialogue/characters/niko.png b/assets/images/dialogue/characters/niko.png index 81acec6e..b7fde571 100644 Binary files a/assets/images/dialogue/characters/niko.png and b/assets/images/dialogue/characters/niko.png differ diff --git a/assets/images/editors/autosave-delete.png b/assets/images/editors/autosave-delete.png index 18ab7de0..3e4b8ab2 100644 Binary files a/assets/images/editors/autosave-delete.png and b/assets/images/editors/autosave-delete.png differ diff --git a/assets/images/editors/autosave-icons.png b/assets/images/editors/autosave-icons.png index 26f99bc2..6b5136f4 100644 Binary files a/assets/images/editors/autosave-icons.png and b/assets/images/editors/autosave-icons.png differ diff --git a/assets/images/editors/bgs/charter.png b/assets/images/editors/bgs/charter.png index e1a6f93a..03ea9580 100644 Binary files a/assets/images/editors/bgs/charter.png and b/assets/images/editors/bgs/charter.png differ diff --git a/assets/images/editors/character/WOAH.png b/assets/images/editors/character/WOAH.png index 8821bc1d..8d4b3b56 100644 Binary files a/assets/images/editors/character/WOAH.png and b/assets/images/editors/character/WOAH.png differ diff --git a/assets/images/editors/character/anim-icons.png b/assets/images/editors/character/anim-icons.png new file mode 100644 index 00000000..765e3725 Binary files /dev/null and b/assets/images/editors/character/anim-icons.png differ diff --git a/assets/images/editors/character/edit-button.png b/assets/images/editors/character/edit-button.png index 4667961d..8b439a63 100644 Binary files a/assets/images/editors/character/edit-button.png and b/assets/images/editors/character/edit-button.png differ diff --git a/assets/images/editors/character/ghost-button.png b/assets/images/editors/character/ghost-button.png deleted file mode 100644 index fa8e032b..00000000 Binary files a/assets/images/editors/character/ghost-button.png and /dev/null differ diff --git a/assets/images/editors/character/ghost.png b/assets/images/editors/character/ghost.png new file mode 100644 index 00000000..4906cd32 Binary files /dev/null and b/assets/images/editors/character/ghost.png differ diff --git a/assets/images/editors/charter/add-button.png b/assets/images/editors/charter/add-button.png index 906d8ca3..defdb6ef 100644 Binary files a/assets/images/editors/charter/add-button.png and b/assets/images/editors/charter/add-button.png differ diff --git a/assets/images/editors/charter/event-icons/Add Camera Zoom.png b/assets/images/editors/charter/event-icons/Add Camera Zoom.png index 48ce845c..044b1196 100644 Binary files a/assets/images/editors/charter/event-icons/Add Camera Zoom.png and b/assets/images/editors/charter/event-icons/Add Camera Zoom.png differ diff --git a/assets/images/editors/charter/event-icons/Alt Animation Toggle.png b/assets/images/editors/charter/event-icons/Alt Animation Toggle.png index e982158a..a2046320 100644 Binary files a/assets/images/editors/charter/event-icons/Alt Animation Toggle.png and b/assets/images/editors/charter/event-icons/Alt Animation Toggle.png differ diff --git a/assets/images/editors/charter/event-icons/BPM Change Start.png b/assets/images/editors/charter/event-icons/BPM Change Start.png new file mode 100644 index 00000000..832a0235 Binary files /dev/null and b/assets/images/editors/charter/event-icons/BPM Change Start.png differ diff --git a/assets/images/editors/charter/event-icons/BPM Change.png b/assets/images/editors/charter/event-icons/BPM Change.png index b95e0d36..4eefebc3 100644 Binary files a/assets/images/editors/charter/event-icons/BPM Change.png and b/assets/images/editors/charter/event-icons/BPM Change.png differ diff --git a/assets/images/editors/charter/event-icons/Camera Flash.png b/assets/images/editors/charter/event-icons/Camera Flash.png index 22903bea..3abfd1cc 100644 Binary files a/assets/images/editors/charter/event-icons/Camera Flash.png and b/assets/images/editors/charter/event-icons/Camera Flash.png differ diff --git a/assets/images/editors/charter/event-icons/Camera Modulo Change.png b/assets/images/editors/charter/event-icons/Camera Modulo Change.png index 5eb5f064..27353aeb 100644 Binary files a/assets/images/editors/charter/event-icons/Camera Modulo Change.png and b/assets/images/editors/charter/event-icons/Camera Modulo Change.png differ diff --git a/assets/images/editors/charter/event-icons/Camera Movement.png b/assets/images/editors/charter/event-icons/Camera Movement.png index 3126c502..74ba908a 100644 Binary files a/assets/images/editors/charter/event-icons/Camera Movement.png and b/assets/images/editors/charter/event-icons/Camera Movement.png differ diff --git a/assets/images/editors/charter/event-icons/HScript Call.png b/assets/images/editors/charter/event-icons/HScript Call.png index 5aa4095a..f63e4a2b 100644 Binary files a/assets/images/editors/charter/event-icons/HScript Call.png and b/assets/images/editors/charter/event-icons/HScript Call.png differ diff --git a/assets/images/editors/charter/event-icons/Play Animation.png b/assets/images/editors/charter/event-icons/Play Animation.png index 1092f05f..54da0eb4 100644 Binary files a/assets/images/editors/charter/event-icons/Play Animation.png and b/assets/images/editors/charter/event-icons/Play Animation.png differ diff --git a/assets/images/editors/charter/event-icons/Scroll Speed Change.png b/assets/images/editors/charter/event-icons/Scroll Speed Change.png index 4b06071c..97b6fc95 100644 Binary files a/assets/images/editors/charter/event-icons/Scroll Speed Change.png and b/assets/images/editors/charter/event-icons/Scroll Speed Change.png differ diff --git a/assets/images/editors/charter/event-icons/Time Signature Change.png b/assets/images/editors/charter/event-icons/Time Signature Change.png new file mode 100644 index 00000000..96714600 Binary files /dev/null and b/assets/images/editors/charter/event-icons/Time Signature Change.png differ diff --git a/assets/images/editors/charter/event-icons/Unknown.png b/assets/images/editors/charter/event-icons/Unknown.png index 26ac16c6..5fbbdbb8 100644 Binary files a/assets/images/editors/charter/event-icons/Unknown.png and b/assets/images/editors/charter/event-icons/Unknown.png differ diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.png b/assets/images/editors/charter/event-icons/components/arrow-down.png new file mode 100644 index 00000000..f44a8711 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/arrow-down.png differ diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.xml b/assets/images/editors/charter/event-icons/components/arrow-down.xml new file mode 100644 index 00000000..1e58cc38 --- /dev/null +++ b/assets/images/editors/charter/event-icons/components/arrow-down.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextureAtlas imagePath="arrow-down.png"> + <SubTexture name="tail" x="0" y="0" width="8" height="3"/> + <SubTexture name="segment" x="0" y="3" width="8" height="1"/> + <SubTexture name="arrow" x="0" y="6" width="8" height="6"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/images/editors/charter/event-icons/components/arrow-right.png b/assets/images/editors/charter/event-icons/components/arrow-right.png new file mode 100644 index 00000000..a5da3a70 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/arrow-right.png differ diff --git a/assets/images/editors/charter/event-icons/components/cross.png b/assets/images/editors/charter/event-icons/components/cross.png new file mode 100644 index 00000000..a3dee356 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/cross.png differ diff --git a/assets/images/editors/charter/event-icons/components/end-plus.png b/assets/images/editors/charter/event-icons/components/end-plus.png new file mode 100644 index 00000000..7629e754 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/end-plus.png differ diff --git a/assets/images/editors/charter/event-icons/components/eventNums.png b/assets/images/editors/charter/event-icons/components/eventNums.png new file mode 100644 index 00000000..8ae1513c Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/eventNums.png differ diff --git a/assets/images/editors/charter/event-icons/components/flash.png b/assets/images/editors/charter/event-icons/components/flash.png new file mode 100644 index 00000000..2ef5f949 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/flash.png differ diff --git a/assets/images/editors/charter/event-icons/components/note.png b/assets/images/editors/charter/event-icons/components/note.png new file mode 100644 index 00000000..4b08e4f5 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/note.png differ diff --git a/assets/images/editors/charter/event-icons/components/plus.png b/assets/images/editors/charter/event-icons/components/plus.png new file mode 100644 index 00000000..939d4c46 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/plus.png differ diff --git a/assets/images/editors/charter/event-icons/components/start-plus.png b/assets/images/editors/charter/event-icons/components/start-plus.png new file mode 100644 index 00000000..84b42f13 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/start-plus.png differ diff --git a/assets/images/editors/charter/event-icons/components/warning.png b/assets/images/editors/charter/event-icons/components/warning.png new file mode 100644 index 00000000..03a423db Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/warning.png differ diff --git a/assets/images/editors/charter/event-spr-add.png b/assets/images/editors/charter/event-spr-add.png index 21cc06d9..5898ea3b 100644 Binary files a/assets/images/editors/charter/event-spr-add.png and b/assets/images/editors/charter/event-spr-add.png differ diff --git a/assets/images/editors/charter/event-spr.png b/assets/images/editors/charter/event-spr.png index 45149581..1d006cff 100644 Binary files a/assets/images/editors/charter/event-spr.png and b/assets/images/editors/charter/event-spr.png differ diff --git a/assets/images/editors/charter/event.png b/assets/images/editors/charter/event.png index 8fe12787..30860be9 100644 Binary files a/assets/images/editors/charter/event.png and b/assets/images/editors/charter/event.png differ diff --git a/assets/images/editors/charter/events-grid.png b/assets/images/editors/charter/events-grid.png index 3a4f90e6..a49b3d72 100644 Binary files a/assets/images/editors/charter/events-grid.png and b/assets/images/editors/charter/events-grid.png differ diff --git a/assets/images/editors/charter/lock-strumline.png b/assets/images/editors/charter/lock-strumline.png index 637e5f64..3175325c 100644 Binary files a/assets/images/editors/charter/lock-strumline.png and b/assets/images/editors/charter/lock-strumline.png differ diff --git a/assets/images/editors/charter/strumline-drag.png b/assets/images/editors/charter/strumline-drag.png index e886483f..e9f8b907 100644 Binary files a/assets/images/editors/charter/strumline-drag.png and b/assets/images/editors/charter/strumline-drag.png differ diff --git a/assets/images/editors/charter/strumline-info-bg.png b/assets/images/editors/charter/strumline-info-bg.png index 74d7d906..01bf0072 100644 Binary files a/assets/images/editors/charter/strumline-info-bg.png and b/assets/images/editors/charter/strumline-info-bg.png differ diff --git a/assets/images/editors/delete-button.png b/assets/images/editors/delete-button.png index 4a80427c..386c6662 100644 Binary files a/assets/images/editors/delete-button.png and b/assets/images/editors/delete-button.png differ diff --git a/assets/images/editors/deleter circle.png b/assets/images/editors/deleter circle.png index c4120b13..db63b885 100644 Binary files a/assets/images/editors/deleter circle.png and b/assets/images/editors/deleter circle.png differ diff --git a/assets/images/editors/deleter.png b/assets/images/editors/deleter.png index c3c39bcc..a872a17a 100644 Binary files a/assets/images/editors/deleter.png and b/assets/images/editors/deleter.png differ diff --git a/assets/images/editors/new.png b/assets/images/editors/new.png index 461fd2ea..5a062188 100644 Binary files a/assets/images/editors/new.png and b/assets/images/editors/new.png differ diff --git a/assets/images/editors/stage/edit-button.png b/assets/images/editors/stage/edit-button.png new file mode 100644 index 00000000..47991177 Binary files /dev/null and b/assets/images/editors/stage/edit-button.png differ diff --git a/assets/images/editors/stage/selectionDot.png b/assets/images/editors/stage/selectionDot.png new file mode 100644 index 00000000..d5aa449e Binary files /dev/null and b/assets/images/editors/stage/selectionDot.png differ diff --git a/assets/images/editors/stage/tarr.png b/assets/images/editors/stage/tarr.png new file mode 100644 index 00000000..d989b82b Binary files /dev/null and b/assets/images/editors/stage/tarr.png differ diff --git a/assets/images/editors/stage/trash-icon.png b/assets/images/editors/stage/trash-icon.png new file mode 100644 index 00000000..37041078 Binary files /dev/null and b/assets/images/editors/stage/trash-icon.png differ diff --git a/assets/images/editors/stage/visible-icon.png b/assets/images/editors/stage/visible-icon.png new file mode 100644 index 00000000..2e603333 Binary files /dev/null and b/assets/images/editors/stage/visible-icon.png differ diff --git a/assets/images/editors/ui/audio-buttons.png b/assets/images/editors/ui/audio-buttons.png index 6bd347a8..7719e8eb 100644 Binary files a/assets/images/editors/ui/audio-buttons.png and b/assets/images/editors/ui/audio-buttons.png differ diff --git a/assets/images/editors/ui/audio-icon.png b/assets/images/editors/ui/audio-icon.png index 97eb7763..575450ba 100644 Binary files a/assets/images/editors/ui/audio-icon.png and b/assets/images/editors/ui/audio-icon.png differ diff --git a/assets/images/editors/ui/audio-time-empty.png b/assets/images/editors/ui/audio-time-empty.png index b08c0154..77f8fd21 100644 Binary files a/assets/images/editors/ui/audio-time-empty.png and b/assets/images/editors/ui/audio-time-empty.png differ diff --git a/assets/images/editors/ui/audio-time-full.png b/assets/images/editors/ui/audio-time-full.png index 85c4e77c..bd5ecd44 100644 Binary files a/assets/images/editors/ui/audio-time-full.png and b/assets/images/editors/ui/audio-time-full.png differ diff --git a/assets/images/editors/ui/audio-volume-empty.png b/assets/images/editors/ui/audio-volume-empty.png index 290f8099..c876657b 100644 Binary files a/assets/images/editors/ui/audio-volume-empty.png and b/assets/images/editors/ui/audio-volume-empty.png differ diff --git a/assets/images/editors/ui/audio-volume-full.png b/assets/images/editors/ui/audio-volume-full.png index eebab268..0304b3e3 100644 Binary files a/assets/images/editors/ui/audio-volume-full.png and b/assets/images/editors/ui/audio-volume-full.png differ diff --git a/assets/images/editors/ui/button small.png b/assets/images/editors/ui/button small.png new file mode 100644 index 00000000..9b0a33e4 Binary files /dev/null and b/assets/images/editors/ui/button small.png differ diff --git a/assets/images/editors/ui/button small.xml b/assets/images/editors/ui/button small.xml new file mode 100644 index 00000000..5f1cae61 --- /dev/null +++ b/assets/images/editors/ui/button small.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextureAtlas imagePath="button.png"> + <!-- Created without animate lol i wrote this--> + <SubTexture name="normal-topleft" x="0" y="0" width="2" height="2"/> + <SubTexture name="normal-top" x="2" y="0" width="6" height="2"/> + <SubTexture name="normal-topright" x="8" y="0" width="2" height="2"/> + <SubTexture name="normal-middleleft" x="0" y="2" width="2" height="7.5"/> + <SubTexture name="normal-middle" x="2" y="2" width="6" height="7.5"/> + <SubTexture name="normal-middleright" x="8" y="2" width="2" height="7.5"/> + <SubTexture name="normal-bottomleft" x="0" y="9.5" width="2" height="2"/> + <SubTexture name="normal-bottom" x="2" y="9.5" width="6" height="2"/> + <SubTexture name="normal-bottomright" x="8" y="9.5" width="2" height="2"/> + + <SubTexture name="hover-topleft" x="10" y="0" width="2" height="2"/> + <SubTexture name="hover-top" x="12" y="0" width="6" height="2"/> + <SubTexture name="hover-topright" x="18" y="0" width="2" height="2"/> + <SubTexture name="hover-middleleft" x="10" y="2" width="2" height="7.5"/> + <SubTexture name="hover-middle" x="12" y="2" width="6" height="7.5"/> + <SubTexture name="hover-middleright" x="18" y="2" width="2" height="7.5"/> + <SubTexture name="hover-bottomleft" x="10" y="9.5" width="2" height="2"/> + <SubTexture name="hover-bottom" x="12" y="9.5" width="6" height="2"/> + <SubTexture name="hover-bottomright" x="18" y="9.5" width="2" height="2"/> + + <SubTexture name="pressed-topleft" x="20" y="0" width="2" height="2"/> + <SubTexture name="pressed-top" x="22" y="0" width="6" height="2"/> + <SubTexture name="pressed-topright" x="28" y="0" width="2" height="2"/> + <SubTexture name="pressed-middleleft" x="20" y="2" width="2" height="7.5"/> + <SubTexture name="pressed-middle" x="22" y="2" width="6" height="7.5"/> + <SubTexture name="pressed-middleright" x="28" y="2" width="2" height="7.5"/> + <SubTexture name="pressed-bottomleft" x="20" y="9.5" width="2" height="2"/> + <SubTexture name="pressed-bottom" x="22" y="9.5" width="6" height="2"/> + <SubTexture name="pressed-bottomright" x="28" y="9.5" width="2" height="2"/> +</TextureAtlas> diff --git a/assets/images/editors/ui/button.png b/assets/images/editors/ui/button.png index dc270bee..3107e42c 100644 Binary files a/assets/images/editors/ui/button.png and b/assets/images/editors/ui/button.png differ diff --git a/assets/images/editors/ui/checkbox-small.png b/assets/images/editors/ui/checkbox-small.png new file mode 100644 index 00000000..bd6472a2 Binary files /dev/null and b/assets/images/editors/ui/checkbox-small.png differ diff --git a/assets/images/editors/ui/checkbox.png b/assets/images/editors/ui/checkbox.png index 95ef7ccb..e4bdd9bb 100644 Binary files a/assets/images/editors/ui/checkbox.png and b/assets/images/editors/ui/checkbox.png differ diff --git a/assets/images/editors/ui/context-bg-debug.png b/assets/images/editors/ui/context-bg-debug.png new file mode 100644 index 00000000..aa83123f Binary files /dev/null and b/assets/images/editors/ui/context-bg-debug.png differ diff --git a/assets/images/editors/ui/context-bg-debug.xml b/assets/images/editors/ui/context-bg-debug.xml new file mode 100644 index 00000000..6eb3c21a --- /dev/null +++ b/assets/images/editors/ui/context-bg-debug.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextureAtlas imagePath="context-bg.png"> + <!-- Created without animate lol i wrote this--> + <SubTexture name="topleft" x="0" y="0" width="10" height="10"/> + <SubTexture name="top" x="10" y="0" width="44" height="10"/> + <SubTexture name="topright" x="54" y="0" width="10" height="10"/> + + <SubTexture name="middleleft" x="0" y="10" width="10" height="44"/> + <SubTexture name="middle" x="10" y="10" width="44" height="44"/> + <SubTexture name="middleright" x="54" y="10" width="10" height="44"/> + + <SubTexture name="bottomleft" x="0" y="54" width="10" height="10"/> + <SubTexture name="bottom" x="10" y="54" width="44" height="10"/> + <SubTexture name="bottomright" x="54" y="54" width="10" height="10"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/images/editors/ui/context-bg.png b/assets/images/editors/ui/context-bg.png index 4e46b537..1e4c74b3 100644 Binary files a/assets/images/editors/ui/context-bg.png and b/assets/images/editors/ui/context-bg.png differ diff --git a/assets/images/editors/ui/context-icons.png b/assets/images/editors/ui/context-icons.png index 733a8523..b7411d02 100644 Binary files a/assets/images/editors/ui/context-icons.png and b/assets/images/editors/ui/context-icons.png differ diff --git a/assets/images/editors/ui/dropdown-button.png b/assets/images/editors/ui/dropdown-button.png new file mode 100644 index 00000000..13a96bad Binary files /dev/null and b/assets/images/editors/ui/dropdown-button.png differ diff --git a/assets/images/editors/ui/inputbox-small.png b/assets/images/editors/ui/inputbox-small.png new file mode 100644 index 00000000..30b76224 Binary files /dev/null and b/assets/images/editors/ui/inputbox-small.png differ diff --git a/assets/images/editors/ui/inputbox-small.xml b/assets/images/editors/ui/inputbox-small.xml new file mode 100644 index 00000000..de0e833b --- /dev/null +++ b/assets/images/editors/ui/inputbox-small.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextureAtlas imagePath="button.png"> + <!-- Created without animate lol i wrote this--> + + <!-- Thanks yosh, and i wrote this script incase anyone ever needs to resize these xmls --> + <!-- Here you guys go :D https://try.haxe.org/#7C373001 -lunar --> + + <SubTexture name="normal-topleft" x="0" y="0" width="4" height="4"/> + <SubTexture name="normal-top" x="4" y="0" width="8" height="4"/> + <SubTexture name="normal-topright" x="12" y="0" width="4" height="4"/> + <SubTexture name="normal-middleleft" x="0" y="4" width="4" height="8"/> + <SubTexture name="normal-middle" x="4" y="4" width="8" height="8"/> + <SubTexture name="normal-middleright" x="12" y="4" width="4" height="8"/> + <SubTexture name="normal-bottomleft" x="0" y="12" width="4" height="4"/> + <SubTexture name="normal-bottom" x="4" y="12" width="8" height="4"/> + <SubTexture name="normal-bottomright" x="12" y="12" width="4" height="4"/> + + <SubTexture name="hover-topleft" x="16" y="0" width="4" height="4"/> + <SubTexture name="hover-top" x="20" y="0" width="8" height="4"/> + <SubTexture name="hover-topright" x="28" y="0" width="4" height="4"/> + <SubTexture name="hover-middleleft" x="16" y="4" width="4" height="8"/> + <SubTexture name="hover-middle" x="20" y="4" width="8" height="8"/> + <SubTexture name="hover-middleright" x="28" y="4" width="4" height="8"/> + <SubTexture name="hover-bottomleft" x="16" y="12" width="4" height="4"/> + <SubTexture name="hover-bottom" x="20" y="12" width="8" height="4"/> + <SubTexture name="hover-bottomright" x="28" y="12" width="4" height="4"/> + + <SubTexture name="pressed-topleft" x="32" y="0" width="4" height="4"/> + <SubTexture name="pressed-top" x="36" y="0" width="8" height="4"/> + <SubTexture name="pressed-topright" x="44" y="0" width="4" height="4"/> + <SubTexture name="pressed-middleleft" x="32" y="4" width="4" height="8"/> + <SubTexture name="pressed-middle" x="36" y="4" width="8" height="8"/> + <SubTexture name="pressed-middleright" x="44" y="4" width="4" height="8"/> + <SubTexture name="pressed-bottomleft" x="32" y="12" width="4" height="4"/> + <SubTexture name="pressed-bottom" x="36" y="12" width="8" height="4"/> + <SubTexture name="pressed-bottomright" x="44" y="12" width="4" height="4"/> +</TextureAtlas> diff --git a/assets/images/editors/ui/inputbox.png b/assets/images/editors/ui/inputbox.png index f3b1df34..6c38457b 100644 Binary files a/assets/images/editors/ui/inputbox.png and b/assets/images/editors/ui/inputbox.png differ diff --git a/assets/images/editors/ui/menu-item.png b/assets/images/editors/ui/menu-item.png index 5babc03a..4a95616f 100644 Binary files a/assets/images/editors/ui/menu-item.png and b/assets/images/editors/ui/menu-item.png differ diff --git a/assets/images/editors/ui/normal-popup.png b/assets/images/editors/ui/normal-popup.png index 1227df6d..a4a0746b 100644 Binary files a/assets/images/editors/ui/normal-popup.png and b/assets/images/editors/ui/normal-popup.png differ diff --git a/assets/images/editors/ui/picker.png b/assets/images/editors/ui/picker.png new file mode 100644 index 00000000..12fb58a1 Binary files /dev/null and b/assets/images/editors/ui/picker.png differ diff --git a/assets/images/editors/ui/radiobutton.png b/assets/images/editors/ui/radiobutton.png new file mode 100644 index 00000000..56c26c15 Binary files /dev/null and b/assets/images/editors/ui/radiobutton.png differ diff --git a/assets/images/editors/ui/scrollbar-bg.png b/assets/images/editors/ui/scrollbar-bg.png index 1f49a24a..55fded94 100644 Binary files a/assets/images/editors/ui/scrollbar-bg.png and b/assets/images/editors/ui/scrollbar-bg.png differ diff --git a/assets/images/editors/ui/scrollbar-icon.png b/assets/images/editors/ui/scrollbar-icon.png index f2a5c0ec..feb29469 100644 Binary files a/assets/images/editors/ui/scrollbar-icon.png and b/assets/images/editors/ui/scrollbar-icon.png differ diff --git a/assets/images/editors/ui/selection.png b/assets/images/editors/ui/selection.png index 456897fe..2d95a2d1 100644 Binary files a/assets/images/editors/ui/selection.png and b/assets/images/editors/ui/selection.png differ diff --git a/assets/images/editors/ui/slider.png b/assets/images/editors/ui/slider.png index 82b6c80b..13758fbe 100644 Binary files a/assets/images/editors/ui/slider.png and b/assets/images/editors/ui/slider.png differ diff --git a/assets/images/editors/ui/textbox.png b/assets/images/editors/ui/textbox.png index a66637a4..2ada0766 100644 Binary files a/assets/images/editors/ui/textbox.png and b/assets/images/editors/ui/textbox.png differ diff --git a/assets/images/editors/ui/topmenu.png b/assets/images/editors/ui/topmenu.png index 4f010db4..3824c37f 100644 Binary files a/assets/images/editors/ui/topmenu.png and b/assets/images/editors/ui/topmenu.png differ diff --git a/assets/images/editors/ui/upload-button.png b/assets/images/editors/ui/upload-button.png index bfdba151..f3700640 100644 Binary files a/assets/images/editors/ui/upload-button.png and b/assets/images/editors/ui/upload-button.png differ diff --git a/assets/images/editors/warnings/error.png b/assets/images/editors/warnings/error.png index 07282f8b..aef47776 100644 Binary files a/assets/images/editors/warnings/error.png and b/assets/images/editors/warnings/error.png differ diff --git a/assets/images/editors/warnings/warning.png b/assets/images/editors/warnings/warning.png index 1d501046..44e8b560 100644 Binary files a/assets/images/editors/warnings/warning.png and b/assets/images/editors/warnings/warning.png differ diff --git a/assets/images/game/cutscenes/tank/guns-tankman/spritemap1.png b/assets/images/game/cutscenes/tank/guns-tankman/spritemap1.png index 337491b6..ccfe25ee 100644 Binary files a/assets/images/game/cutscenes/tank/guns-tankman/spritemap1.png and b/assets/images/game/cutscenes/tank/guns-tankman/spritemap1.png differ diff --git a/assets/images/game/cutscenes/tank/stress-tankman/spritemap1.png b/assets/images/game/cutscenes/tank/stress-tankman/spritemap1.png index 7ed88503..3c19a130 100644 Binary files a/assets/images/game/cutscenes/tank/stress-tankman/spritemap1.png and b/assets/images/game/cutscenes/tank/stress-tankman/spritemap1.png differ diff --git a/assets/images/game/cutscenes/tank/ugh-tankman/spritemap1.png b/assets/images/game/cutscenes/tank/ugh-tankman/spritemap1.png index 957cae53..787b49aa 100644 Binary files a/assets/images/game/cutscenes/tank/ugh-tankman/spritemap1.png and b/assets/images/game/cutscenes/tank/ugh-tankman/spritemap1.png differ diff --git a/assets/images/game/cutscenes/weeb/senpaiCrazy.png b/assets/images/game/cutscenes/weeb/senpaiCrazy.png index 1091f92a..752aa3d1 100644 Binary files a/assets/images/game/cutscenes/weeb/senpaiCrazy.png and b/assets/images/game/cutscenes/weeb/senpaiCrazy.png differ diff --git a/assets/images/game/cutscenes/weeb/spiritFaceForward.png b/assets/images/game/cutscenes/weeb/spiritFaceForward.png index 93c7ed04..9bf698ed 100644 Binary files a/assets/images/game/cutscenes/weeb/spiritFaceForward.png and b/assets/images/game/cutscenes/weeb/spiritFaceForward.png differ diff --git a/assets/images/game/go.png b/assets/images/game/go.png index 78cc7f68..d82c6b6d 100644 Binary files a/assets/images/game/go.png and b/assets/images/game/go.png differ diff --git a/assets/images/game/healthBar.png b/assets/images/game/healthBar.png index 8bfec941..08babb89 100644 Binary files a/assets/images/game/healthBar.png and b/assets/images/game/healthBar.png differ diff --git a/assets/images/game/notes/default.png b/assets/images/game/notes/default.png index 6cd38f99..3201fe30 100644 Binary files a/assets/images/game/notes/default.png and b/assets/images/game/notes/default.png differ diff --git a/assets/images/game/ready.png b/assets/images/game/ready.png index 1ee103df..d6e8d062 100644 Binary files a/assets/images/game/ready.png and b/assets/images/game/ready.png differ diff --git a/assets/images/game/restart.png b/assets/images/game/restart.png index 82c210d0..6ae807a3 100644 Binary files a/assets/images/game/restart.png and b/assets/images/game/restart.png differ diff --git a/assets/images/game/score/bad.png b/assets/images/game/score/bad.png index 7b7c7398..47d81323 100644 Binary files a/assets/images/game/score/bad.png and b/assets/images/game/score/bad.png differ diff --git a/assets/images/game/score/combo.png b/assets/images/game/score/combo.png index ffc94f44..450b7a11 100644 Binary files a/assets/images/game/score/combo.png and b/assets/images/game/score/combo.png differ diff --git a/assets/images/game/score/good.png b/assets/images/game/score/good.png index d6c8f386..0a9f4551 100644 Binary files a/assets/images/game/score/good.png and b/assets/images/game/score/good.png differ diff --git a/assets/images/game/score/num0.png b/assets/images/game/score/num0.png index 83958371..b3513a75 100644 Binary files a/assets/images/game/score/num0.png and b/assets/images/game/score/num0.png differ diff --git a/assets/images/game/score/num1.png b/assets/images/game/score/num1.png index 52452ae1..3255408c 100644 Binary files a/assets/images/game/score/num1.png and b/assets/images/game/score/num1.png differ diff --git a/assets/images/game/score/num2.png b/assets/images/game/score/num2.png index 2db35d5b..11df1c9a 100644 Binary files a/assets/images/game/score/num2.png and b/assets/images/game/score/num2.png differ diff --git a/assets/images/game/score/num3.png b/assets/images/game/score/num3.png index a6b32303..c2842efa 100644 Binary files a/assets/images/game/score/num3.png and b/assets/images/game/score/num3.png differ diff --git a/assets/images/game/score/num4.png b/assets/images/game/score/num4.png index 24f99bda..72234aef 100644 Binary files a/assets/images/game/score/num4.png and b/assets/images/game/score/num4.png differ diff --git a/assets/images/game/score/num5.png b/assets/images/game/score/num5.png index 542423eb..1b16e1a8 100644 Binary files a/assets/images/game/score/num5.png and b/assets/images/game/score/num5.png differ diff --git a/assets/images/game/score/num6.png b/assets/images/game/score/num6.png index c6044194..498ce3b0 100644 Binary files a/assets/images/game/score/num6.png and b/assets/images/game/score/num6.png differ diff --git a/assets/images/game/score/num7.png b/assets/images/game/score/num7.png index 8b2d5498..4a2ce04a 100644 Binary files a/assets/images/game/score/num7.png and b/assets/images/game/score/num7.png differ diff --git a/assets/images/game/score/num8.png b/assets/images/game/score/num8.png index db61c3e9..c7a337ea 100644 Binary files a/assets/images/game/score/num8.png and b/assets/images/game/score/num8.png differ diff --git a/assets/images/game/score/num9.png b/assets/images/game/score/num9.png index 607f9ea7..e3ae145c 100644 Binary files a/assets/images/game/score/num9.png and b/assets/images/game/score/num9.png differ diff --git a/assets/images/game/score/shit.png b/assets/images/game/score/shit.png index 346141ad..4b2450b8 100644 Binary files a/assets/images/game/score/shit.png and b/assets/images/game/score/shit.png differ diff --git a/assets/images/game/score/sick.png b/assets/images/game/score/sick.png index 5cbba1e2..2a3aacf4 100644 Binary files a/assets/images/game/score/sick.png and b/assets/images/game/score/sick.png differ diff --git a/assets/images/game/set.png b/assets/images/game/set.png index a0450751..09eabfa0 100644 Binary files a/assets/images/game/set.png and b/assets/images/game/set.png differ diff --git a/assets/images/game/splashes/default.png b/assets/images/game/splashes/default.png index e93fdcb7..3cd0fc4a 100644 Binary files a/assets/images/game/splashes/default.png and b/assets/images/game/splashes/default.png differ diff --git a/assets/images/game/splashes/secret.png b/assets/images/game/splashes/secret.png index f0eccb47..5e9cdf2d 100644 Binary files a/assets/images/game/splashes/secret.png and b/assets/images/game/splashes/secret.png differ diff --git a/assets/images/icons/bf-old.png b/assets/images/icons/bf-old.png deleted file mode 100644 index b8369aca..00000000 Binary files a/assets/images/icons/bf-old.png and /dev/null differ diff --git a/assets/images/icons/bf-old/icon.png b/assets/images/icons/bf-old/icon.png new file mode 100644 index 00000000..df350d4a Binary files /dev/null and b/assets/images/icons/bf-old/icon.png differ diff --git a/assets/images/icons/bf-pixel.png b/assets/images/icons/bf-pixel.png deleted file mode 100644 index 1351f810..00000000 Binary files a/assets/images/icons/bf-pixel.png and /dev/null differ diff --git a/assets/images/icons/bf-pixel/data.xml b/assets/images/icons/bf-pixel/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/bf-pixel/data.xml @@ -0,0 +1,2 @@ +<!DOCTYPE codename-engine-icon> +<icon antialiasing="false" /> \ No newline at end of file diff --git a/assets/images/icons/bf-pixel/icon.png b/assets/images/icons/bf-pixel/icon.png new file mode 100644 index 00000000..d1ae0f87 Binary files /dev/null and b/assets/images/icons/bf-pixel/icon.png differ diff --git a/assets/images/icons/bf.png b/assets/images/icons/bf.png deleted file mode 100644 index ab0903a7..00000000 Binary files a/assets/images/icons/bf.png and /dev/null differ diff --git a/assets/images/icons/bf/icon.png b/assets/images/icons/bf/icon.png new file mode 100644 index 00000000..53f9ccaa Binary files /dev/null and b/assets/images/icons/bf/icon.png differ diff --git a/assets/images/icons/dad.png b/assets/images/icons/dad.png deleted file mode 100644 index ffdf4ac1..00000000 Binary files a/assets/images/icons/dad.png and /dev/null differ diff --git a/assets/images/icons/dad/icon.png b/assets/images/icons/dad/icon.png new file mode 100644 index 00000000..cffed6f4 Binary files /dev/null and b/assets/images/icons/dad/icon.png differ diff --git a/assets/images/icons/face.png b/assets/images/icons/face.png deleted file mode 100644 index 622eb6dc..00000000 Binary files a/assets/images/icons/face.png and /dev/null differ diff --git a/assets/images/icons/face/icon.png b/assets/images/icons/face/icon.png new file mode 100644 index 00000000..bc24a5f8 Binary files /dev/null and b/assets/images/icons/face/icon.png differ diff --git a/assets/images/icons/gf-pixel.png b/assets/images/icons/gf-pixel.png deleted file mode 100644 index 5122ddc0..00000000 Binary files a/assets/images/icons/gf-pixel.png and /dev/null differ diff --git a/assets/images/icons/gf-pixel/data.xml b/assets/images/icons/gf-pixel/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/gf-pixel/data.xml @@ -0,0 +1,2 @@ +<!DOCTYPE codename-engine-icon> +<icon antialiasing="false" /> \ No newline at end of file diff --git a/assets/images/icons/gf-pixel/icon.png b/assets/images/icons/gf-pixel/icon.png new file mode 100644 index 00000000..c2a6bdf6 Binary files /dev/null and b/assets/images/icons/gf-pixel/icon.png differ diff --git a/assets/images/icons/gf.png b/assets/images/icons/gf.png deleted file mode 100644 index ee837c1d..00000000 Binary files a/assets/images/icons/gf.png and /dev/null differ diff --git a/assets/images/icons/gf/icon.png b/assets/images/icons/gf/icon.png new file mode 100644 index 00000000..1493d177 Binary files /dev/null and b/assets/images/icons/gf/icon.png differ diff --git a/assets/images/icons/mom.png b/assets/images/icons/mom.png deleted file mode 100644 index 1179e39b..00000000 Binary files a/assets/images/icons/mom.png and /dev/null differ diff --git a/assets/images/icons/mom/icon.png b/assets/images/icons/mom/icon.png new file mode 100644 index 00000000..793d846c Binary files /dev/null and b/assets/images/icons/mom/icon.png differ diff --git a/assets/images/icons/monster.png b/assets/images/icons/monster.png deleted file mode 100644 index a7195ca3..00000000 Binary files a/assets/images/icons/monster.png and /dev/null differ diff --git a/assets/images/icons/monster/icon.png b/assets/images/icons/monster/icon.png new file mode 100644 index 00000000..525effd8 Binary files /dev/null and b/assets/images/icons/monster/icon.png differ diff --git a/assets/images/icons/parents-christmas.png b/assets/images/icons/parents-christmas.png deleted file mode 100644 index 64efb7fd..00000000 Binary files a/assets/images/icons/parents-christmas.png and /dev/null differ diff --git a/assets/images/icons/parents-christmas/icon.png b/assets/images/icons/parents-christmas/icon.png new file mode 100644 index 00000000..74e06a3d Binary files /dev/null and b/assets/images/icons/parents-christmas/icon.png differ diff --git a/assets/images/icons/pico.png b/assets/images/icons/pico.png deleted file mode 100644 index c159e236..00000000 Binary files a/assets/images/icons/pico.png and /dev/null differ diff --git a/assets/images/icons/pico/icon.png b/assets/images/icons/pico/icon.png new file mode 100644 index 00000000..3a6322e7 Binary files /dev/null and b/assets/images/icons/pico/icon.png differ diff --git a/assets/images/icons/senpai.png b/assets/images/icons/senpai.png deleted file mode 100644 index 22d0881b..00000000 Binary files a/assets/images/icons/senpai.png and /dev/null differ diff --git a/assets/images/icons/senpai/data.xml b/assets/images/icons/senpai/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/senpai/data.xml @@ -0,0 +1,2 @@ +<!DOCTYPE codename-engine-icon> +<icon antialiasing="false" /> \ No newline at end of file diff --git a/assets/images/icons/senpai/icon.png b/assets/images/icons/senpai/icon.png new file mode 100644 index 00000000..0064cf42 Binary files /dev/null and b/assets/images/icons/senpai/icon.png differ diff --git a/assets/images/icons/spirit.png b/assets/images/icons/spirit.png deleted file mode 100644 index c4ccb2a9..00000000 Binary files a/assets/images/icons/spirit.png and /dev/null differ diff --git a/assets/images/icons/spirit/data.xml b/assets/images/icons/spirit/data.xml new file mode 100644 index 00000000..57653cad --- /dev/null +++ b/assets/images/icons/spirit/data.xml @@ -0,0 +1,2 @@ +<!DOCTYPE codename-engine-icon> +<icon antialiasing="false" /> \ No newline at end of file diff --git a/assets/images/icons/spirit/icon.png b/assets/images/icons/spirit/icon.png new file mode 100644 index 00000000..e4cb0fd7 Binary files /dev/null and b/assets/images/icons/spirit/icon.png differ diff --git a/assets/images/icons/spooky.png b/assets/images/icons/spooky.png deleted file mode 100644 index 062eb196..00000000 Binary files a/assets/images/icons/spooky.png and /dev/null differ diff --git a/assets/images/icons/spooky/icon.png b/assets/images/icons/spooky/icon.png new file mode 100644 index 00000000..01534020 Binary files /dev/null and b/assets/images/icons/spooky/icon.png differ diff --git a/assets/images/icons/tankman.png b/assets/images/icons/tankman.png deleted file mode 100644 index d5d4ba46..00000000 Binary files a/assets/images/icons/tankman.png and /dev/null differ diff --git a/assets/images/icons/tankman/icon.png b/assets/images/icons/tankman/icon.png new file mode 100644 index 00000000..dafda943 Binary files /dev/null and b/assets/images/icons/tankman/icon.png differ diff --git a/assets/images/menus/mainmenu/freeplay.png b/assets/images/menus/mainmenu/freeplay.png index 361ddfa7..2e4bb436 100644 Binary files a/assets/images/menus/mainmenu/freeplay.png and b/assets/images/menus/mainmenu/freeplay.png differ diff --git a/assets/images/menus/mainmenu/options.png b/assets/images/menus/mainmenu/options.png index 20688ecc..abaa54ef 100644 Binary files a/assets/images/menus/mainmenu/options.png and b/assets/images/menus/mainmenu/options.png differ diff --git a/assets/images/menus/menuBG.png b/assets/images/menus/menuBG.png index 35458e80..896f0c11 100644 Binary files a/assets/images/menus/menuBG.png and b/assets/images/menus/menuBG.png differ diff --git a/assets/images/menus/menuBGBlue.png b/assets/images/menus/menuBGBlue.png index d3b42842..273fc2e3 100644 Binary files a/assets/images/menus/menuBGBlue.png and b/assets/images/menus/menuBGBlue.png differ diff --git a/assets/images/menus/menuBGMagenta.png b/assets/images/menus/menuBGMagenta.png index 96794ec8..2046067f 100644 Binary files a/assets/images/menus/menuBGMagenta.png and b/assets/images/menus/menuBGMagenta.png differ diff --git a/assets/images/menus/menuDesat.png b/assets/images/menus/menuDesat.png index 65759840..9f440340 100644 Binary files a/assets/images/menus/menuDesat.png and b/assets/images/menus/menuDesat.png differ diff --git a/assets/images/menus/menuEditors.png b/assets/images/menus/menuEditors.png index d6afefd1..84d40b18 100644 Binary files a/assets/images/menus/menuEditors.png and b/assets/images/menus/menuEditors.png differ diff --git a/assets/images/menus/menuTransparent.png b/assets/images/menus/menuTransparent.png index 8ea4883b..d6b275fb 100644 Binary files a/assets/images/menus/menuTransparent.png and b/assets/images/menus/menuTransparent.png differ diff --git a/assets/images/menus/pauseAlt/bfLol.png b/assets/images/menus/pauseAlt/bfLol.png index bfbdb86a..993618ba 100644 Binary files a/assets/images/menus/pauseAlt/bfLol.png and b/assets/images/menus/pauseAlt/bfLol.png differ diff --git a/assets/images/menus/pauseAlt/pauseBG.png b/assets/images/menus/pauseAlt/pauseBG.png index 5b5a77c6..32d0d92c 100644 Binary files a/assets/images/menus/pauseAlt/pauseBG.png and b/assets/images/menus/pauseAlt/pauseBG.png differ diff --git a/assets/images/menus/pauseAlt/pauseUI.png b/assets/images/menus/pauseAlt/pauseUI.png index b395863b..63b22e75 100644 Binary files a/assets/images/menus/pauseAlt/pauseUI.png and b/assets/images/menus/pauseAlt/pauseUI.png differ diff --git a/assets/images/menus/storymenu/assets.png b/assets/images/menus/storymenu/assets.png index 914229b6..097c8391 100644 Binary files a/assets/images/menus/storymenu/assets.png and b/assets/images/menus/storymenu/assets.png differ diff --git a/assets/images/menus/storymenu/characters/bf.png b/assets/images/menus/storymenu/characters/bf.png index 74713c3d..48da8dff 100644 Binary files a/assets/images/menus/storymenu/characters/bf.png and b/assets/images/menus/storymenu/characters/bf.png differ diff --git a/assets/images/menus/storymenu/characters/dad.png b/assets/images/menus/storymenu/characters/dad.png index d63e4537..d571774f 100644 Binary files a/assets/images/menus/storymenu/characters/dad.png and b/assets/images/menus/storymenu/characters/dad.png differ diff --git a/assets/images/menus/storymenu/characters/gf.png b/assets/images/menus/storymenu/characters/gf.png index 8ad08471..ae3093ef 100644 Binary files a/assets/images/menus/storymenu/characters/gf.png and b/assets/images/menus/storymenu/characters/gf.png differ diff --git a/assets/images/menus/storymenu/characters/mom.png b/assets/images/menus/storymenu/characters/mom.png index 5ff1badf..57c4ed03 100644 Binary files a/assets/images/menus/storymenu/characters/mom.png and b/assets/images/menus/storymenu/characters/mom.png differ diff --git a/assets/images/menus/storymenu/characters/parents-christmas.png b/assets/images/menus/storymenu/characters/parents-christmas.png index 7f081ede..a8b27578 100644 Binary files a/assets/images/menus/storymenu/characters/parents-christmas.png and b/assets/images/menus/storymenu/characters/parents-christmas.png differ diff --git a/assets/images/menus/storymenu/characters/senpai.png b/assets/images/menus/storymenu/characters/senpai.png index 8fe57d22..ab2366fa 100644 Binary files a/assets/images/menus/storymenu/characters/senpai.png and b/assets/images/menus/storymenu/characters/senpai.png differ diff --git a/assets/images/menus/storymenu/characters/spooky.png b/assets/images/menus/storymenu/characters/spooky.png index e9bc3a15..8b6fc801 100644 Binary files a/assets/images/menus/storymenu/characters/spooky.png and b/assets/images/menus/storymenu/characters/spooky.png differ diff --git a/assets/images/menus/storymenu/characters/tankman.png b/assets/images/menus/storymenu/characters/tankman.png index 26f26dca..ec9fc55a 100644 Binary files a/assets/images/menus/storymenu/characters/tankman.png and b/assets/images/menus/storymenu/characters/tankman.png differ diff --git a/assets/images/menus/storymenu/difficulties/easy.png b/assets/images/menus/storymenu/difficulties/easy.png index e73783ad..b0941dd8 100644 Binary files a/assets/images/menus/storymenu/difficulties/easy.png and b/assets/images/menus/storymenu/difficulties/easy.png differ diff --git a/assets/images/menus/storymenu/difficulties/hard.png b/assets/images/menus/storymenu/difficulties/hard.png index 8c90f062..4c167dbc 100644 Binary files a/assets/images/menus/storymenu/difficulties/hard.png and b/assets/images/menus/storymenu/difficulties/hard.png differ diff --git a/assets/images/menus/storymenu/difficulties/normal.png b/assets/images/menus/storymenu/difficulties/normal.png index 8acd14e9..81958d5d 100644 Binary files a/assets/images/menus/storymenu/difficulties/normal.png and b/assets/images/menus/storymenu/difficulties/normal.png differ diff --git a/assets/images/menus/storymenu/weeks/tutorial.png b/assets/images/menus/storymenu/weeks/tutorial.png index 7ef58a55..17c960f8 100644 Binary files a/assets/images/menus/storymenu/weeks/tutorial.png and b/assets/images/menus/storymenu/weeks/tutorial.png differ diff --git a/assets/images/menus/storymenu/weeks/week1.png b/assets/images/menus/storymenu/weeks/week1.png index 9421bd85..f3246757 100644 Binary files a/assets/images/menus/storymenu/weeks/week1.png and b/assets/images/menus/storymenu/weeks/week1.png differ diff --git a/assets/images/menus/storymenu/weeks/week2.png b/assets/images/menus/storymenu/weeks/week2.png index 0b76c032..20edbaae 100644 Binary files a/assets/images/menus/storymenu/weeks/week2.png and b/assets/images/menus/storymenu/weeks/week2.png differ diff --git a/assets/images/menus/storymenu/weeks/week3.png b/assets/images/menus/storymenu/weeks/week3.png index 4a331b15..c1b43251 100644 Binary files a/assets/images/menus/storymenu/weeks/week3.png and b/assets/images/menus/storymenu/weeks/week3.png differ diff --git a/assets/images/menus/storymenu/weeks/week4.png b/assets/images/menus/storymenu/weeks/week4.png index 7122f979..654acecb 100644 Binary files a/assets/images/menus/storymenu/weeks/week4.png and b/assets/images/menus/storymenu/weeks/week4.png differ diff --git a/assets/images/menus/storymenu/weeks/week5.png b/assets/images/menus/storymenu/weeks/week5.png index 1a3a02a8..fe548757 100644 Binary files a/assets/images/menus/storymenu/weeks/week5.png and b/assets/images/menus/storymenu/weeks/week5.png differ diff --git a/assets/images/menus/storymenu/weeks/week6.png b/assets/images/menus/storymenu/weeks/week6.png index 0e02a8b1..1eb70de1 100644 Binary files a/assets/images/menus/storymenu/weeks/week6.png and b/assets/images/menus/storymenu/weeks/week6.png differ diff --git a/assets/images/menus/storymenu/weeks/week7.png b/assets/images/menus/storymenu/weeks/week7.png index 6b526d3f..8960961f 100644 Binary files a/assets/images/menus/storymenu/weeks/week7.png and b/assets/images/menus/storymenu/weeks/week7.png differ diff --git a/assets/images/menus/titlescreen/gf.png b/assets/images/menus/titlescreen/gf.png index 2705f13d..2208cd48 100644 Binary files a/assets/images/menus/titlescreen/gf.png and b/assets/images/menus/titlescreen/gf.png differ diff --git a/assets/images/menus/titlescreen/newgrounds_logo.png b/assets/images/menus/titlescreen/newgrounds_logo.png index 26948b66..00e18517 100644 Binary files a/assets/images/menus/titlescreen/newgrounds_logo.png and b/assets/images/menus/titlescreen/newgrounds_logo.png differ diff --git a/assets/images/menus/titlescreen/titleEnter.png b/assets/images/menus/titlescreen/titleEnter.png index ad1a9f5f..957e5b75 100644 Binary files a/assets/images/menus/titlescreen/titleEnter.png and b/assets/images/menus/titlescreen/titleEnter.png differ diff --git a/assets/images/menus/transitionSpr.png b/assets/images/menus/transitionSpr.png index 0044c064..3b973a46 100644 Binary files a/assets/images/menus/transitionSpr.png and b/assets/images/menus/transitionSpr.png differ diff --git a/assets/images/menus/update/bf.png b/assets/images/menus/update/bf.png index 975f3d1a..bba8bbf6 100644 Binary files a/assets/images/menus/update/bf.png and b/assets/images/menus/update/bf.png differ diff --git a/assets/images/stages/default/stageback.png b/assets/images/stages/default/stageback.png index f0e69407..3baea3d2 100644 Binary files a/assets/images/stages/default/stageback.png and b/assets/images/stages/default/stageback.png differ diff --git a/assets/images/stages/default/stagecurtains.png b/assets/images/stages/default/stagecurtains.png index de33c06e..58574ae4 100644 Binary files a/assets/images/stages/default/stagecurtains.png and b/assets/images/stages/default/stagecurtains.png differ diff --git a/assets/images/stages/default/stagefront.png b/assets/images/stages/default/stagefront.png index 14790298..8176a71f 100644 Binary files a/assets/images/stages/default/stagefront.png and b/assets/images/stages/default/stagefront.png differ diff --git a/assets/images/stages/limo/bgLimo.png b/assets/images/stages/limo/bgLimo.png index 579862d0..54b58d04 100644 Binary files a/assets/images/stages/limo/bgLimo.png and b/assets/images/stages/limo/bgLimo.png differ diff --git a/assets/images/stages/limo/dumb.png b/assets/images/stages/limo/dumb.png index 25ea6651..62516171 100644 Binary files a/assets/images/stages/limo/dumb.png and b/assets/images/stages/limo/dumb.png differ diff --git a/assets/images/stages/limo/fastCarLol.png b/assets/images/stages/limo/fastCarLol.png index 392bc8e4..66be24ad 100644 Binary files a/assets/images/stages/limo/fastCarLol.png and b/assets/images/stages/limo/fastCarLol.png differ diff --git a/assets/images/stages/limo/limoDrive.png b/assets/images/stages/limo/limoDrive.png index 7ba8dd90..a7fd6db6 100644 Binary files a/assets/images/stages/limo/limoDrive.png and b/assets/images/stages/limo/limoDrive.png differ diff --git a/assets/images/stages/limo/limoOverlay.png b/assets/images/stages/limo/limoOverlay.png index 24ede11c..d44db7ca 100644 Binary files a/assets/images/stages/limo/limoOverlay.png and b/assets/images/stages/limo/limoOverlay.png differ diff --git a/assets/images/stages/limo/limoSunset.png b/assets/images/stages/limo/limoSunset.png index dddf07b2..e1c2c23a 100644 Binary files a/assets/images/stages/limo/limoSunset.png and b/assets/images/stages/limo/limoSunset.png differ diff --git a/assets/images/stages/mall/bgEscalator.png b/assets/images/stages/mall/bgEscalator.png index 8cff7edf..6cbae26c 100644 Binary files a/assets/images/stages/mall/bgEscalator.png and b/assets/images/stages/mall/bgEscalator.png differ diff --git a/assets/images/stages/mall/bgWalls.png b/assets/images/stages/mall/bgWalls.png index 0222b8cf..b17430a2 100644 Binary files a/assets/images/stages/mall/bgWalls.png and b/assets/images/stages/mall/bgWalls.png differ diff --git a/assets/images/stages/mall/bottomBop.png b/assets/images/stages/mall/bottomBop.png index d952e8e8..30cfe478 100644 Binary files a/assets/images/stages/mall/bottomBop.png and b/assets/images/stages/mall/bottomBop.png differ diff --git a/assets/images/stages/mall/christmasWall.png b/assets/images/stages/mall/christmasWall.png index 0ac5c218..906162e4 100644 Binary files a/assets/images/stages/mall/christmasWall.png and b/assets/images/stages/mall/christmasWall.png differ diff --git a/assets/images/stages/mall/evilBG.png b/assets/images/stages/mall/evilBG.png index ea087576..e7250675 100644 Binary files a/assets/images/stages/mall/evilBG.png and b/assets/images/stages/mall/evilBG.png differ diff --git a/assets/images/stages/mall/evilSnow.png b/assets/images/stages/mall/evilSnow.png index ff420ce6..912030a8 100644 Binary files a/assets/images/stages/mall/evilSnow.png and b/assets/images/stages/mall/evilSnow.png differ diff --git a/assets/images/stages/mall/fgSnow.png b/assets/images/stages/mall/fgSnow.png index 66091f02..c65ea057 100644 Binary files a/assets/images/stages/mall/fgSnow.png and b/assets/images/stages/mall/fgSnow.png differ diff --git a/assets/images/stages/mall/santa.png b/assets/images/stages/mall/santa.png index 58c1efad..4399b51c 100644 Binary files a/assets/images/stages/mall/santa.png and b/assets/images/stages/mall/santa.png differ diff --git a/assets/images/stages/mall/upperBop.png b/assets/images/stages/mall/upperBop.png index d3741eb3..2296eb0b 100644 Binary files a/assets/images/stages/mall/upperBop.png and b/assets/images/stages/mall/upperBop.png differ diff --git a/assets/images/stages/philly/behindTrain.png b/assets/images/stages/philly/behindTrain.png index fe5affe5..4df2fe41 100644 Binary files a/assets/images/stages/philly/behindTrain.png and b/assets/images/stages/philly/behindTrain.png differ diff --git a/assets/images/stages/philly/city.png b/assets/images/stages/philly/city.png index 46c44b20..e54e214a 100644 Binary files a/assets/images/stages/philly/city.png and b/assets/images/stages/philly/city.png differ diff --git a/assets/images/stages/philly/sky.png b/assets/images/stages/philly/sky.png index cf502063..95bf430e 100644 Binary files a/assets/images/stages/philly/sky.png and b/assets/images/stages/philly/sky.png differ diff --git a/assets/images/stages/philly/street.png b/assets/images/stages/philly/street.png index 14fd87bd..93082101 100644 Binary files a/assets/images/stages/philly/street.png and b/assets/images/stages/philly/street.png differ diff --git a/assets/images/stages/philly/train.png b/assets/images/stages/philly/train.png index cbee2da7..85b43f99 100644 Binary files a/assets/images/stages/philly/train.png and b/assets/images/stages/philly/train.png differ diff --git a/assets/images/stages/philly/win.png b/assets/images/stages/philly/win.png index 4e0091d6..dd8a43f7 100644 Binary files a/assets/images/stages/philly/win.png and b/assets/images/stages/philly/win.png differ diff --git a/assets/images/stages/school/animatedEvilSchool.png b/assets/images/stages/school/animatedEvilSchool.png index bc98fffc..9b2a1348 100644 Binary files a/assets/images/stages/school/animatedEvilSchool.png and b/assets/images/stages/school/animatedEvilSchool.png differ diff --git a/assets/images/stages/school/bgFreaks.png b/assets/images/stages/school/bgFreaks.png index db53cb20..253ed427 100644 Binary files a/assets/images/stages/school/bgFreaks.png and b/assets/images/stages/school/bgFreaks.png differ diff --git a/assets/images/stages/school/pause/bg.png b/assets/images/stages/school/pause/bg.png index 44d136cc..80c41a70 100644 Binary files a/assets/images/stages/school/pause/bg.png and b/assets/images/stages/school/pause/bg.png differ diff --git a/assets/images/stages/school/petals.png b/assets/images/stages/school/petals.png index 9ec86f70..49c4a766 100644 Binary files a/assets/images/stages/school/petals.png and b/assets/images/stages/school/petals.png differ diff --git a/assets/images/stages/school/ui/arrowEnds.png b/assets/images/stages/school/ui/arrowEnds.png index 9d4dc874..6c645fb1 100644 Binary files a/assets/images/stages/school/ui/arrowEnds.png and b/assets/images/stages/school/ui/arrowEnds.png differ diff --git a/assets/images/stages/school/ui/arrows-pixels.png b/assets/images/stages/school/ui/arrows-pixels.png index 235cdf2e..a1a7fd09 100644 Binary files a/assets/images/stages/school/ui/arrows-pixels.png and b/assets/images/stages/school/ui/arrows-pixels.png differ diff --git a/assets/images/stages/school/ui/bad.png b/assets/images/stages/school/ui/bad.png index 364c8ac4..f74abdb6 100644 Binary files a/assets/images/stages/school/ui/bad.png and b/assets/images/stages/school/ui/bad.png differ diff --git a/assets/images/stages/school/ui/combo.png b/assets/images/stages/school/ui/combo.png index 154244a1..ee368626 100644 Binary files a/assets/images/stages/school/ui/combo.png and b/assets/images/stages/school/ui/combo.png differ diff --git a/assets/images/stages/school/ui/go.png b/assets/images/stages/school/ui/go.png index ee331059..c754a97d 100644 Binary files a/assets/images/stages/school/ui/go.png and b/assets/images/stages/school/ui/go.png differ diff --git a/assets/images/stages/school/ui/good.png b/assets/images/stages/school/ui/good.png index 302986dc..a5eba2b7 100644 Binary files a/assets/images/stages/school/ui/good.png and b/assets/images/stages/school/ui/good.png differ diff --git a/assets/images/stages/school/ui/hand_textbox.png b/assets/images/stages/school/ui/hand_textbox.png index 5b4f1361..0ba7fd43 100644 Binary files a/assets/images/stages/school/ui/hand_textbox.png and b/assets/images/stages/school/ui/hand_textbox.png differ diff --git a/assets/images/stages/school/ui/num0.png b/assets/images/stages/school/ui/num0.png index d002c36c..e3ed8016 100644 Binary files a/assets/images/stages/school/ui/num0.png and b/assets/images/stages/school/ui/num0.png differ diff --git a/assets/images/stages/school/ui/num1.png b/assets/images/stages/school/ui/num1.png index 45a78d5b..a92cac9a 100644 Binary files a/assets/images/stages/school/ui/num1.png and b/assets/images/stages/school/ui/num1.png differ diff --git a/assets/images/stages/school/ui/num2.png b/assets/images/stages/school/ui/num2.png index 29396f94..12941da6 100644 Binary files a/assets/images/stages/school/ui/num2.png and b/assets/images/stages/school/ui/num2.png differ diff --git a/assets/images/stages/school/ui/num3.png b/assets/images/stages/school/ui/num3.png index 00dde82e..3e60295e 100644 Binary files a/assets/images/stages/school/ui/num3.png and b/assets/images/stages/school/ui/num3.png differ diff --git a/assets/images/stages/school/ui/num4.png b/assets/images/stages/school/ui/num4.png index d3be8b8c..1f63ea86 100644 Binary files a/assets/images/stages/school/ui/num4.png and b/assets/images/stages/school/ui/num4.png differ diff --git a/assets/images/stages/school/ui/num5.png b/assets/images/stages/school/ui/num5.png index cde2a204..79bf20f1 100644 Binary files a/assets/images/stages/school/ui/num5.png and b/assets/images/stages/school/ui/num5.png differ diff --git a/assets/images/stages/school/ui/num6.png b/assets/images/stages/school/ui/num6.png index cea53eb9..76603795 100644 Binary files a/assets/images/stages/school/ui/num6.png and b/assets/images/stages/school/ui/num6.png differ diff --git a/assets/images/stages/school/ui/num7.png b/assets/images/stages/school/ui/num7.png index f43639e7..0306f3ec 100644 Binary files a/assets/images/stages/school/ui/num7.png and b/assets/images/stages/school/ui/num7.png differ diff --git a/assets/images/stages/school/ui/num8.png b/assets/images/stages/school/ui/num8.png index 63531c58..ed05add5 100644 Binary files a/assets/images/stages/school/ui/num8.png and b/assets/images/stages/school/ui/num8.png differ diff --git a/assets/images/stages/school/ui/num9.png b/assets/images/stages/school/ui/num9.png index 91b05b66..ec888d2e 100644 Binary files a/assets/images/stages/school/ui/num9.png and b/assets/images/stages/school/ui/num9.png differ diff --git a/assets/images/stages/school/ui/pixel-secret-splashes.png b/assets/images/stages/school/ui/pixel-secret-splashes.png index b686e686..53f3c9be 100644 Binary files a/assets/images/stages/school/ui/pixel-secret-splashes.png and b/assets/images/stages/school/ui/pixel-secret-splashes.png differ diff --git a/assets/images/stages/school/ui/pixel-splashes.png b/assets/images/stages/school/ui/pixel-splashes.png index 78a06c6a..3b864bee 100644 Binary files a/assets/images/stages/school/ui/pixel-splashes.png and b/assets/images/stages/school/ui/pixel-splashes.png differ diff --git a/assets/images/stages/school/ui/ready.png b/assets/images/stages/school/ui/ready.png index b2f1c039..ec220e74 100644 Binary files a/assets/images/stages/school/ui/ready.png and b/assets/images/stages/school/ui/ready.png differ diff --git a/assets/images/stages/school/ui/set.png b/assets/images/stages/school/ui/set.png index b73b8449..79f2166c 100644 Binary files a/assets/images/stages/school/ui/set.png and b/assets/images/stages/school/ui/set.png differ diff --git a/assets/images/stages/school/ui/shit.png b/assets/images/stages/school/ui/shit.png index 46f415eb..601266dd 100644 Binary files a/assets/images/stages/school/ui/shit.png and b/assets/images/stages/school/ui/shit.png differ diff --git a/assets/images/stages/school/ui/sick.png b/assets/images/stages/school/ui/sick.png index 3d7de624..f19dcded 100644 Binary files a/assets/images/stages/school/ui/sick.png and b/assets/images/stages/school/ui/sick.png differ diff --git a/assets/images/stages/school/weebSchool.png b/assets/images/stages/school/weebSchool.png index a54e6cc7..fb44fea7 100644 Binary files a/assets/images/stages/school/weebSchool.png and b/assets/images/stages/school/weebSchool.png differ diff --git a/assets/images/stages/school/weebSky.png b/assets/images/stages/school/weebSky.png index 4d7201d4..0e4933a8 100644 Binary files a/assets/images/stages/school/weebSky.png and b/assets/images/stages/school/weebSky.png differ diff --git a/assets/images/stages/school/weebStreet.png b/assets/images/stages/school/weebStreet.png index b8fc1247..cf90f5f4 100644 Binary files a/assets/images/stages/school/weebStreet.png and b/assets/images/stages/school/weebStreet.png differ diff --git a/assets/images/stages/school/weebTrees.png b/assets/images/stages/school/weebTrees.png index b62368f2..efb9e031 100644 Binary files a/assets/images/stages/school/weebTrees.png and b/assets/images/stages/school/weebTrees.png differ diff --git a/assets/images/stages/school/weebTreesBack.png b/assets/images/stages/school/weebTreesBack.png index 1a5fcbc2..78460fa2 100644 Binary files a/assets/images/stages/school/weebTreesBack.png and b/assets/images/stages/school/weebTreesBack.png differ diff --git a/assets/images/stages/spooky/halloween_bg.png b/assets/images/stages/spooky/halloween_bg.png index 2105bc83..8c85c8a7 100644 Binary files a/assets/images/stages/spooky/halloween_bg.png and b/assets/images/stages/spooky/halloween_bg.png differ diff --git a/assets/images/stages/tank/smokeLeft.png b/assets/images/stages/tank/smokeLeft.png index 37dc5781..98aee0ca 100644 Binary files a/assets/images/stages/tank/smokeLeft.png and b/assets/images/stages/tank/smokeLeft.png differ diff --git a/assets/images/stages/tank/smokeRight.png b/assets/images/stages/tank/smokeRight.png index d02890a3..a7cb19a7 100644 Binary files a/assets/images/stages/tank/smokeRight.png and b/assets/images/stages/tank/smokeRight.png differ diff --git a/assets/images/stages/tank/tankClouds.png b/assets/images/stages/tank/tankClouds.png index c0112da0..b6d5b7ce 100644 Binary files a/assets/images/stages/tank/tankClouds.png and b/assets/images/stages/tank/tankClouds.png differ diff --git a/assets/images/stages/tank/tankGround.png b/assets/images/stages/tank/tankGround.png index 956b9e9e..6b2c3231 100644 Binary files a/assets/images/stages/tank/tankGround.png and b/assets/images/stages/tank/tankGround.png differ diff --git a/assets/images/stages/tank/tankMountains.png b/assets/images/stages/tank/tankMountains.png index 48e4158b..bc361ab0 100644 Binary files a/assets/images/stages/tank/tankMountains.png and b/assets/images/stages/tank/tankMountains.png differ diff --git a/assets/images/stages/tank/tankRuins.png b/assets/images/stages/tank/tankRuins.png index 1169d7f4..59551099 100644 Binary files a/assets/images/stages/tank/tankRuins.png and b/assets/images/stages/tank/tankRuins.png differ diff --git a/assets/images/stages/tank/tankSky.png b/assets/images/stages/tank/tankSky.png index 9a0a6207..87cedea8 100644 Binary files a/assets/images/stages/tank/tankSky.png and b/assets/images/stages/tank/tankSky.png differ diff --git a/assets/images/stages/tank/tankmanKilled1.png b/assets/images/stages/tank/tankmanKilled1.png index c6553402..9ecf9ad4 100644 Binary files a/assets/images/stages/tank/tankmanKilled1.png and b/assets/images/stages/tank/tankmanKilled1.png differ diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml index 25edfbcb..1b925cc2 100644 --- a/assets/languages/en/Editors.xml +++ b/assets/languages/en/Editors.xml @@ -273,6 +273,7 @@ <str id="acceptFolder">Press ACCEPT to edit this folder.</str> <str id="acceptCharacter">Press ACCEPT to edit this character.</str> <str id="newCharacter">New Character</str> + <str id="newCharacterDesc">Press ACCEPT to create a new character.</str> <str id="desc">Select a character to edit</str> <str id="desc-folder">Select a character to edit in {0}</str> diff --git a/assets/paths.json b/assets/paths.json deleted file mode 100644 index e69de29b..00000000 diff --git a/assets/shaders/engine/charterGrid.frag b/assets/shaders/engine/charterGrid.frag new file mode 100644 index 00000000..cddafa2a --- /dev/null +++ b/assets/shaders/engine/charterGrid.frag @@ -0,0 +1,35 @@ +#pragma header + +#define GRID_SIZE 40.0 +#define GRID_HEIGHT 4.0 + +uniform int segments; + +vec4 colorA = vec4(0.329, 0.329, 0.329, 1.0); +vec4 colorB = vec4(0.153, 0.153, 0.153, 1.0); +vec4 colorOutline = vec4(0.867, 0.867, 0.867, 1.0); + +void main() +{ + vec2 pixelSize = (1.0 / openfl_TextureSize) / (GRID_SIZE * float(segments)); + vec2 uv = openfl_TextureCoordv.xy; + vec2 pixelUV = vec2(uv.x * GRID_SIZE * float(segments), uv.y * GRID_SIZE * GRID_HEIGHT); + if (uv.x < pixelSize.x || uv.x > 1.0 - pixelSize.x) { //edges + gl_FragColor = colorOutline; + return; + } + vec4 col = colorA; + bool flip = false; + + //horizontal + if (mod(pixelUV.x, GRID_SIZE * 2.0) < GRID_SIZE) + flip = !flip; + //vertical + if (mod(pixelUV.y, GRID_SIZE * 2.0) < GRID_SIZE) + flip = !flip; + + if (flip) + col = colorB; + + gl_FragColor = col * flixel_texture2D(bitmap, uv); +} \ No newline at end of file diff --git a/assets/shaders/engine/editorWaveforms.frag b/assets/shaders/engine/editorWaveforms.frag index 490d6698..c65452dc 100644 --- a/assets/shaders/engine/editorWaveforms.frag +++ b/assets/shaders/engine/editorWaveforms.frag @@ -21,8 +21,9 @@ uniform bool lowDetail; float getAmplitude(vec2 pixel) { float pixelID = floor((pixel.y+pixelOffset)/3.0); - // TODO: INVESTIGATE THE 1.+ AND WHY IT WORKS (SERIOUSLY I GOT NO CLUE) -lunar - vec2 wavePixel = vec2(mod(pixelID, waveformSize.x), 1.0+floor(pixelID/waveformSize.x)); + // TODO: INVESTIGATE THE 1.+ AND WHY IT WORKS (SRSLY I GOT NO CLUE) -lunar + // Somehow using 0.999 instead of 1.0 fixes weird glitches on some systems -Ne_Eo + vec2 wavePixel = vec2(mod(pixelID, waveformSize.x), 0.999+floor(pixelID/waveformSize.x)); vec4 waveData = texture2D(waveformTexture, wavePixel / waveformSize); int id = int(mod(wavePixel.x, 3.0)); diff --git a/assets/shaders/engine/editorWaveformsRainbow.frag b/assets/shaders/engine/editorWaveformsRainbow.frag new file mode 100644 index 00000000..7ac0df32 --- /dev/null +++ b/assets/shaders/engine/editorWaveformsRainbow.frag @@ -0,0 +1,88 @@ +#pragma header + +// Used in charter by waveforms + +#define HEIGHT 0.05 // Height of the Bar +#define BACKLIGHT 0.6 // Backlight +#define BRIGHTNESS 0.33 // overall Brightness +#define GLOW 1.25 // Glow intensity of the Bar + +const vec3 gradient1 = vec3(114.0/255.0, 81.0/255.0, 135.0/255.0); +const vec3 gradient2 = vec3(144.0/255.0, 80.0/255.0, 186.0/255.0); + +const vec4 outlinecolor = vec4(0.1); +const vec3 lowdetailcol = vec3(0.0, 0.0, 1.0); + +uniform vec2 textureRes; +uniform float pixelOffset; + +uniform float playerPosition; + +uniform sampler2D waveformTexture; +uniform ivec2 waveformSize; + +uniform bool lowDetail; + +uniform float time; + +float getAmplitude(vec2 pixel) { + float pixelID = floor((pixel.y+pixelOffset)/3.0); + + // TODO: INVESTIGATE THE 1.+ AND WHY IT WORKS (SRSLY I GOT NO CLUE) -lunar + vec2 wavePixel = vec2(mod(pixelID,waveformSize.x), 1.0+floor(pixelID/waveformSize.x)); + vec4 waveData = texture2D(waveformTexture, wavePixel / waveformSize); + + int id = int(mod(wavePixel.x, 3.0)); + if(id == 0) return waveData.r; + if(id == 1) return waveData.g; + if(id == 2) return waveData.b; + return 0.0; +} + +float getAmpWidth(float amplitude) { + return (1.0-amplitude) * textureRes.x; +} + +bool inWaveForm(vec2 pixel, float width) { + float widthdiv2 = width/2.0; + return pixel.x > widthdiv2 && pixel.x < textureRes.x-widthdiv2; +} + +float getHightlight(float ampwidth) { + float remapCoord = (ampwidth/textureRes.x)/2.0; + float mappedCoord = map(openfl_TextureCoordv.x, remapCoord, 1.0-remapCoord, 0.0, 1.0); + return 1.0-(abs(mappedCoord-0.5)*2.0); +} + +void outlineWaveform(vec2 pixel, vec2 offset) { + float amplitude = getAmplitude(pixel - offset); + float ampwidth = (1.0-amplitude) * textureRes.x; + + if (!inWaveForm(pixel, ampwidth)) + gl_FragColor = vec4(vec3(0.), 1.); +} + +void main() +{ + vec2 pixel = openfl_TextureCoordv * textureRes; + float amplitude = getAmplitude(pixel); + float ampwidth = getAmpWidth(amplitude); + + gl_FragColor = vec4(vec3(0.0), .1); + + vec2 uv = openfl_TextureCoordv.xy - 0.5; + + float c = BACKLIGHT; + float a = abs(uv.x); + float s = 1.0 - smoothstep(0.0, ampwidth/textureRes.x, a); + c *= 1.33 - smoothstep(0.0, 0.5, a); + c*=c*c; + c += s*.5; + + if (inWaveForm(pixel, ampwidth)) + c += s; + + gl_FragColor = vec4((cos(6.283 * (uv.y + time + vec3(0.0,.33,0.66)))*map(getHightlight(amplitude), 0.0, 1.0, 0.5, 1.0)) + GLOW, 1.0) * c * BRIGHTNESS; + gl_FragColor.xyz *= map(getHightlight(amplitude), 0.0, 1.0, 0.5, 1.0); + gl_FragColor.xyz *= pixel.y+pixelOffset>playerPosition ? 0.6 : 1.0; +} \ No newline at end of file diff --git a/assets/songs/bopeebo/charts/easy.json b/assets/songs/bopeebo/charts/easy.json index f32e1dd2..dbad2465 100644 --- a/assets/songs/bopeebo/charts/easy.json +++ b/assets/songs/bopeebo/charts/easy.json @@ -1 +1 @@ -{"song":{"song":"Bopeebo","bpm":100.0,"needsVoices":true,"player1":"bf","player2":"dad","speed":1.0,"notes":[{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[0.0,2,0.0],[600.0,3,450.0],[1200.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[2400.0,2,0.0],[3000.0,3,450.0],[3600.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[4800.0,1,300.0],[5400.0,0,300.0],[6000.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[7200.0,1,300.0],[7800.0,0,300.0],[8400.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9600.0,1,0.0],[10200.0,3,0.0],[10500.0,0,0.0],[10800.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12000.0,1,0.0],[12600.0,3,0.0],[12900.0,0,0.0],[13200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[14400.0,3,0.0],[14700.0,1,0.0],[15300.0,0,0.0],[15600.0,2,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16800.0,3,0.0],[17100.0,1,0.0],[17700.0,0,0.0],[18000.0,2,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19200.0,0,0.0],[19500.0,3,0.0],[19800.0,1,900.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[21600.0,0,0.0],[21900.0,3,0.0],[22200.0,1,900.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24000.0,1,0.0],[24300.0,3,0.0],[24600.0,0,900.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[26400.0,1,0.0],[26700.0,3,0.0],[27000.0,0,900.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[28800.0,2,0.0],[29100.0,3,0.0],[29400.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31200.0,2,0.0],[31500.0,3,0.0],[31800.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33600.0,0,0.0],[33900.0,3,0.0],[34500.0,2,0.0],[34800.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36000.0,0,0.0],[36300.0,3,0.0],[36900.0,2,0.0],[37200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[38400.0,2,450.0],[39000.0,3,300.0],[39600.0,0,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[40800.0,2,450.0],[41400.0,3,300.0],[42000.0,0,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[43200.0,1,0.0],[43800.0,2,0.0],[44400.0,1,0.0],[44700.0,1,0.0],[45000.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[45600.0,1,0.0],[46200.0,2,0.0],[46800.0,1,0.0],[47100.0,1,0.0],[47400.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48000.0,2,450.0],[48600.0,3,300.0],[49200.0,0,450.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[50400.0,2,450.0],[51000.0,3,300.0],[51600.0,0,450.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52800.0,3,1800.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[55200.0,3,1800.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57600.0,2,0.0],[57900.0,3,0.0],[58200.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60000.0,2,0.0],[60300.0,3,0.0],[60600.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62400.0,0,0.0],[62700.0,3,0.0],[63300.0,2,0.0],[63600.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64800.0,0,0.0],[65100.0,3,0.0],[65700.0,2,0.0],[66000.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[67200.0,2,0.0],[67500.0,3,0.0],[67800.0,0,0.0],[68100.0,2,0.0],[68400.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[69600.0,2,0.0],[69900.0,3,0.0],[70200.0,0,0.0],[70500.0,2,0.0],[70800.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72000.0,0,0.0],[72300.0,3,0.0],[72900.0,2,0.0],[73200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[74400.0,0,0.0],[74700.0,3,0.0],[75300.0,2,0.0],[75600.0,1,600.0]]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":2400,"name":"Camera Movement"},{"params":[0],"time":4800,"name":"Camera Movement"},{"params":[1],"time":7200,"name":"Camera Movement"},{"params":[0],"time":9600,"name":"Camera Movement"},{"params":[1],"time":12000,"name":"Camera Movement"},{"params":[0],"time":14400,"name":"Camera Movement"},{"params":[1],"time":16800,"name":"Camera Movement"},{"params":[0],"time":19200,"name":"Camera Movement"},{"params":[1],"time":21600,"name":"Camera Movement"},{"params":[0],"time":24000,"name":"Camera Movement"},{"params":[1],"time":26400,"name":"Camera Movement"},{"params":[0],"time":28800,"name":"Camera Movement"},{"params":[1],"time":31200,"name":"Camera Movement"},{"params":[0],"time":33600,"name":"Camera Movement"},{"params":[1],"time":36000,"name":"Camera Movement"},{"params":[0],"time":38400,"name":"Camera Movement"},{"params":[1],"time":40800,"name":"Camera Movement"},{"params":[0],"time":43200,"name":"Camera Movement"},{"params":[1],"time":45600,"name":"Camera Movement"},{"params":[0],"time":48000,"name":"Camera Movement"},{"params":[1],"time":50400,"name":"Camera Movement"},{"params":[0],"time":52800,"name":"Camera Movement"},{"params":[1],"time":55200,"name":"Camera Movement"},{"params":[0],"time":57600,"name":"Camera Movement"},{"params":[1],"time":60000,"name":"Camera Movement"},{"params":[0],"time":62400,"name":"Camera Movement"},{"params":[1],"time":64800,"name":"Camera Movement"},{"params":[0],"time":67200,"name":"Camera Movement"},{"params":[1],"time":69600,"name":"Camera Movement"},{"params":[0],"time":72000,"name":"Camera Movement"},{"params":[1],"time":74400,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":3,"sLen":450,"time":600,"type":0},{"id":3,"sLen":600,"time":1200,"type":0},{"id":1,"sLen":300,"time":4800,"type":0},{"id":0,"sLen":300,"time":5400,"type":0},{"id":3,"sLen":600,"time":6000,"type":0},{"id":1,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10200,"type":0},{"id":0,"sLen":0,"time":10500,"type":0},{"id":1,"sLen":600,"time":10800,"type":0},{"id":3,"sLen":0,"time":14400,"type":0},{"id":1,"sLen":0,"time":14700,"type":0},{"id":0,"sLen":0,"time":15300,"type":0},{"id":2,"sLen":600,"time":15600,"type":0},{"id":0,"sLen":0,"time":19200,"type":0},{"id":3,"sLen":0,"time":19500,"type":0},{"id":1,"sLen":900,"time":19800,"type":0},{"id":1,"sLen":0,"time":24000,"type":0},{"id":3,"sLen":0,"time":24300,"type":0},{"id":0,"sLen":900,"time":24600,"type":0},{"id":2,"sLen":0,"time":28800,"type":0},{"id":3,"sLen":0,"time":29100,"type":0},{"id":0,"sLen":1200,"time":29400,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":3,"sLen":0,"time":33900,"type":0},{"id":2,"sLen":0,"time":34500,"type":0},{"id":1,"sLen":600,"time":34800,"type":0},{"id":2,"sLen":450,"time":38400,"type":0},{"id":3,"sLen":300,"time":39000,"type":0},{"id":0,"sLen":600,"time":39600,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":2,"sLen":0,"time":43800,"type":0},{"id":1,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":44700,"type":0},{"id":2,"sLen":0,"time":45000,"type":0},{"id":2,"sLen":450,"time":48000,"type":0},{"id":3,"sLen":300,"time":48600,"type":0},{"id":0,"sLen":450,"time":49200,"type":0},{"id":3,"sLen":1800,"time":52800,"type":0},{"id":2,"sLen":0,"time":57600,"type":0},{"id":3,"sLen":0,"time":57900,"type":0},{"id":0,"sLen":1200,"time":58200,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":3,"sLen":0,"time":62700,"type":0},{"id":2,"sLen":0,"time":63300,"type":0},{"id":1,"sLen":600,"time":63600,"type":0},{"id":2,"sLen":0,"time":67200,"type":0},{"id":3,"sLen":0,"time":67500,"type":0},{"id":0,"sLen":0,"time":67800,"type":0},{"id":2,"sLen":0,"time":68100,"type":0},{"id":1,"sLen":600,"time":68400,"type":0},{"id":0,"sLen":0,"time":72000,"type":0},{"id":3,"sLen":0,"time":72300,"type":0},{"id":2,"sLen":0,"time":72900,"type":0},{"id":1,"sLen":600,"time":73200,"type":0}],"position":"dad","type":0,"characters":["dad"]},{"visible":true,"keyCount":4,"notes":[{"id":2,"sLen":0,"time":2400,"type":0},{"id":3,"sLen":450,"time":3000,"type":0},{"id":3,"sLen":600,"time":3600,"type":0},{"id":1,"sLen":300,"time":7200,"type":0},{"id":0,"sLen":300,"time":7800,"type":0},{"id":3,"sLen":600,"time":8400,"type":0},{"id":1,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":12600,"type":0},{"id":0,"sLen":0,"time":12900,"type":0},{"id":1,"sLen":600,"time":13200,"type":0},{"id":3,"sLen":0,"time":16800,"type":0},{"id":1,"sLen":0,"time":17100,"type":0},{"id":0,"sLen":0,"time":17700,"type":0},{"id":2,"sLen":600,"time":18000,"type":0},{"id":0,"sLen":0,"time":21600,"type":0},{"id":3,"sLen":0,"time":21900,"type":0},{"id":1,"sLen":900,"time":22200,"type":0},{"id":1,"sLen":0,"time":26400,"type":0},{"id":3,"sLen":0,"time":26700,"type":0},{"id":0,"sLen":900,"time":27000,"type":0},{"id":2,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":31500,"type":0},{"id":0,"sLen":1200,"time":31800,"type":0},{"id":0,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":36300,"type":0},{"id":2,"sLen":0,"time":36900,"type":0},{"id":1,"sLen":600,"time":37200,"type":0},{"id":2,"sLen":450,"time":40800,"type":0},{"id":3,"sLen":300,"time":41400,"type":0},{"id":0,"sLen":600,"time":42000,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":2,"sLen":0,"time":46200,"type":0},{"id":1,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":47100,"type":0},{"id":2,"sLen":0,"time":47400,"type":1},{"id":2,"sLen":450,"time":50400,"type":0},{"id":3,"sLen":300,"time":51000,"type":0},{"id":0,"sLen":450,"time":51600,"type":0},{"id":3,"sLen":1800,"time":55200,"type":0},{"id":2,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60300,"type":0},{"id":0,"sLen":1200,"time":60600,"type":0},{"id":0,"sLen":0,"time":64800,"type":0},{"id":3,"sLen":0,"time":65100,"type":0},{"id":2,"sLen":0,"time":65700,"type":0},{"id":1,"sLen":600,"time":66000,"type":0},{"id":2,"sLen":0,"time":69600,"type":0},{"id":3,"sLen":0,"time":69900,"type":0},{"id":0,"sLen":0,"time":70200,"type":0},{"id":2,"sLen":0,"time":70500,"type":0},{"id":1,"sLen":600,"time":70800,"type":0},{"id":0,"sLen":0,"time":74400,"type":0},{"id":3,"sLen":0,"time":74700,"type":0},{"id":2,"sLen":0,"time":75300,"type":0},{"id":1,"sLen":600,"time":75600,"type":0}],"position":"boyfriend","type":1,"characters":["bf"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf"]}],"codenameChart":true,"scrollSpeed":1,"chartVersion":"1.6.0","stage":"stage","noteTypes":["No Anim Note"]} \ No newline at end of file diff --git a/assets/songs/bopeebo/charts/hard.json b/assets/songs/bopeebo/charts/hard.json index 09e58004..06009383 100644 --- a/assets/songs/bopeebo/charts/hard.json +++ b/assets/songs/bopeebo/charts/hard.json @@ -1 +1 @@ -{"song":{"song":"Bopeebo","bpm":100.0,"needsVoices":true,"player1":"bf","player2":"dad","speed":1.3,"notes":[{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[0.0,2,0.0],[600.0,3,600.0],[1200.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[2400.0,2,0.0],[3000.0,3,600.0],[3600.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[4800.0,1,300.0],[5400.0,0,300.0],[6000.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[7200.0,1,300.0],[7800.0,0,300.0],[8400.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9600.0,1,300.0],[10200.0,3,0.0],[10500.0,0,0.0],[10800.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12000.0,1,300.0],[12600.0,3,0.0],[12900.0,0,0.0],[13200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[14400.0,3,0.0],[14700.0,1,0.0],[15300.0,0,0.0],[15600.0,2,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16800.0,3,0.0],[17100.0,1,0.0],[17700.0,0,0.0],[18000.0,2,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19200.0,0,0.0],[19500.0,3,0.0],[19800.0,1,900.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[21600.0,0,0.0],[21900.0,3,0.0],[22200.0,1,900.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24000.0,1,0.0],[24300.0,3,0.0],[24600.0,0,900.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[26400.0,1,0.0],[26700.0,3,0.0],[27000.0,0,900.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[28800.0,2,0.0],[29100.0,3,0.0],[29400.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31200.0,2,0.0],[31500.0,3,0.0],[31800.0,0,1200.0],[33300.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33600.0,0,0.0],[33900.0,3,0.0],[34500.0,2,0.0],[34575.0,0,0.0],[34800.0,1,600.0],[35700.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36000.0,0,0.0],[36300.0,3,0.0],[36900.0,2,0.0],[36975.0,0,0.0],[37200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[38400.0,2,450.0],[39000.0,3,300.0],[39600.0,0,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[40800.0,2,450.0],[41400.0,3,300.0],[42000.0,0,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[43200.0,1,0.0],[43800.0,2,0.0],[44400.0,1,0.0],[44550.0,1,0.0],[44700.0,1,0.0],[45000.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[45600.0,1,0.0],[46200.0,2,0.0],[46800.0,1,0.0],[46950.0,1,0.0],[47100.0,1,0.0],[47400.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48000.0,2,450.0],[48600.0,3,300.0],[49200.0,0,450.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[50400.0,2,450.0],[51000.0,3,300.0],[51600.0,0,450.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52800.0,3,1800.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[55200.0,3,1800.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57600.0,2,0.0],[57900.0,3,0.0],[58200.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60000.0,2,0.0],[60300.0,3,0.0],[60600.0,0,1200.0],[62100.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62400.0,0,0.0],[62700.0,3,0.0],[63300.0,2,0.0],[63375.0,0,0.0],[63600.0,1,600.0],[64500.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64800.0,0,0.0],[65100.0,3,0.0],[65700.0,2,0.0],[65775.0,0,0.0],[66000.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[67200.0,2,0.0],[67500.0,3,0.0],[67800.0,0,0.0],[68100.0,2,0.0],[68400.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[69600.0,2,0.0],[69900.0,3,0.0],[70200.0,0,0.0],[70500.0,2,0.0],[70800.0,1,600.0],[71700.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72000.0,0,0.0],[72300.0,3,0.0],[72900.0,2,0.0],[72975.0,0,0.0],[73200.0,1,600.0],[74100.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[74400.0,0,0.0],[74700.0,3,0.0],[75300.0,2,0.0],[75375.0,0,0.0],[75600.0,1,600.0]]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":2400,"name":"Camera Movement"},{"params":[0],"time":4800,"name":"Camera Movement"},{"params":[1],"time":7200,"name":"Camera Movement"},{"params":[0],"time":9600,"name":"Camera Movement"},{"params":[1],"time":12000,"name":"Camera Movement"},{"params":[0],"time":14400,"name":"Camera Movement"},{"params":[1],"time":16800,"name":"Camera Movement"},{"params":[0],"time":19200,"name":"Camera Movement"},{"params":[1],"time":21600,"name":"Camera Movement"},{"params":[0],"time":24000,"name":"Camera Movement"},{"params":[1],"time":26400,"name":"Camera Movement"},{"params":[0],"time":28800,"name":"Camera Movement"},{"params":[1],"time":31200,"name":"Camera Movement"},{"params":[0],"time":33600,"name":"Camera Movement"},{"params":[1],"time":36000,"name":"Camera Movement"},{"params":[0],"time":38400,"name":"Camera Movement"},{"params":[1],"time":40800,"name":"Camera Movement"},{"params":[0],"time":43200,"name":"Camera Movement"},{"params":[1],"time":45600,"name":"Camera Movement"},{"params":[0],"time":48000,"name":"Camera Movement"},{"params":[1],"time":50400,"name":"Camera Movement"},{"params":[0],"time":52800,"name":"Camera Movement"},{"params":[1],"time":55200,"name":"Camera Movement"},{"params":[0],"time":57600,"name":"Camera Movement"},{"params":[1],"time":60000,"name":"Camera Movement"},{"params":[0],"time":62400,"name":"Camera Movement"},{"params":[1],"time":64800,"name":"Camera Movement"},{"params":[0],"time":67200,"name":"Camera Movement"},{"params":[1],"time":69600,"name":"Camera Movement"},{"params":[0],"time":72000,"name":"Camera Movement"},{"params":[1],"time":74400,"name":"Camera Movement"}],"codenameChart":true,"strumLines":[{"keyCount":4,"position":"dad","notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":3,"sLen":600,"time":600,"type":0},{"id":3,"sLen":600,"time":1200,"type":0},{"id":1,"sLen":300,"time":4800,"type":0},{"id":0,"sLen":300,"time":5400,"type":0},{"id":3,"sLen":600,"time":6000,"type":0},{"id":1,"sLen":300,"time":9600,"type":0},{"id":3,"sLen":0,"time":10200,"type":0},{"id":0,"sLen":0,"time":10500,"type":0},{"id":1,"sLen":600,"time":10800,"type":0},{"id":3,"sLen":0,"time":14400,"type":0},{"id":1,"sLen":0,"time":14700,"type":0},{"id":0,"sLen":0,"time":15300,"type":0},{"id":2,"sLen":600,"time":15600,"type":0},{"id":0,"sLen":0,"time":19200,"type":0},{"id":3,"sLen":0,"time":19500,"type":0},{"id":1,"sLen":900,"time":19800,"type":0},{"id":1,"sLen":0,"time":24000,"type":0},{"id":3,"sLen":0,"time":24300,"type":0},{"id":0,"sLen":900,"time":24600,"type":0},{"id":2,"sLen":0,"time":28800,"type":0},{"id":3,"sLen":0,"time":29100,"type":0},{"id":0,"sLen":1200,"time":29400,"type":0},{"id":2,"sLen":0,"time":33300,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":3,"sLen":0,"time":33900,"type":0},{"id":2,"sLen":0,"time":34500,"type":0},{"id":0,"sLen":0,"time":34575,"type":0},{"id":1,"sLen":600,"time":34800,"type":0},{"id":2,"sLen":450,"time":38400,"type":0},{"id":3,"sLen":300,"time":39000,"type":0},{"id":0,"sLen":600,"time":39600,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":2,"sLen":0,"time":43800,"type":0},{"id":1,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":44550,"type":0},{"id":1,"sLen":0,"time":44700,"type":0},{"id":2,"sLen":0,"time":45000,"type":0},{"id":2,"sLen":450,"time":48000,"type":0},{"id":3,"sLen":300,"time":48600,"type":0},{"id":0,"sLen":450,"time":49200,"type":0},{"id":3,"sLen":1800,"time":52800,"type":0},{"id":2,"sLen":0,"time":57600,"type":0},{"id":3,"sLen":0,"time":57900,"type":0},{"id":0,"sLen":1200,"time":58200,"type":0},{"id":2,"sLen":0,"time":62100,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":3,"sLen":0,"time":62700,"type":0},{"id":2,"sLen":0,"time":63300,"type":0},{"id":0,"sLen":0,"time":63375,"type":0},{"id":1,"sLen":600,"time":63600,"type":0},{"id":2,"sLen":0,"time":67200,"type":0},{"id":3,"sLen":0,"time":67500,"type":0},{"id":0,"sLen":0,"time":67800,"type":0},{"id":2,"sLen":0,"time":68100,"type":0},{"id":1,"sLen":600,"time":68400,"type":0},{"id":2,"sLen":0,"time":71700,"type":0},{"id":0,"sLen":0,"time":72000,"type":0},{"id":3,"sLen":0,"time":72300,"type":0},{"id":2,"sLen":0,"time":72900,"type":0},{"id":0,"sLen":0,"time":72975,"type":0},{"id":1,"sLen":600,"time":73200,"type":0}],"characters":["dad"],"type":0,"visible":true},{"keyCount":4,"position":"boyfriend","notes":[{"id":2,"sLen":0,"time":2400,"type":0},{"id":3,"sLen":600,"time":3000,"type":0},{"id":3,"sLen":600,"time":3600,"type":0},{"id":1,"sLen":300,"time":7200,"type":0},{"id":0,"sLen":300,"time":7800,"type":0},{"id":3,"sLen":600,"time":8400,"type":0},{"id":1,"sLen":300,"time":12000,"type":0},{"id":3,"sLen":0,"time":12600,"type":0},{"id":0,"sLen":0,"time":12900,"type":0},{"id":1,"sLen":600,"time":13200,"type":0},{"id":3,"sLen":0,"time":16800,"type":0},{"id":1,"sLen":0,"time":17100,"type":0},{"id":0,"sLen":0,"time":17700,"type":0},{"id":2,"sLen":600,"time":18000,"type":0},{"id":0,"sLen":0,"time":21600,"type":0},{"id":3,"sLen":0,"time":21900,"type":0},{"id":1,"sLen":900,"time":22200,"type":0},{"id":1,"sLen":0,"time":26400,"type":0},{"id":3,"sLen":0,"time":26700,"type":0},{"id":0,"sLen":900,"time":27000,"type":0},{"id":2,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":31500,"type":0},{"id":0,"sLen":1200,"time":31800,"type":0},{"id":2,"sLen":0,"time":35700,"type":0},{"id":0,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":36300,"type":0},{"id":2,"sLen":0,"time":36900,"type":0},{"id":0,"sLen":0,"time":36975,"type":0},{"id":1,"sLen":600,"time":37200,"type":0},{"id":2,"sLen":450,"time":40800,"type":0},{"id":3,"sLen":300,"time":41400,"type":0},{"id":0,"sLen":600,"time":42000,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":2,"sLen":0,"time":46200,"type":0},{"id":1,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":46950,"type":0},{"id":1,"sLen":0,"time":47100,"type":0},{"id":2,"sLen":0,"time":47400,"type":1},{"id":2,"sLen":450,"time":50400,"type":0},{"id":3,"sLen":300,"time":51000,"type":0},{"id":0,"sLen":450,"time":51600,"type":0},{"id":3,"sLen":1800,"time":55200,"type":0},{"id":2,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60300,"type":0},{"id":0,"sLen":1200,"time":60600,"type":0},{"id":2,"sLen":0,"time":64500,"type":0},{"id":0,"sLen":0,"time":64800,"type":0},{"id":3,"sLen":0,"time":65100,"type":0},{"id":2,"sLen":0,"time":65700,"type":0},{"id":0,"sLen":0,"time":65775,"type":0},{"id":1,"sLen":600,"time":66000,"type":0},{"id":2,"sLen":0,"time":69600,"type":0},{"id":3,"sLen":0,"time":69900,"type":0},{"id":0,"sLen":0,"time":70200,"type":0},{"id":2,"sLen":0,"time":70500,"type":0},{"id":1,"sLen":600,"time":70800,"type":0},{"id":2,"sLen":0,"time":74100,"type":0},{"id":0,"sLen":0,"time":74400,"type":0},{"id":3,"sLen":0,"time":74700,"type":0},{"id":2,"sLen":0,"time":75300,"type":0},{"id":0,"sLen":0,"time":75375,"type":0},{"id":1,"sLen":600,"time":75600,"type":0}],"characters":["bf"],"type":1,"visible":true},{"keyCount":4,"position":"girlfriend","notes":[],"characters":["gf"],"type":2,"visible":false}],"scrollSpeed":1.3,"chartVersion":"1.6.0","stage":"stage","noteTypes":["No Anim Note"]} \ No newline at end of file diff --git a/assets/songs/bopeebo/charts/normal.json b/assets/songs/bopeebo/charts/normal.json index 3567ce5e..c70f6291 100644 --- a/assets/songs/bopeebo/charts/normal.json +++ b/assets/songs/bopeebo/charts/normal.json @@ -1 +1 @@ -{"song":{"song":"Bopeebo","bpm":100.0,"needsVoices":true,"player1":"bf","player2":"dad","speed":1.0,"notes":[{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[0.0,2,0.0],[600.0,3,600.0],[1200.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[2400.0,2,0.0],[3000.0,3,600.0],[3600.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[4800.0,1,300.0],[5400.0,0,300.0],[6000.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[7200.0,1,300.0],[7800.0,0,300.0],[8400.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9600.0,1,300.0],[10200.0,3,0.0],[10500.0,0,0.0],[10800.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12000.0,1,300.0],[12600.0,3,0.0],[12900.0,0,0.0],[13200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[14400.0,3,0.0],[14700.0,1,0.0],[15300.0,0,0.0],[15600.0,2,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16800.0,3,0.0],[17100.0,1,0.0],[17700.0,0,0.0],[18000.0,2,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19200.0,0,0.0],[19500.0,3,0.0],[19800.0,1,900.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[21600.0,0,0.0],[21900.0,3,0.0],[22200.0,1,900.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24000.0,1,0.0],[24300.0,3,0.0],[24600.0,0,900.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[26400.0,1,0.0],[26700.0,3,0.0],[27000.0,0,900.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[28800.0,2,0.0],[29100.0,3,0.0],[29400.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31200.0,2,0.0],[31500.0,3,0.0],[31800.0,0,1200.0],[33300.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33600.0,0,0.0],[33900.0,3,0.0],[34500.0,2,0.0],[34800.0,1,600.0],[35700.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36000.0,0,0.0],[36300.0,3,0.0],[36900.0,2,0.0],[37200.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[38400.0,2,450.0],[39000.0,3,300.0],[39600.0,0,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[40800.0,2,450.0],[41400.0,3,300.0],[42000.0,0,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[43200.0,1,0.0],[43800.0,2,0.0],[44400.0,1,0.0],[44550.0,1,0.0],[44700.0,1,0.0],[45000.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[45600.0,1,0.0],[46200.0,2,0.0],[46800.0,1,0.0],[46950.0,1,0.0],[47100.0,1,0.0],[47400.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48000.0,2,450.0],[48600.0,3,300.0],[49200.0,0,450.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[50400.0,2,450.0],[51000.0,3,300.0],[51600.0,0,450.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52800.0,3,1800.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[55200.0,3,1800.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57600.0,2,0.0],[57900.0,3,0.0],[58200.0,0,1200.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60000.0,2,0.0],[60300.0,3,0.0],[60600.0,0,1200.0],[62100.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62400.0,0,0.0],[62700.0,3,0.0],[63300.0,2,0.0],[63600.0,1,600.0],[64500.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64800.0,0,0.0],[65100.0,3,0.0],[65700.0,2,0.0],[66000.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[67200.0,2,0.0],[67500.0,3,0.0],[67800.0,0,0.0],[68100.0,2,0.0],[68400.0,1,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[69600.0,2,0.0],[69900.0,3,0.0],[70200.0,0,0.0],[70500.0,2,0.0],[70800.0,1,600.0],[71700.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72000.0,0,0.0],[72300.0,3,0.0],[72900.0,2,0.0],[73200.0,1,600.0],[74100.0,6,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[74400.0,0,0.0],[74700.0,3,0.0],[75300.0,2,0.0],[75600.0,1,600.0]]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":2400,"name":"Camera Movement"},{"params":[0],"time":4800,"name":"Camera Movement"},{"params":[1],"time":7200,"name":"Camera Movement"},{"params":[0],"time":9600,"name":"Camera Movement"},{"params":[1],"time":12000,"name":"Camera Movement"},{"params":[0],"time":14400,"name":"Camera Movement"},{"params":[1],"time":16800,"name":"Camera Movement"},{"params":[0],"time":19200,"name":"Camera Movement"},{"params":[1],"time":21600,"name":"Camera Movement"},{"params":[0],"time":24000,"name":"Camera Movement"},{"params":[1],"time":26400,"name":"Camera Movement"},{"params":[0],"time":28800,"name":"Camera Movement"},{"params":[1],"time":31200,"name":"Camera Movement"},{"params":[0],"time":33600,"name":"Camera Movement"},{"params":[1],"time":36000,"name":"Camera Movement"},{"params":[0],"time":38400,"name":"Camera Movement"},{"params":[1],"time":40800,"name":"Camera Movement"},{"params":[0],"time":43200,"name":"Camera Movement"},{"params":[1],"time":45600,"name":"Camera Movement"},{"params":[0],"time":48000,"name":"Camera Movement"},{"params":[1],"time":50400,"name":"Camera Movement"},{"params":[0],"time":52800,"name":"Camera Movement"},{"params":[1],"time":55200,"name":"Camera Movement"},{"params":[0],"time":57600,"name":"Camera Movement"},{"params":[1],"time":60000,"name":"Camera Movement"},{"params":[0],"time":62400,"name":"Camera Movement"},{"params":[1],"time":64800,"name":"Camera Movement"},{"params":[0],"time":67200,"name":"Camera Movement"},{"params":[1],"time":69600,"name":"Camera Movement"},{"params":[0],"time":72000,"name":"Camera Movement"},{"params":[1],"time":74400,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":3,"sLen":600,"time":600,"type":0},{"id":3,"sLen":600,"time":1200,"type":0},{"id":1,"sLen":300,"time":4800,"type":0},{"id":0,"sLen":300,"time":5400,"type":0},{"id":3,"sLen":600,"time":6000,"type":0},{"id":1,"sLen":300,"time":9600,"type":0},{"id":3,"sLen":0,"time":10200,"type":0},{"id":0,"sLen":0,"time":10500,"type":0},{"id":1,"sLen":600,"time":10800,"type":0},{"id":3,"sLen":0,"time":14400,"type":0},{"id":1,"sLen":0,"time":14700,"type":0},{"id":0,"sLen":0,"time":15300,"type":0},{"id":2,"sLen":600,"time":15600,"type":0},{"id":0,"sLen":0,"time":19200,"type":0},{"id":3,"sLen":0,"time":19500,"type":0},{"id":1,"sLen":900,"time":19800,"type":0},{"id":1,"sLen":0,"time":24000,"type":0},{"id":3,"sLen":0,"time":24300,"type":0},{"id":0,"sLen":900,"time":24600,"type":0},{"id":2,"sLen":0,"time":28800,"type":0},{"id":3,"sLen":0,"time":29100,"type":0},{"id":0,"sLen":1200,"time":29400,"type":0},{"id":2,"sLen":0,"time":33300,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":3,"sLen":0,"time":33900,"type":0},{"id":2,"sLen":0,"time":34500,"type":0},{"id":1,"sLen":600,"time":34800,"type":0},{"id":2,"sLen":450,"time":38400,"type":0},{"id":3,"sLen":300,"time":39000,"type":0},{"id":0,"sLen":600,"time":39600,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":2,"sLen":0,"time":43800,"type":0},{"id":1,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":44550,"type":0},{"id":1,"sLen":0,"time":44700,"type":0},{"id":2,"sLen":0,"time":45000,"type":0},{"id":2,"sLen":450,"time":48000,"type":0},{"id":3,"sLen":300,"time":48600,"type":0},{"id":0,"sLen":450,"time":49200,"type":0},{"id":3,"sLen":1800,"time":52800,"type":0},{"id":2,"sLen":0,"time":57600,"type":0},{"id":3,"sLen":0,"time":57900,"type":0},{"id":0,"sLen":1200,"time":58200,"type":0},{"id":2,"sLen":0,"time":62100,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":3,"sLen":0,"time":62700,"type":0},{"id":2,"sLen":0,"time":63300,"type":0},{"id":1,"sLen":600,"time":63600,"type":0},{"id":2,"sLen":0,"time":67200,"type":0},{"id":3,"sLen":0,"time":67500,"type":0},{"id":0,"sLen":0,"time":67800,"type":0},{"id":2,"sLen":0,"time":68100,"type":0},{"id":1,"sLen":600,"time":68400,"type":0},{"id":2,"sLen":0,"time":71700,"type":0},{"id":0,"sLen":0,"time":72000,"type":0},{"id":3,"sLen":0,"time":72300,"type":0},{"id":2,"sLen":0,"time":72900,"type":0},{"id":1,"sLen":600,"time":73200,"type":0}],"position":"dad","type":0,"characters":["dad"]},{"visible":true,"keyCount":4,"notes":[{"id":2,"sLen":0,"time":2400,"type":0},{"id":3,"sLen":600,"time":3000,"type":0},{"id":3,"sLen":600,"time":3600,"type":0},{"id":1,"sLen":300,"time":7200,"type":0},{"id":0,"sLen":300,"time":7800,"type":0},{"id":3,"sLen":600,"time":8400,"type":0},{"id":1,"sLen":300,"time":12000,"type":0},{"id":3,"sLen":0,"time":12600,"type":0},{"id":0,"sLen":0,"time":12900,"type":0},{"id":1,"sLen":600,"time":13200,"type":0},{"id":3,"sLen":0,"time":16800,"type":0},{"id":1,"sLen":0,"time":17100,"type":0},{"id":0,"sLen":0,"time":17700,"type":0},{"id":2,"sLen":600,"time":18000,"type":0},{"id":0,"sLen":0,"time":21600,"type":0},{"id":3,"sLen":0,"time":21900,"type":0},{"id":1,"sLen":900,"time":22200,"type":0},{"id":1,"sLen":0,"time":26400,"type":0},{"id":3,"sLen":0,"time":26700,"type":0},{"id":0,"sLen":900,"time":27000,"type":0},{"id":2,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":31500,"type":0},{"id":0,"sLen":1200,"time":31800,"type":0},{"id":2,"sLen":0,"time":35700,"type":0},{"id":0,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":36300,"type":0},{"id":2,"sLen":0,"time":36900,"type":0},{"id":1,"sLen":600,"time":37200,"type":0},{"id":2,"sLen":450,"time":40800,"type":0},{"id":3,"sLen":300,"time":41400,"type":0},{"id":0,"sLen":600,"time":42000,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":2,"sLen":0,"time":46200,"type":0},{"id":1,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":46950,"type":0},{"id":1,"sLen":0,"time":47100,"type":0},{"id":2,"sLen":0,"time":47400,"type":1},{"id":2,"sLen":450,"time":50400,"type":0},{"id":3,"sLen":300,"time":51000,"type":0},{"id":0,"sLen":450,"time":51600,"type":0},{"id":3,"sLen":1800,"time":55200,"type":0},{"id":2,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60300,"type":0},{"id":0,"sLen":1200,"time":60600,"type":0},{"id":2,"sLen":0,"time":64500,"type":0},{"id":0,"sLen":0,"time":64800,"type":0},{"id":3,"sLen":0,"time":65100,"type":0},{"id":2,"sLen":0,"time":65700,"type":0},{"id":1,"sLen":600,"time":66000,"type":0},{"id":2,"sLen":0,"time":69600,"type":0},{"id":3,"sLen":0,"time":69900,"type":0},{"id":0,"sLen":0,"time":70200,"type":0},{"id":2,"sLen":0,"time":70500,"type":0},{"id":1,"sLen":600,"time":70800,"type":0},{"id":2,"sLen":0,"time":74100,"type":0},{"id":0,"sLen":0,"time":74400,"type":0},{"id":3,"sLen":0,"time":74700,"type":0},{"id":2,"sLen":0,"time":75300,"type":0},{"id":1,"sLen":600,"time":75600,"type":0}],"position":"boyfriend","type":1,"characters":["bf"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf"]}],"codenameChart":true,"scrollSpeed":1,"chartVersion":"1.6.0","stage":"stage","noteTypes":["No Anim Note"]} \ No newline at end of file diff --git a/assets/songs/bopeebo/events.json b/assets/songs/bopeebo/events.json new file mode 100644 index 00000000..f7fcd2cf --- /dev/null +++ b/assets/songs/bopeebo/events.json @@ -0,0 +1 @@ +{"events":[{"params":[1,"hey",true],"time":4200,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":9000},{"params":[1,"hey",true],"time":13800,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":18600},{"params":[1,"hey",true],"time":23400,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":28200},{"params":[1,"hey",true],"time":28500,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":33000},{"params":[1,"hey",true],"time":37800,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":42600},{"params":[1,"hey",true],"time":47400,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":52200},{"params":[1,"hey",true],"time":57000,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":61800},{"params":[1,"hey",true],"time":66600,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":66900},{"params":[1,"hey",true],"time":71400,"name":"Play Animation"},{"params":[1,"hey",true],"name":"Play Animation","time":76200}]} \ No newline at end of file diff --git a/assets/songs/bopeebo/scripts/hey.hx b/assets/songs/bopeebo/scripts/hey.hx deleted file mode 100644 index eb3a7d04..00000000 --- a/assets/songs/bopeebo/scripts/hey.hx +++ /dev/null @@ -1,4 +0,0 @@ -function beatHit(curBeat) { - if (curBeat != 79 && curBeat % 8 == 7) - boyfriend.playAnim("hey"); -} \ No newline at end of file diff --git a/assets/songs/cocoa/charts/easy.json b/assets/songs/cocoa/charts/easy.json index 19c14a3c..1d815f45 100644 --- a/assets/songs/cocoa/charts/easy.json +++ b/assets/songs/cocoa/charts/easy.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall","song":"Cocoa","bpm":100.0,"needsVoices":true,"player1":"bf-christmas","player2":"parents-christmas","speed":1.3,"notes":[{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[0.0,0,0.0],[600.0,2,0.0],[1350.0,1,0.0],[1650.0,3,0.0],[2100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[2400.0,0,0.0],[3000.0,2,0.0],[3750.0,1,0.0],[4050.0,3,0.0],[4500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[4800.0,0,0.0],[5400.0,2,0.0],[5700.0,0,0.0],[6300.0,2,0.0],[6600.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[7200.0,2,0.0],[7500.0,0,0.0],[8100.0,2,0.0],[8400.0,0,0.0],[9000.0,2,0.0],[9300.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[9600.0,0,0.0],[10200.0,2,0.0],[10950.0,1,0.0],[11250.0,3,0.0],[11700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12000.0,0,0.0],[12600.0,2,0.0],[13350.0,1,0.0],[13650.0,3,0.0],[14100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[14400.0,0,0.0],[15000.0,2,0.0],[15300.0,0,0.0],[15900.0,2,0.0],[16200.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16800.0,2,0.0],[17100.0,0,0.0],[17700.0,2,0.0],[18000.0,0,0.0],[18600.0,2,0.0],[18900.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19500.0,3,150.0],[19800.0,1,450.0],[20400.0,2,1050.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22500.0,1,0.0],[23100.0,1,0.0],[23400.0,3,0.0],[23700.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24000.0,0,0.0],[24600.0,2,0.0],[24900.0,0,0.0],[25500.0,2,0.0],[25800.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[26400.0,2,0.0],[26700.0,0,0.0],[27300.0,2,0.0],[27600.0,0,0.0],[28200.0,2,0.0],[28500.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28800.0,6,600.0],[29100.0,3,150.0],[29400.0,1,450.0],[30000.0,2,1050.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[32100.0,1,0.0],[32700.0,1,0.0],[33000.0,3,0.0],[33300.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[33600.0,0,0.0],[34200.0,2,0.0],[34500.0,0,0.0],[35100.0,2,0.0],[35400.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36000.0,2,0.0],[36300.0,0,0.0],[36900.0,2,0.0],[37200.0,0,0.0],[37800.0,2,0.0],[38100.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[39000.0,0,0.0],[39450.0,3,0.0],[40050.0,2,0.0],[40500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[40800.0,1,0.0],[41400.0,0,0.0],[41850.0,3,0.0],[42450.0,2,0.0],[42900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[43200.0,1,0.0],[43800.0,0,0.0],[44250.0,3,0.0],[44850.0,2,0.0],[45300.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45600.0,0,0.0],[46050.0,0,0.0],[46200.0,3,0.0],[46950.0,0,0.0],[47100.0,3,0.0],[47400.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[48600.0,0,0.0],[49050.0,3,0.0],[49650.0,2,0.0],[50100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[50400.0,1,0.0],[51000.0,0,0.0],[51450.0,3,0.0],[52050.0,2,0.0],[52500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[52800.0,1,0.0],[53400.0,0,0.0],[53850.0,3,0.0],[54450.0,2,0.0],[54900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[55200.0,0,0.0],[55650.0,0,0.0],[55800.0,3,0.0],[56550.0,0,0.0],[56700.0,3,0.0],[57000.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57750.0,0,0.0],[58200.0,2,450.0],[58800.0,3,450.0],[59400.0,2,150.0],[59700.0,0,750.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[60600.0,2,300.0],[61200.0,3,450.0],[61800.0,2,450.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62400.0,0,0.0],[63000.0,2,0.0],[63300.0,0,0.0],[63900.0,2,0.0],[64200.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[64800.0,2,0.0],[65100.0,0,0.0],[65700.0,2,0.0],[66000.0,0,0.0],[66600.0,2,0.0],[66900.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[67350.0,0,0.0],[67800.0,2,450.0],[68400.0,3,450.0],[69000.0,2,150.0],[69300.0,0,750.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[70200.0,2,300.0],[70800.0,3,450.0],[71400.0,2,450.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[72000.0,0,0.0],[72600.0,2,0.0],[72900.0,0,0.0],[73500.0,2,0.0],[73800.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[74400.0,2,0.0],[74700.0,0,0.0],[75300.0,2,0.0],[75600.0,0,0.0],[76200.0,2,0.0],[76500.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[76800.0,0,0.0],[77400.0,2,0.0],[78150.0,1,0.0],[78450.0,3,0.0],[78900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[79200.0,0,0.0],[79800.0,2,0.0],[80550.0,1,0.0],[80850.0,3,0.0],[81300.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[81600.0,0,0.0],[82200.0,2,0.0],[82950.0,1,0.0],[83250.0,3,0.0],[83700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[84000.0,0,0.0],[84600.0,2,0.0],[85350.0,1,0.0],[85650.0,3,0.0],[86100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[86400.0,0,0.0],[86700.0,3,0.0],[87300.0,3,0.0],[87600.0,0,0.0],[87900.0,3,0.0],[88200.0,3,0.0],[88500.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[88800.0,0,0.0],[89100.0,3,0.0],[89700.0,3,0.0],[90000.0,3,0.0],[90600.0,3,0.0],[90900.0,2,0.0],[91050.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[91200.0,0,0.0],[91500.0,3,0.0],[92100.0,3,0.0],[92400.0,0,0.0],[92700.0,3,0.0],[93000.0,3,0.0],[93300.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[93600.0,0,0.0],[93900.0,3,0.0],[94500.0,3,0.0],[94800.0,3,0.0],[95400.0,3,0.0],[95700.0,2,0.0],[95850.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[96000.0,0,0.0],[96300.0,3,0.0],[96900.0,3,0.0],[97200.0,0,0.0],[97500.0,3,0.0],[97800.0,3,0.0],[98100.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98400.0,0,0.0],[98700.0,3,0.0],[99300.0,3,0.0],[99600.0,3,0.0],[100200.0,3,0.0],[100500.0,2,0.0],[100650.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[100800.0,7,600.0],[100800.0,0,0.0],[101100.0,3,0.0],[101700.0,3,0.0],[102000.0,0,0.0],[102300.0,3,0.0],[102600.0,3,0.0],[102900.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[103200.0,0,0.0],[103500.0,3,0.0],[104100.0,3,0.0],[104400.0,3,0.0],[105000.0,3,0.0],[105300.0,2,0.0],[105450.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[105600.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":9600,"name":"Camera Movement"},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"time":28800,"name":"Camera Movement"},{"params":[0],"time":38400,"name":"Camera Movement"},{"params":[1],"time":48000,"name":"Camera Movement"},{"params":[0],"time":57600,"name":"Camera Movement"},{"params":[1],"time":67200,"name":"Camera Movement"},{"params":[0],"time":76800,"name":"Camera Movement"},{"params":[1],"time":81600,"name":"Camera Movement"},{"params":[0],"time":86400,"name":"Camera Movement"},{"params":[1],"time":91200,"name":"Camera Movement"},{"params":[0],"time":96000,"name":"Camera Movement"},{"params":[1],"time":100800,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":2,"sLen":0,"time":600,"type":0},{"id":1,"sLen":0,"time":1350,"type":0},{"id":3,"sLen":0,"time":1650,"type":0},{"id":0,"sLen":0,"time":2100,"type":0},{"id":0,"sLen":0,"time":2400,"type":0},{"id":2,"sLen":0,"time":3000,"type":0},{"id":1,"sLen":0,"time":3750,"type":0},{"id":3,"sLen":0,"time":4050,"type":0},{"id":0,"sLen":0,"time":4500,"type":0},{"id":0,"sLen":0,"time":4800,"type":0},{"id":2,"sLen":0,"time":5400,"type":0},{"id":0,"sLen":0,"time":5700,"type":0},{"id":2,"sLen":0,"time":6300,"type":0},{"id":0,"sLen":0,"time":6600,"type":0},{"id":2,"sLen":0,"time":7200,"type":0},{"id":0,"sLen":0,"time":7500,"type":0},{"id":2,"sLen":0,"time":8100,"type":0},{"id":0,"sLen":0,"time":8400,"type":0},{"id":2,"sLen":0,"time":9000,"type":0},{"id":1,"sLen":0,"time":9300,"type":0},{"id":3,"sLen":150,"time":19500,"type":0},{"id":1,"sLen":450,"time":19800,"type":0},{"id":2,"sLen":1050,"time":20400,"type":0},{"id":1,"sLen":0,"time":22500,"type":0},{"id":1,"sLen":0,"time":23100,"type":0},{"id":3,"sLen":0,"time":23400,"type":0},{"id":1,"sLen":0,"time":23700,"type":0},{"id":0,"sLen":0,"time":24000,"type":0},{"id":2,"sLen":0,"time":24600,"type":0},{"id":0,"sLen":0,"time":24900,"type":0},{"id":2,"sLen":0,"time":25500,"type":0},{"id":0,"sLen":0,"time":25800,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":0,"sLen":0,"time":26700,"type":0},{"id":2,"sLen":0,"time":27300,"type":0},{"id":0,"sLen":0,"time":27600,"type":0},{"id":2,"sLen":0,"time":28200,"type":0},{"id":1,"sLen":0,"time":28500,"type":0},{"id":2,"sLen":600,"time":28800,"type":0},{"id":0,"sLen":0,"time":39000,"type":0},{"id":3,"sLen":0,"time":39450,"type":0},{"id":2,"sLen":0,"time":40050,"type":0},{"id":0,"sLen":0,"time":40500,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":0,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":41850,"type":0},{"id":2,"sLen":0,"time":42450,"type":0},{"id":0,"sLen":0,"time":42900,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":0,"sLen":0,"time":43800,"type":0},{"id":3,"sLen":0,"time":44250,"type":0},{"id":2,"sLen":0,"time":44850,"type":0},{"id":0,"sLen":0,"time":45300,"type":0},{"id":0,"sLen":0,"time":45600,"type":0},{"id":0,"sLen":0,"time":46050,"type":0},{"id":3,"sLen":0,"time":46200,"type":0},{"id":0,"sLen":0,"time":46950,"type":0},{"id":3,"sLen":0,"time":47100,"type":0},{"id":0,"sLen":0,"time":47400,"type":0},{"id":0,"sLen":0,"time":57750,"type":0},{"id":2,"sLen":450,"time":58200,"type":0},{"id":3,"sLen":450,"time":58800,"type":0},{"id":2,"sLen":150,"time":59400,"type":0},{"id":0,"sLen":750,"time":59700,"type":0},{"id":2,"sLen":300,"time":60600,"type":0},{"id":3,"sLen":450,"time":61200,"type":0},{"id":2,"sLen":450,"time":61800,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":0,"sLen":0,"time":64200,"type":0},{"id":2,"sLen":0,"time":64800,"type":0},{"id":0,"sLen":0,"time":65100,"type":0},{"id":2,"sLen":0,"time":65700,"type":0},{"id":0,"sLen":0,"time":66000,"type":0},{"id":2,"sLen":0,"time":66600,"type":0},{"id":1,"sLen":0,"time":66900,"type":0},{"id":0,"sLen":0,"time":76800,"type":0},{"id":2,"sLen":0,"time":77400,"type":0},{"id":1,"sLen":0,"time":78150,"type":0},{"id":3,"sLen":0,"time":78450,"type":0},{"id":0,"sLen":0,"time":78900,"type":0},{"id":0,"sLen":0,"time":79200,"type":0},{"id":2,"sLen":0,"time":79800,"type":0},{"id":1,"sLen":0,"time":80550,"type":0},{"id":3,"sLen":0,"time":80850,"type":0},{"id":0,"sLen":0,"time":81300,"type":0},{"id":0,"sLen":0,"time":86400,"type":0},{"id":3,"sLen":0,"time":86700,"type":0},{"id":3,"sLen":0,"time":87300,"type":0},{"id":0,"sLen":0,"time":87600,"type":0},{"id":3,"sLen":0,"time":87900,"type":0},{"id":3,"sLen":0,"time":88200,"type":0},{"id":3,"sLen":0,"time":88500,"type":0},{"id":0,"sLen":0,"time":88800,"type":0},{"id":3,"sLen":0,"time":89100,"type":0},{"id":3,"sLen":0,"time":89700,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":3,"sLen":0,"time":90600,"type":0},{"id":2,"sLen":0,"time":90900,"type":0},{"id":0,"sLen":0,"time":91050,"type":0},{"id":0,"sLen":0,"time":96000,"type":0},{"id":3,"sLen":0,"time":96300,"type":0},{"id":3,"sLen":0,"time":96900,"type":0},{"id":0,"sLen":0,"time":97200,"type":0},{"id":3,"sLen":0,"time":97500,"type":0},{"id":3,"sLen":0,"time":97800,"type":0},{"id":3,"sLen":0,"time":98100,"type":0},{"id":0,"sLen":0,"time":98400,"type":0},{"id":3,"sLen":0,"time":98700,"type":0},{"id":3,"sLen":0,"time":99300,"type":0},{"id":3,"sLen":0,"time":99600,"type":0},{"id":3,"sLen":0,"time":100200,"type":0},{"id":2,"sLen":0,"time":100500,"type":0},{"id":0,"sLen":0,"time":100650,"type":0},{"id":3,"sLen":600,"time":100800,"type":0}],"position":"dad","type":0,"characters":["parents-christmas"]},{"visible":true,"keyCount":4,"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":2,"sLen":0,"time":10200,"type":0},{"id":1,"sLen":0,"time":10950,"type":0},{"id":3,"sLen":0,"time":11250,"type":0},{"id":0,"sLen":0,"time":11700,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":2,"sLen":0,"time":12600,"type":0},{"id":1,"sLen":0,"time":13350,"type":0},{"id":3,"sLen":0,"time":13650,"type":0},{"id":0,"sLen":0,"time":14100,"type":0},{"id":0,"sLen":0,"time":14400,"type":0},{"id":2,"sLen":0,"time":15000,"type":0},{"id":0,"sLen":0,"time":15300,"type":0},{"id":2,"sLen":0,"time":15900,"type":0},{"id":0,"sLen":0,"time":16200,"type":0},{"id":2,"sLen":0,"time":16800,"type":0},{"id":0,"sLen":0,"time":17100,"type":0},{"id":2,"sLen":0,"time":17700,"type":0},{"id":0,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":18600,"type":0},{"id":1,"sLen":0,"time":18900,"type":0},{"id":3,"sLen":150,"time":29100,"type":0},{"id":1,"sLen":450,"time":29400,"type":0},{"id":2,"sLen":1050,"time":30000,"type":0},{"id":1,"sLen":0,"time":32100,"type":0},{"id":1,"sLen":0,"time":32700,"type":0},{"id":3,"sLen":0,"time":33000,"type":0},{"id":1,"sLen":0,"time":33300,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34200,"type":0},{"id":0,"sLen":0,"time":34500,"type":0},{"id":2,"sLen":0,"time":35100,"type":0},{"id":0,"sLen":0,"time":35400,"type":0},{"id":2,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36300,"type":0},{"id":2,"sLen":0,"time":36900,"type":0},{"id":0,"sLen":0,"time":37200,"type":0},{"id":2,"sLen":0,"time":37800,"type":0},{"id":1,"sLen":0,"time":38100,"type":0},{"id":0,"sLen":0,"time":48600,"type":0},{"id":3,"sLen":0,"time":49050,"type":0},{"id":2,"sLen":0,"time":49650,"type":0},{"id":0,"sLen":0,"time":50100,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":0,"sLen":0,"time":51000,"type":0},{"id":3,"sLen":0,"time":51450,"type":0},{"id":2,"sLen":0,"time":52050,"type":0},{"id":0,"sLen":0,"time":52500,"type":0},{"id":1,"sLen":0,"time":52800,"type":0},{"id":0,"sLen":0,"time":53400,"type":0},{"id":3,"sLen":0,"time":53850,"type":0},{"id":2,"sLen":0,"time":54450,"type":0},{"id":0,"sLen":0,"time":54900,"type":0},{"id":0,"sLen":0,"time":55200,"type":0},{"id":0,"sLen":0,"time":55650,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56550,"type":0},{"id":3,"sLen":0,"time":56700,"type":0},{"id":0,"sLen":0,"time":57000,"type":0},{"id":0,"sLen":0,"time":67350,"type":0},{"id":2,"sLen":450,"time":67800,"type":0},{"id":3,"sLen":450,"time":68400,"type":0},{"id":2,"sLen":150,"time":69000,"type":0},{"id":0,"sLen":750,"time":69300,"type":0},{"id":2,"sLen":300,"time":70200,"type":0},{"id":3,"sLen":450,"time":70800,"type":0},{"id":2,"sLen":450,"time":71400,"type":0},{"id":0,"sLen":0,"time":72000,"type":0},{"id":2,"sLen":0,"time":72600,"type":0},{"id":0,"sLen":0,"time":72900,"type":0},{"id":2,"sLen":0,"time":73500,"type":0},{"id":0,"sLen":0,"time":73800,"type":0},{"id":2,"sLen":0,"time":74400,"type":0},{"id":0,"sLen":0,"time":74700,"type":0},{"id":2,"sLen":0,"time":75300,"type":0},{"id":0,"sLen":0,"time":75600,"type":0},{"id":2,"sLen":0,"time":76200,"type":0},{"id":1,"sLen":0,"time":76500,"type":0},{"id":0,"sLen":0,"time":81600,"type":0},{"id":2,"sLen":0,"time":82200,"type":0},{"id":1,"sLen":0,"time":82950,"type":0},{"id":3,"sLen":0,"time":83250,"type":0},{"id":0,"sLen":0,"time":83700,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":2,"sLen":0,"time":84600,"type":0},{"id":1,"sLen":0,"time":85350,"type":0},{"id":3,"sLen":0,"time":85650,"type":0},{"id":0,"sLen":0,"time":86100,"type":0},{"id":0,"sLen":0,"time":91200,"type":0},{"id":3,"sLen":0,"time":91500,"type":0},{"id":3,"sLen":0,"time":92100,"type":0},{"id":0,"sLen":0,"time":92400,"type":0},{"id":3,"sLen":0,"time":92700,"type":0},{"id":3,"sLen":0,"time":93000,"type":0},{"id":3,"sLen":0,"time":93300,"type":0},{"id":0,"sLen":0,"time":93600,"type":0},{"id":3,"sLen":0,"time":93900,"type":0},{"id":3,"sLen":0,"time":94500,"type":0},{"id":3,"sLen":0,"time":94800,"type":0},{"id":3,"sLen":0,"time":95400,"type":0},{"id":2,"sLen":0,"time":95700,"type":0},{"id":0,"sLen":0,"time":95850,"type":0},{"id":0,"sLen":0,"time":100800,"type":0},{"id":3,"sLen":0,"time":101100,"type":0},{"id":3,"sLen":0,"time":101700,"type":0},{"id":0,"sLen":0,"time":102000,"type":0},{"id":3,"sLen":0,"time":102300,"type":0},{"id":3,"sLen":0,"time":102600,"type":0},{"id":3,"sLen":0,"time":102900,"type":0},{"id":0,"sLen":0,"time":103200,"type":0},{"id":3,"sLen":0,"time":103500,"type":0},{"id":3,"sLen":0,"time":104100,"type":0},{"id":3,"sLen":0,"time":104400,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":2,"sLen":0,"time":105300,"type":0},{"id":0,"sLen":0,"time":105450,"type":0},{"id":3,"sLen":600,"time":105600,"type":0}],"position":"boyfriend","type":1,"characters":["bf-christmas"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf-christmas"]}],"scrollSpeed":1.3,"chartVersion":"1.6.0","stage":"mall","codenameChart":true,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/cocoa/charts/hard.json b/assets/songs/cocoa/charts/hard.json index 2f20d63f..c085a578 100644 --- a/assets/songs/cocoa/charts/hard.json +++ b/assets/songs/cocoa/charts/hard.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall","song":"Cocoa","bpm":100.0,"needsVoices":true,"player1":"bf-christmas","player2":"parents-christmas","speed":1.5,"notes":[{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[0.0,0,0.0],[600.0,2,0.0],[900.0,0,0.0],[1350.0,1,0.0],[1650.0,3,0.0],[1950.0,1,0.0],[2100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[2400.0,0,0.0],[3000.0,2,0.0],[3300.0,0,0.0],[3750.0,1,0.0],[4050.0,3,0.0],[4350.0,1,0.0],[4500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[4800.0,0,0.0],[4950.0,3,0.0],[5100.0,0,0.0],[5250.0,3,0.0],[5400.0,2,0.0],[5700.0,0,0.0],[5850.0,3,0.0],[6000.0,0,0.0],[6150.0,3,0.0],[6300.0,2,0.0],[6600.0,0,0.0],[6750.0,3,0.0],[6900.0,0,0.0],[7050.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[7200.0,2,0.0],[7500.0,0,0.0],[7650.0,3,0.0],[7800.0,0,0.0],[7950.0,3,0.0],[8100.0,2,0.0],[8400.0,0,0.0],[8550.0,3,0.0],[8700.0,0,0.0],[8850.0,3,0.0],[9000.0,2,0.0],[9300.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[9600.0,0,0.0],[10200.0,2,0.0],[10500.0,0,0.0],[10950.0,1,0.0],[11250.0,3,0.0],[11550.0,1,0.0],[11700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12000.0,0,0.0],[12600.0,2,0.0],[12900.0,0,0.0],[13350.0,1,0.0],[13650.0,3,0.0],[13950.0,1,0.0],[14100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[14400.0,0,0.0],[14550.0,3,0.0],[14700.0,0,0.0],[14850.0,3,0.0],[15000.0,2,0.0],[15300.0,0,0.0],[15450.0,3,0.0],[15600.0,0,0.0],[15750.0,3,0.0],[15900.0,2,0.0],[16200.0,0,0.0],[16350.0,3,0.0],[16500.0,0,0.0],[16650.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16800.0,2,0.0],[17100.0,0,0.0],[17250.0,3,0.0],[17400.0,0,0.0],[17550.0,3,0.0],[17700.0,2,0.0],[18000.0,0,0.0],[18150.0,3,0.0],[18300.0,0,0.0],[18450.0,3,0.0],[18600.0,2,0.0],[18900.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19500.0,3,150.0],[19800.0,1,450.0],[20400.0,2,1050.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22500.0,1,0.0],[22650.0,3,0.0],[22800.0,0,150.0],[23100.0,1,0.0],[23400.0,3,0.0],[23700.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24000.0,0,0.0],[24150.0,3,0.0],[24300.0,0,0.0],[24450.0,3,0.0],[24600.0,2,0.0],[24900.0,0,0.0],[25050.0,3,0.0],[25200.0,0,0.0],[25350.0,3,0.0],[25500.0,2,0.0],[25800.0,0,0.0],[25950.0,3,0.0],[26100.0,0,0.0],[26250.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[26400.0,2,0.0],[26700.0,0,0.0],[26850.0,3,0.0],[27000.0,0,0.0],[27150.0,3,0.0],[27300.0,2,0.0],[27600.0,0,0.0],[27750.0,3,0.0],[27900.0,0,0.0],[28050.0,3,0.0],[28200.0,2,0.0],[28500.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28800.0,6,600.0],[29100.0,3,150.0],[29400.0,1,450.0],[30000.0,2,1050.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[32100.0,1,0.0],[32250.0,3,0.0],[32400.0,0,150.0],[32700.0,1,0.0],[33000.0,3,0.0],[33300.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[33600.0,0,0.0],[33750.0,3,0.0],[33900.0,0,0.0],[34050.0,3,0.0],[34200.0,2,0.0],[34500.0,0,0.0],[34650.0,3,0.0],[34800.0,0,0.0],[34950.0,3,0.0],[35100.0,2,0.0],[35400.0,0,0.0],[35550.0,3,0.0],[35700.0,0,0.0],[35850.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36000.0,2,0.0],[36300.0,0,0.0],[36450.0,3,0.0],[36600.0,0,0.0],[36750.0,3,0.0],[36900.0,2,0.0],[37200.0,0,0.0],[37350.0,3,0.0],[37500.0,0,0.0],[37650.0,3,0.0],[37800.0,2,0.0],[38100.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[39000.0,0,0.0],[39150.0,1,0.0],[39450.0,3,0.0],[39750.0,3,0.0],[40050.0,2,0.0],[40200.0,3,0.0],[40500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[40800.0,1,0.0],[41400.0,0,0.0],[41550.0,1,0.0],[41850.0,3,0.0],[42150.0,3,0.0],[42450.0,2,0.0],[42600.0,3,0.0],[42900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[43200.0,1,0.0],[43800.0,0,0.0],[43950.0,1,0.0],[44250.0,3,0.0],[44550.0,3,0.0],[44850.0,2,0.0],[45000.0,3,0.0],[45300.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45600.0,0,0.0],[45750.0,3,0.0],[46050.0,0,0.0],[46200.0,3,0.0],[46500.0,0,0.0],[46650.0,3,0.0],[46950.0,0,0.0],[47100.0,3,0.0],[47400.0,0,0.0],[47550.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[48600.0,0,0.0],[48750.0,1,0.0],[49050.0,3,0.0],[49350.0,3,0.0],[49650.0,2,0.0],[49800.0,3,0.0],[50100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[50400.0,1,0.0],[51000.0,0,0.0],[51150.0,1,0.0],[51450.0,3,0.0],[51750.0,3,0.0],[52050.0,2,0.0],[52200.0,3,0.0],[52500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[52800.0,1,0.0],[53400.0,0,0.0],[53550.0,1,0.0],[53850.0,3,0.0],[54150.0,3,0.0],[54450.0,2,0.0],[54600.0,3,0.0],[54900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[55200.0,0,0.0],[55350.0,3,0.0],[55650.0,0,0.0],[55800.0,3,0.0],[56100.0,0,0.0],[56250.0,3,0.0],[56550.0,0,0.0],[56700.0,3,0.0],[57000.0,0,0.0],[57150.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57750.0,0,0.0],[57900.0,1,0.0],[58050.0,0,0.0],[58200.0,2,450.0],[58800.0,3,450.0],[59400.0,2,150.0],[59700.0,0,750.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[60600.0,2,300.0],[61200.0,3,450.0],[61800.0,2,450.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62400.0,0,0.0],[62550.0,3,0.0],[62700.0,0,0.0],[62850.0,3,0.0],[63000.0,2,0.0],[63300.0,0,0.0],[63450.0,3,0.0],[63600.0,0,0.0],[63750.0,3,0.0],[63900.0,2,0.0],[64200.0,0,0.0],[64350.0,3,0.0],[64500.0,0,0.0],[64650.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[64800.0,2,0.0],[65100.0,0,0.0],[65250.0,3,0.0],[65400.0,0,0.0],[65550.0,3,0.0],[65700.0,2,0.0],[66000.0,0,0.0],[66150.0,3,0.0],[66300.0,0,0.0],[66450.0,3,0.0],[66600.0,2,0.0],[66900.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[67350.0,0,0.0],[67500.0,1,0.0],[67650.0,0,0.0],[67800.0,2,450.0],[68400.0,3,450.0],[69000.0,2,150.0],[69300.0,0,750.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[70200.0,2,300.0],[70800.0,3,450.0],[71400.0,2,450.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[72000.0,0,0.0],[72150.0,3,0.0],[72300.0,0,0.0],[72450.0,3,0.0],[72600.0,2,0.0],[72900.0,0,0.0],[73050.0,3,0.0],[73200.0,0,0.0],[73350.0,3,0.0],[73500.0,2,0.0],[73800.0,0,0.0],[73950.0,3,0.0],[74100.0,0,0.0],[74250.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[74400.0,2,0.0],[74700.0,0,0.0],[74850.0,3,0.0],[75000.0,0,0.0],[75150.0,3,0.0],[75300.0,2,0.0],[75600.0,0,0.0],[75750.0,3,0.0],[75900.0,0,0.0],[76050.0,3,0.0],[76200.0,2,0.0],[76500.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[76800.0,0,0.0],[77400.0,2,0.0],[77700.0,0,0.0],[78150.0,1,0.0],[78450.0,3,0.0],[78750.0,1,0.0],[78900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[79200.0,0,0.0],[79800.0,2,0.0],[80100.0,0,0.0],[80550.0,1,0.0],[80850.0,3,0.0],[81150.0,1,0.0],[81300.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[81600.0,0,0.0],[82200.0,2,0.0],[82500.0,0,0.0],[82950.0,1,0.0],[83250.0,3,0.0],[83550.0,1,0.0],[83700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[84000.0,0,0.0],[84600.0,2,0.0],[84900.0,0,0.0],[85350.0,1,0.0],[85650.0,3,0.0],[85950.0,1,0.0],[86100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[86400.0,0,0.0],[86550.0,1,0.0],[86700.0,3,0.0],[86850.0,1,0.0],[87000.0,0,0.0],[87150.0,1,0.0],[87300.0,3,0.0],[87450.0,2,0.0],[87600.0,0,0.0],[87750.0,1,0.0],[87900.0,3,0.0],[88050.0,1,0.0],[88200.0,0,0.0],[88350.0,1,0.0],[88500.0,3,0.0],[88650.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[88800.0,0,0.0],[88950.0,1,0.0],[89100.0,3,0.0],[89250.0,1,0.0],[89400.0,0,0.0],[89550.0,1,0.0],[89700.0,3,0.0],[89850.0,2,0.0],[90000.0,3,0.0],[90150.0,0,0.0],[90300.0,3,0.0],[90450.0,1,0.0],[90600.0,3,0.0],[90750.0,1,0.0],[90900.0,2,0.0],[91050.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[91200.0,0,0.0],[91350.0,1,0.0],[91500.0,3,0.0],[91650.0,1,0.0],[91800.0,0,0.0],[91950.0,1,0.0],[92100.0,3,0.0],[92250.0,2,0.0],[92400.0,0,0.0],[92550.0,1,0.0],[92700.0,3,0.0],[92850.0,1,0.0],[93000.0,0,0.0],[93150.0,1,0.0],[93300.0,3,0.0],[93450.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[93600.0,0,0.0],[93750.0,1,0.0],[93900.0,3,0.0],[94050.0,1,0.0],[94200.0,0,0.0],[94350.0,1,0.0],[94500.0,3,0.0],[94650.0,2,0.0],[94800.0,3,0.0],[94950.0,0,0.0],[95100.0,3,0.0],[95250.0,1,0.0],[95400.0,3,0.0],[95550.0,1,0.0],[95700.0,2,0.0],[95850.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[96000.0,0,0.0],[96150.0,1,0.0],[96300.0,3,0.0],[96450.0,1,0.0],[96600.0,0,0.0],[96750.0,1,0.0],[96900.0,3,0.0],[97050.0,2,0.0],[97200.0,0,0.0],[97350.0,1,0.0],[97500.0,3,0.0],[97650.0,1,0.0],[97800.0,0,0.0],[97950.0,1,0.0],[98100.0,3,0.0],[98250.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98400.0,0,0.0],[98550.0,1,0.0],[98700.0,3,0.0],[98850.0,1,0.0],[99000.0,0,0.0],[99150.0,1,0.0],[99300.0,3,0.0],[99450.0,2,0.0],[99600.0,3,0.0],[99750.0,0,0.0],[99900.0,3,0.0],[100050.0,1,0.0],[100200.0,3,0.0],[100350.0,1,0.0],[100500.0,2,0.0],[100650.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[100800.0,7,600.0],[100800.0,0,0.0],[100950.0,1,0.0],[101100.0,3,0.0],[101250.0,1,0.0],[101400.0,0,0.0],[101550.0,1,0.0],[101700.0,3,0.0],[101850.0,2,0.0],[102000.0,0,0.0],[102150.0,1,0.0],[102300.0,3,0.0],[102450.0,1,0.0],[102600.0,0,0.0],[102750.0,1,0.0],[102900.0,3,0.0],[103050.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[103200.0,0,0.0],[103350.0,1,0.0],[103500.0,3,0.0],[103650.0,1,0.0],[103800.0,0,0.0],[103950.0,1,0.0],[104100.0,3,0.0],[104250.0,2,0.0],[104400.0,3,0.0],[104550.0,0,0.0],[104700.0,3,0.0],[104850.0,1,0.0],[105000.0,3,0.0],[105150.0,1,0.0],[105300.0,2,0.0],[105450.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[105600.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":9600,"name":"Camera Movement"},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"time":28800,"name":"Camera Movement"},{"params":[0],"time":38400,"name":"Camera Movement"},{"params":[1],"time":48000,"name":"Camera Movement"},{"params":[0],"time":57600,"name":"Camera Movement"},{"params":[1],"time":67200,"name":"Camera Movement"},{"params":[0],"time":76800,"name":"Camera Movement"},{"params":[1],"time":81600,"name":"Camera Movement"},{"params":[0],"time":86400,"name":"Camera Movement"},{"params":[1],"time":91200,"name":"Camera Movement"},{"params":[0],"time":96000,"name":"Camera Movement"},{"params":[1],"time":100800,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":2,"sLen":0,"time":600,"type":0},{"id":0,"sLen":0,"time":900,"type":0},{"id":1,"sLen":0,"time":1350,"type":0},{"id":3,"sLen":0,"time":1650,"type":0},{"id":1,"sLen":0,"time":1950,"type":0},{"id":0,"sLen":0,"time":2100,"type":0},{"id":0,"sLen":0,"time":2400,"type":0},{"id":2,"sLen":0,"time":3000,"type":0},{"id":0,"sLen":0,"time":3300,"type":0},{"id":1,"sLen":0,"time":3750,"type":0},{"id":3,"sLen":0,"time":4050,"type":0},{"id":1,"sLen":0,"time":4350,"type":0},{"id":0,"sLen":0,"time":4500,"type":0},{"id":0,"sLen":0,"time":4800,"type":0},{"id":3,"sLen":0,"time":4950,"type":0},{"id":0,"sLen":0,"time":5100,"type":0},{"id":3,"sLen":0,"time":5250,"type":0},{"id":2,"sLen":0,"time":5400,"type":0},{"id":0,"sLen":0,"time":5700,"type":0},{"id":3,"sLen":0,"time":5850,"type":0},{"id":0,"sLen":0,"time":6000,"type":0},{"id":3,"sLen":0,"time":6150,"type":0},{"id":2,"sLen":0,"time":6300,"type":0},{"id":0,"sLen":0,"time":6600,"type":0},{"id":3,"sLen":0,"time":6750,"type":0},{"id":0,"sLen":0,"time":6900,"type":0},{"id":3,"sLen":0,"time":7050,"type":0},{"id":2,"sLen":0,"time":7200,"type":0},{"id":0,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7650,"type":0},{"id":0,"sLen":0,"time":7800,"type":0},{"id":3,"sLen":0,"time":7950,"type":0},{"id":2,"sLen":0,"time":8100,"type":0},{"id":0,"sLen":0,"time":8400,"type":0},{"id":3,"sLen":0,"time":8550,"type":0},{"id":0,"sLen":0,"time":8700,"type":0},{"id":3,"sLen":0,"time":8850,"type":0},{"id":2,"sLen":0,"time":9000,"type":0},{"id":1,"sLen":0,"time":9300,"type":0},{"id":3,"sLen":150,"time":19500,"type":0},{"id":1,"sLen":450,"time":19800,"type":0},{"id":2,"sLen":1050,"time":20400,"type":0},{"id":1,"sLen":0,"time":22500,"type":0},{"id":3,"sLen":0,"time":22650,"type":0},{"id":0,"sLen":150,"time":22800,"type":0},{"id":1,"sLen":0,"time":23100,"type":0},{"id":3,"sLen":0,"time":23400,"type":0},{"id":1,"sLen":0,"time":23700,"type":0},{"id":0,"sLen":0,"time":24000,"type":0},{"id":3,"sLen":0,"time":24150,"type":0},{"id":0,"sLen":0,"time":24300,"type":0},{"id":3,"sLen":0,"time":24450,"type":0},{"id":2,"sLen":0,"time":24600,"type":0},{"id":0,"sLen":0,"time":24900,"type":0},{"id":3,"sLen":0,"time":25050,"type":0},{"id":0,"sLen":0,"time":25200,"type":0},{"id":3,"sLen":0,"time":25350,"type":0},{"id":2,"sLen":0,"time":25500,"type":0},{"id":0,"sLen":0,"time":25800,"type":0},{"id":3,"sLen":0,"time":25950,"type":0},{"id":0,"sLen":0,"time":26100,"type":0},{"id":3,"sLen":0,"time":26250,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":0,"sLen":0,"time":26700,"type":0},{"id":3,"sLen":0,"time":26850,"type":0},{"id":0,"sLen":0,"time":27000,"type":0},{"id":3,"sLen":0,"time":27150,"type":0},{"id":2,"sLen":0,"time":27300,"type":0},{"id":0,"sLen":0,"time":27600,"type":0},{"id":3,"sLen":0,"time":27750,"type":0},{"id":0,"sLen":0,"time":27900,"type":0},{"id":3,"sLen":0,"time":28050,"type":0},{"id":2,"sLen":0,"time":28200,"type":0},{"id":1,"sLen":0,"time":28500,"type":0},{"id":2,"sLen":600,"time":28800,"type":0},{"id":0,"sLen":0,"time":39000,"type":0},{"id":1,"sLen":0,"time":39150,"type":0},{"id":3,"sLen":0,"time":39450,"type":0},{"id":3,"sLen":0,"time":39750,"type":0},{"id":2,"sLen":0,"time":40050,"type":0},{"id":3,"sLen":0,"time":40200,"type":0},{"id":0,"sLen":0,"time":40500,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":0,"sLen":0,"time":41400,"type":0},{"id":1,"sLen":0,"time":41550,"type":0},{"id":3,"sLen":0,"time":41850,"type":0},{"id":3,"sLen":0,"time":42150,"type":0},{"id":2,"sLen":0,"time":42450,"type":0},{"id":3,"sLen":0,"time":42600,"type":0},{"id":0,"sLen":0,"time":42900,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":0,"sLen":0,"time":43800,"type":0},{"id":1,"sLen":0,"time":43950,"type":0},{"id":3,"sLen":0,"time":44250,"type":0},{"id":3,"sLen":0,"time":44550,"type":0},{"id":2,"sLen":0,"time":44850,"type":0},{"id":3,"sLen":0,"time":45000,"type":0},{"id":0,"sLen":0,"time":45300,"type":0},{"id":0,"sLen":0,"time":45600,"type":0},{"id":3,"sLen":0,"time":45750,"type":0},{"id":0,"sLen":0,"time":46050,"type":0},{"id":3,"sLen":0,"time":46200,"type":0},{"id":0,"sLen":0,"time":46500,"type":0},{"id":3,"sLen":0,"time":46650,"type":0},{"id":0,"sLen":0,"time":46950,"type":0},{"id":3,"sLen":0,"time":47100,"type":0},{"id":0,"sLen":0,"time":47400,"type":0},{"id":3,"sLen":0,"time":47550,"type":0},{"id":0,"sLen":0,"time":57750,"type":0},{"id":1,"sLen":0,"time":57900,"type":0},{"id":0,"sLen":0,"time":58050,"type":0},{"id":2,"sLen":450,"time":58200,"type":0},{"id":3,"sLen":450,"time":58800,"type":0},{"id":2,"sLen":150,"time":59400,"type":0},{"id":0,"sLen":750,"time":59700,"type":0},{"id":2,"sLen":300,"time":60600,"type":0},{"id":3,"sLen":450,"time":61200,"type":0},{"id":2,"sLen":450,"time":61800,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":3,"sLen":0,"time":62550,"type":0},{"id":0,"sLen":0,"time":62700,"type":0},{"id":3,"sLen":0,"time":62850,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":3,"sLen":0,"time":63450,"type":0},{"id":0,"sLen":0,"time":63600,"type":0},{"id":3,"sLen":0,"time":63750,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":0,"sLen":0,"time":64200,"type":0},{"id":3,"sLen":0,"time":64350,"type":0},{"id":0,"sLen":0,"time":64500,"type":0},{"id":3,"sLen":0,"time":64650,"type":0},{"id":2,"sLen":0,"time":64800,"type":0},{"id":0,"sLen":0,"time":65100,"type":0},{"id":3,"sLen":0,"time":65250,"type":0},{"id":0,"sLen":0,"time":65400,"type":0},{"id":3,"sLen":0,"time":65550,"type":0},{"id":2,"sLen":0,"time":65700,"type":0},{"id":0,"sLen":0,"time":66000,"type":0},{"id":3,"sLen":0,"time":66150,"type":0},{"id":0,"sLen":0,"time":66300,"type":0},{"id":3,"sLen":0,"time":66450,"type":0},{"id":2,"sLen":0,"time":66600,"type":0},{"id":1,"sLen":0,"time":66900,"type":0},{"id":0,"sLen":0,"time":76800,"type":0},{"id":2,"sLen":0,"time":77400,"type":0},{"id":0,"sLen":0,"time":77700,"type":0},{"id":1,"sLen":0,"time":78150,"type":0},{"id":3,"sLen":0,"time":78450,"type":0},{"id":1,"sLen":0,"time":78750,"type":0},{"id":0,"sLen":0,"time":78900,"type":0},{"id":0,"sLen":0,"time":79200,"type":0},{"id":2,"sLen":0,"time":79800,"type":0},{"id":0,"sLen":0,"time":80100,"type":0},{"id":1,"sLen":0,"time":80550,"type":0},{"id":3,"sLen":0,"time":80850,"type":0},{"id":1,"sLen":0,"time":81150,"type":0},{"id":0,"sLen":0,"time":81300,"type":0},{"id":0,"sLen":0,"time":86400,"type":0},{"id":1,"sLen":0,"time":86550,"type":0},{"id":3,"sLen":0,"time":86700,"type":0},{"id":1,"sLen":0,"time":86850,"type":0},{"id":0,"sLen":0,"time":87000,"type":0},{"id":1,"sLen":0,"time":87150,"type":0},{"id":3,"sLen":0,"time":87300,"type":0},{"id":2,"sLen":0,"time":87450,"type":0},{"id":0,"sLen":0,"time":87600,"type":0},{"id":1,"sLen":0,"time":87750,"type":0},{"id":3,"sLen":0,"time":87900,"type":0},{"id":1,"sLen":0,"time":88050,"type":0},{"id":0,"sLen":0,"time":88200,"type":0},{"id":1,"sLen":0,"time":88350,"type":0},{"id":3,"sLen":0,"time":88500,"type":0},{"id":2,"sLen":0,"time":88650,"type":0},{"id":0,"sLen":0,"time":88800,"type":0},{"id":1,"sLen":0,"time":88950,"type":0},{"id":3,"sLen":0,"time":89100,"type":0},{"id":1,"sLen":0,"time":89250,"type":0},{"id":0,"sLen":0,"time":89400,"type":0},{"id":1,"sLen":0,"time":89550,"type":0},{"id":3,"sLen":0,"time":89700,"type":0},{"id":2,"sLen":0,"time":89850,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90150,"type":0},{"id":3,"sLen":0,"time":90300,"type":0},{"id":1,"sLen":0,"time":90450,"type":0},{"id":3,"sLen":0,"time":90600,"type":0},{"id":1,"sLen":0,"time":90750,"type":0},{"id":2,"sLen":0,"time":90900,"type":0},{"id":0,"sLen":0,"time":91050,"type":0},{"id":0,"sLen":0,"time":96000,"type":0},{"id":1,"sLen":0,"time":96150,"type":0},{"id":3,"sLen":0,"time":96300,"type":0},{"id":1,"sLen":0,"time":96450,"type":0},{"id":0,"sLen":0,"time":96600,"type":0},{"id":1,"sLen":0,"time":96750,"type":0},{"id":3,"sLen":0,"time":96900,"type":0},{"id":2,"sLen":0,"time":97050,"type":0},{"id":0,"sLen":0,"time":97200,"type":0},{"id":1,"sLen":0,"time":97350,"type":0},{"id":3,"sLen":0,"time":97500,"type":0},{"id":1,"sLen":0,"time":97650,"type":0},{"id":0,"sLen":0,"time":97800,"type":0},{"id":1,"sLen":0,"time":97950,"type":0},{"id":3,"sLen":0,"time":98100,"type":0},{"id":2,"sLen":0,"time":98250,"type":0},{"id":0,"sLen":0,"time":98400,"type":0},{"id":1,"sLen":0,"time":98550,"type":0},{"id":3,"sLen":0,"time":98700,"type":0},{"id":1,"sLen":0,"time":98850,"type":0},{"id":0,"sLen":0,"time":99000,"type":0},{"id":1,"sLen":0,"time":99150,"type":0},{"id":3,"sLen":0,"time":99300,"type":0},{"id":2,"sLen":0,"time":99450,"type":0},{"id":3,"sLen":0,"time":99600,"type":0},{"id":0,"sLen":0,"time":99750,"type":0},{"id":3,"sLen":0,"time":99900,"type":0},{"id":1,"sLen":0,"time":100050,"type":0},{"id":3,"sLen":0,"time":100200,"type":0},{"id":1,"sLen":0,"time":100350,"type":0},{"id":2,"sLen":0,"time":100500,"type":0},{"id":0,"sLen":0,"time":100650,"type":0},{"id":3,"sLen":600,"time":100800,"type":0}],"position":"dad","type":0,"characters":["parents-christmas"]},{"visible":true,"keyCount":4,"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":2,"sLen":0,"time":10200,"type":0},{"id":0,"sLen":0,"time":10500,"type":0},{"id":1,"sLen":0,"time":10950,"type":0},{"id":3,"sLen":0,"time":11250,"type":0},{"id":1,"sLen":0,"time":11550,"type":0},{"id":0,"sLen":0,"time":11700,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":2,"sLen":0,"time":12600,"type":0},{"id":0,"sLen":0,"time":12900,"type":0},{"id":1,"sLen":0,"time":13350,"type":0},{"id":3,"sLen":0,"time":13650,"type":0},{"id":1,"sLen":0,"time":13950,"type":0},{"id":0,"sLen":0,"time":14100,"type":0},{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":14550,"type":0},{"id":0,"sLen":0,"time":14700,"type":0},{"id":3,"sLen":0,"time":14850,"type":0},{"id":2,"sLen":0,"time":15000,"type":0},{"id":0,"sLen":0,"time":15300,"type":0},{"id":3,"sLen":0,"time":15450,"type":0},{"id":0,"sLen":0,"time":15600,"type":0},{"id":3,"sLen":0,"time":15750,"type":0},{"id":2,"sLen":0,"time":15900,"type":0},{"id":0,"sLen":0,"time":16200,"type":0},{"id":3,"sLen":0,"time":16350,"type":0},{"id":0,"sLen":0,"time":16500,"type":0},{"id":3,"sLen":0,"time":16650,"type":0},{"id":2,"sLen":0,"time":16800,"type":0},{"id":0,"sLen":0,"time":17100,"type":0},{"id":3,"sLen":0,"time":17250,"type":0},{"id":0,"sLen":0,"time":17400,"type":0},{"id":3,"sLen":0,"time":17550,"type":0},{"id":2,"sLen":0,"time":17700,"type":0},{"id":0,"sLen":0,"time":18000,"type":0},{"id":3,"sLen":0,"time":18150,"type":0},{"id":0,"sLen":0,"time":18300,"type":0},{"id":3,"sLen":0,"time":18450,"type":0},{"id":2,"sLen":0,"time":18600,"type":0},{"id":1,"sLen":0,"time":18900,"type":0},{"id":3,"sLen":150,"time":29100,"type":0},{"id":1,"sLen":450,"time":29400,"type":0},{"id":2,"sLen":1050,"time":30000,"type":0},{"id":1,"sLen":0,"time":32100,"type":0},{"id":3,"sLen":0,"time":32250,"type":0},{"id":0,"sLen":150,"time":32400,"type":0},{"id":1,"sLen":0,"time":32700,"type":0},{"id":3,"sLen":0,"time":33000,"type":0},{"id":1,"sLen":0,"time":33300,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":3,"sLen":0,"time":33750,"type":0},{"id":0,"sLen":0,"time":33900,"type":0},{"id":3,"sLen":0,"time":34050,"type":0},{"id":2,"sLen":0,"time":34200,"type":0},{"id":0,"sLen":0,"time":34500,"type":0},{"id":3,"sLen":0,"time":34650,"type":0},{"id":0,"sLen":0,"time":34800,"type":0},{"id":3,"sLen":0,"time":34950,"type":0},{"id":2,"sLen":0,"time":35100,"type":0},{"id":0,"sLen":0,"time":35400,"type":0},{"id":3,"sLen":0,"time":35550,"type":0},{"id":0,"sLen":0,"time":35700,"type":0},{"id":3,"sLen":0,"time":35850,"type":0},{"id":2,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36300,"type":0},{"id":3,"sLen":0,"time":36450,"type":0},{"id":0,"sLen":0,"time":36600,"type":0},{"id":3,"sLen":0,"time":36750,"type":0},{"id":2,"sLen":0,"time":36900,"type":0},{"id":0,"sLen":0,"time":37200,"type":0},{"id":3,"sLen":0,"time":37350,"type":0},{"id":0,"sLen":0,"time":37500,"type":0},{"id":3,"sLen":0,"time":37650,"type":0},{"id":2,"sLen":0,"time":37800,"type":0},{"id":1,"sLen":0,"time":38100,"type":0},{"id":0,"sLen":0,"time":48600,"type":0},{"id":1,"sLen":0,"time":48750,"type":0},{"id":3,"sLen":0,"time":49050,"type":0},{"id":3,"sLen":0,"time":49350,"type":0},{"id":2,"sLen":0,"time":49650,"type":0},{"id":3,"sLen":0,"time":49800,"type":0},{"id":0,"sLen":0,"time":50100,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":0,"sLen":0,"time":51000,"type":0},{"id":1,"sLen":0,"time":51150,"type":0},{"id":3,"sLen":0,"time":51450,"type":0},{"id":3,"sLen":0,"time":51750,"type":0},{"id":2,"sLen":0,"time":52050,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":0,"sLen":0,"time":52500,"type":0},{"id":1,"sLen":0,"time":52800,"type":0},{"id":0,"sLen":0,"time":53400,"type":0},{"id":1,"sLen":0,"time":53550,"type":0},{"id":3,"sLen":0,"time":53850,"type":0},{"id":3,"sLen":0,"time":54150,"type":0},{"id":2,"sLen":0,"time":54450,"type":0},{"id":3,"sLen":0,"time":54600,"type":0},{"id":0,"sLen":0,"time":54900,"type":0},{"id":0,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55350,"type":0},{"id":0,"sLen":0,"time":55650,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56100,"type":0},{"id":3,"sLen":0,"time":56250,"type":0},{"id":0,"sLen":0,"time":56550,"type":0},{"id":3,"sLen":0,"time":56700,"type":0},{"id":0,"sLen":0,"time":57000,"type":0},{"id":3,"sLen":0,"time":57150,"type":0},{"id":0,"sLen":0,"time":67350,"type":0},{"id":1,"sLen":0,"time":67500,"type":0},{"id":0,"sLen":0,"time":67650,"type":0},{"id":2,"sLen":450,"time":67800,"type":0},{"id":3,"sLen":450,"time":68400,"type":0},{"id":2,"sLen":150,"time":69000,"type":0},{"id":0,"sLen":750,"time":69300,"type":0},{"id":2,"sLen":300,"time":70200,"type":0},{"id":3,"sLen":450,"time":70800,"type":0},{"id":2,"sLen":450,"time":71400,"type":0},{"id":0,"sLen":0,"time":72000,"type":0},{"id":3,"sLen":0,"time":72150,"type":0},{"id":0,"sLen":0,"time":72300,"type":0},{"id":3,"sLen":0,"time":72450,"type":0},{"id":2,"sLen":0,"time":72600,"type":0},{"id":0,"sLen":0,"time":72900,"type":0},{"id":3,"sLen":0,"time":73050,"type":0},{"id":0,"sLen":0,"time":73200,"type":0},{"id":3,"sLen":0,"time":73350,"type":0},{"id":2,"sLen":0,"time":73500,"type":0},{"id":0,"sLen":0,"time":73800,"type":0},{"id":3,"sLen":0,"time":73950,"type":0},{"id":0,"sLen":0,"time":74100,"type":0},{"id":3,"sLen":0,"time":74250,"type":0},{"id":2,"sLen":0,"time":74400,"type":0},{"id":0,"sLen":0,"time":74700,"type":0},{"id":3,"sLen":0,"time":74850,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":3,"sLen":0,"time":75150,"type":0},{"id":2,"sLen":0,"time":75300,"type":0},{"id":0,"sLen":0,"time":75600,"type":0},{"id":3,"sLen":0,"time":75750,"type":0},{"id":0,"sLen":0,"time":75900,"type":0},{"id":3,"sLen":0,"time":76050,"type":0},{"id":2,"sLen":0,"time":76200,"type":0},{"id":1,"sLen":0,"time":76500,"type":0},{"id":0,"sLen":0,"time":81600,"type":0},{"id":2,"sLen":0,"time":82200,"type":0},{"id":0,"sLen":0,"time":82500,"type":0},{"id":1,"sLen":0,"time":82950,"type":0},{"id":3,"sLen":0,"time":83250,"type":0},{"id":1,"sLen":0,"time":83550,"type":0},{"id":0,"sLen":0,"time":83700,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":2,"sLen":0,"time":84600,"type":0},{"id":0,"sLen":0,"time":84900,"type":0},{"id":1,"sLen":0,"time":85350,"type":0},{"id":3,"sLen":0,"time":85650,"type":0},{"id":1,"sLen":0,"time":85950,"type":0},{"id":0,"sLen":0,"time":86100,"type":0},{"id":0,"sLen":0,"time":91200,"type":0},{"id":1,"sLen":0,"time":91350,"type":0},{"id":3,"sLen":0,"time":91500,"type":0},{"id":1,"sLen":0,"time":91650,"type":0},{"id":0,"sLen":0,"time":91800,"type":0},{"id":1,"sLen":0,"time":91950,"type":0},{"id":3,"sLen":0,"time":92100,"type":0},{"id":2,"sLen":0,"time":92250,"type":0},{"id":0,"sLen":0,"time":92400,"type":0},{"id":1,"sLen":0,"time":92550,"type":0},{"id":3,"sLen":0,"time":92700,"type":0},{"id":1,"sLen":0,"time":92850,"type":0},{"id":0,"sLen":0,"time":93000,"type":0},{"id":1,"sLen":0,"time":93150,"type":0},{"id":3,"sLen":0,"time":93300,"type":0},{"id":2,"sLen":0,"time":93450,"type":0},{"id":0,"sLen":0,"time":93600,"type":0},{"id":1,"sLen":0,"time":93750,"type":0},{"id":3,"sLen":0,"time":93900,"type":0},{"id":1,"sLen":0,"time":94050,"type":0},{"id":0,"sLen":0,"time":94200,"type":0},{"id":1,"sLen":0,"time":94350,"type":0},{"id":3,"sLen":0,"time":94500,"type":0},{"id":2,"sLen":0,"time":94650,"type":0},{"id":3,"sLen":0,"time":94800,"type":0},{"id":0,"sLen":0,"time":94950,"type":0},{"id":3,"sLen":0,"time":95100,"type":0},{"id":1,"sLen":0,"time":95250,"type":0},{"id":3,"sLen":0,"time":95400,"type":0},{"id":1,"sLen":0,"time":95550,"type":0},{"id":2,"sLen":0,"time":95700,"type":0},{"id":0,"sLen":0,"time":95850,"type":0},{"id":0,"sLen":0,"time":100800,"type":0},{"id":1,"sLen":0,"time":100950,"type":0},{"id":3,"sLen":0,"time":101100,"type":0},{"id":1,"sLen":0,"time":101250,"type":0},{"id":0,"sLen":0,"time":101400,"type":0},{"id":1,"sLen":0,"time":101550,"type":0},{"id":3,"sLen":0,"time":101700,"type":0},{"id":2,"sLen":0,"time":101850,"type":0},{"id":0,"sLen":0,"time":102000,"type":0},{"id":1,"sLen":0,"time":102150,"type":0},{"id":3,"sLen":0,"time":102300,"type":0},{"id":1,"sLen":0,"time":102450,"type":0},{"id":0,"sLen":0,"time":102600,"type":0},{"id":1,"sLen":0,"time":102750,"type":0},{"id":3,"sLen":0,"time":102900,"type":0},{"id":2,"sLen":0,"time":103050,"type":0},{"id":0,"sLen":0,"time":103200,"type":0},{"id":1,"sLen":0,"time":103350,"type":0},{"id":3,"sLen":0,"time":103500,"type":0},{"id":1,"sLen":0,"time":103650,"type":0},{"id":0,"sLen":0,"time":103800,"type":0},{"id":1,"sLen":0,"time":103950,"type":0},{"id":3,"sLen":0,"time":104100,"type":0},{"id":2,"sLen":0,"time":104250,"type":0},{"id":3,"sLen":0,"time":104400,"type":0},{"id":0,"sLen":0,"time":104550,"type":0},{"id":3,"sLen":0,"time":104700,"type":0},{"id":1,"sLen":0,"time":104850,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":1,"sLen":0,"time":105150,"type":0},{"id":2,"sLen":0,"time":105300,"type":0},{"id":0,"sLen":0,"time":105450,"type":0},{"id":3,"sLen":600,"time":105600,"type":0}],"position":"boyfriend","type":1,"characters":["bf-christmas"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf-christmas"]}],"codenameChart":true,"scrollSpeed":1.5,"chartVersion":"1.6.0","stage":"mall","noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/cocoa/charts/normal.json b/assets/songs/cocoa/charts/normal.json index 622b90e5..292f47a7 100644 --- a/assets/songs/cocoa/charts/normal.json +++ b/assets/songs/cocoa/charts/normal.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall","song":"Cocoa","bpm":100.0,"needsVoices":true,"player1":"bf-christmas","player2":"parents-christmas","speed":1.3,"notes":[{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[0.0,0,0.0],[600.0,2,0.0],[900.0,0,0.0],[1350.0,1,0.0],[1650.0,3,0.0],[2100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[2400.0,0,0.0],[3000.0,2,0.0],[3300.0,0,0.0],[3750.0,1,0.0],[4050.0,3,0.0],[4500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[4800.0,0,0.0],[5100.0,0,0.0],[5400.0,2,0.0],[5700.0,0,0.0],[6000.0,0,0.0],[6300.0,2,0.0],[6600.0,0,0.0],[6750.0,3,0.0],[6900.0,0,0.0],[7050.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[7200.0,2,0.0],[7500.0,0,0.0],[7800.0,0,0.0],[8100.0,2,0.0],[8400.0,0,0.0],[8700.0,0,0.0],[9000.0,2,0.0],[9300.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[9600.0,0,0.0],[10200.0,2,0.0],[10500.0,0,0.0],[10950.0,1,0.0],[11250.0,3,0.0],[11700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12000.0,0,0.0],[12600.0,2,0.0],[12900.0,0,0.0],[13350.0,1,0.0],[13650.0,3,0.0],[14100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[14400.0,0,0.0],[14700.0,0,0.0],[15000.0,2,0.0],[15300.0,0,0.0],[15600.0,0,0.0],[15900.0,2,0.0],[16200.0,0,0.0],[16350.0,3,0.0],[16500.0,0,0.0],[16650.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16800.0,2,0.0],[17100.0,0,0.0],[17400.0,0,0.0],[17700.0,2,0.0],[18000.0,0,0.0],[18300.0,0,0.0],[18600.0,2,0.0],[18900.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19500.0,3,150.0],[19800.0,1,450.0],[20400.0,2,1050.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22500.0,1,0.0],[22800.0,0,150.0],[23100.0,1,0.0],[23400.0,3,0.0],[23700.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24000.0,0,0.0],[24300.0,0,0.0],[24600.0,2,0.0],[24900.0,0,0.0],[25200.0,0,0.0],[25500.0,2,0.0],[25800.0,0,0.0],[26100.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[26400.0,2,0.0],[26700.0,0,0.0],[27000.0,0,0.0],[27300.0,2,0.0],[27600.0,0,0.0],[27900.0,0,0.0],[28200.0,2,0.0],[28500.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28800.0,6,600.0],[29100.0,3,150.0],[29400.0,1,450.0],[30000.0,2,1050.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[32100.0,1,0.0],[32400.0,0,150.0],[32700.0,1,0.0],[33000.0,3,0.0],[33300.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[33600.0,0,0.0],[33900.0,0,0.0],[34200.0,2,0.0],[34500.0,0,0.0],[34800.0,0,0.0],[35100.0,2,0.0],[35400.0,0,0.0],[35700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36000.0,2,0.0],[36300.0,0,0.0],[36600.0,0,0.0],[36900.0,2,0.0],[37200.0,0,0.0],[37500.0,0,0.0],[37800.0,2,0.0],[38100.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[39000.0,0,0.0],[39450.0,3,0.0],[39750.0,3,0.0],[40050.0,2,0.0],[40500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[40800.0,1,0.0],[41400.0,0,0.0],[41850.0,3,0.0],[42150.0,3,0.0],[42450.0,2,0.0],[42900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[43200.0,1,0.0],[43800.0,0,0.0],[44250.0,3,0.0],[44550.0,3,0.0],[44850.0,2,0.0],[45300.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45600.0,0,0.0],[46050.0,0,0.0],[46200.0,3,0.0],[46500.0,0,0.0],[46950.0,0,0.0],[47100.0,3,0.0],[47400.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[48600.0,0,0.0],[49050.0,3,0.0],[49350.0,3,0.0],[49650.0,2,0.0],[50100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[50400.0,1,0.0],[51000.0,0,0.0],[51450.0,3,0.0],[51750.0,3,0.0],[52050.0,2,0.0],[52500.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[52800.0,1,0.0],[53400.0,0,0.0],[53850.0,3,0.0],[54150.0,3,0.0],[54450.0,2,0.0],[54900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[55200.0,0,0.0],[55650.0,0,0.0],[55800.0,3,0.0],[56100.0,0,0.0],[56550.0,0,0.0],[56700.0,3,0.0],[57000.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57750.0,0,0.0],[58200.0,2,450.0],[58800.0,3,450.0],[59400.0,2,150.0],[59700.0,0,750.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[60600.0,2,300.0],[61200.0,3,450.0],[61800.0,2,450.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62400.0,0,0.0],[62700.0,0,0.0],[63000.0,2,0.0],[63300.0,0,0.0],[63600.0,0,0.0],[63900.0,2,0.0],[64200.0,0,0.0],[64500.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[64800.0,2,0.0],[65100.0,0,0.0],[65400.0,0,0.0],[65700.0,2,0.0],[66000.0,0,0.0],[66300.0,0,0.0],[66600.0,2,0.0],[66900.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[67350.0,0,0.0],[67800.0,2,450.0],[68400.0,3,450.0],[69000.0,2,150.0],[69300.0,0,750.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[70200.0,2,300.0],[70800.0,3,450.0],[71400.0,2,450.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[72000.0,0,0.0],[72300.0,0,0.0],[72600.0,2,0.0],[72900.0,0,0.0],[73200.0,0,0.0],[73500.0,2,0.0],[73800.0,0,0.0],[74100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[74400.0,2,0.0],[74700.0,0,0.0],[75000.0,0,0.0],[75300.0,2,0.0],[75600.0,0,0.0],[75900.0,0,0.0],[76200.0,2,0.0],[76500.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[76800.0,0,0.0],[77400.0,2,0.0],[77700.0,0,0.0],[78150.0,1,0.0],[78450.0,3,0.0],[78900.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[79200.0,0,0.0],[79800.0,2,0.0],[80100.0,0,0.0],[80550.0,1,0.0],[80850.0,3,0.0],[81300.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[81600.0,0,0.0],[82200.0,2,0.0],[82500.0,0,0.0],[82950.0,1,0.0],[83250.0,3,0.0],[83700.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[84000.0,0,0.0],[84600.0,2,0.0],[84900.0,0,0.0],[85350.0,1,0.0],[85650.0,3,0.0],[86100.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[86400.0,0,0.0],[86700.0,3,0.0],[87000.0,3,0.0],[87300.0,3,0.0],[87450.0,2,0.0],[87600.0,0,0.0],[87900.0,3,0.0],[88200.0,3,0.0],[88500.0,3,0.0],[88650.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[88800.0,0,0.0],[89100.0,3,0.0],[89400.0,3,0.0],[89700.0,3,0.0],[89850.0,2,0.0],[90000.0,3,0.0],[90300.0,3,0.0],[90600.0,3,0.0],[90900.0,2,0.0],[91050.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[91200.0,0,0.0],[91500.0,3,0.0],[91800.0,3,0.0],[92100.0,3,0.0],[92250.0,2,0.0],[92400.0,0,0.0],[92700.0,3,0.0],[93000.0,3,0.0],[93300.0,3,0.0],[93450.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[93600.0,0,0.0],[93900.0,3,0.0],[94200.0,3,0.0],[94500.0,3,0.0],[94650.0,2,0.0],[94800.0,3,0.0],[95100.0,3,0.0],[95400.0,3,0.0],[95700.0,2,0.0],[95850.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[96000.0,0,0.0],[96300.0,3,0.0],[96600.0,3,0.0],[96900.0,3,0.0],[97050.0,2,0.0],[97200.0,0,0.0],[97500.0,3,0.0],[97800.0,3,0.0],[98100.0,3,0.0],[98250.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98400.0,0,0.0],[98700.0,3,0.0],[99000.0,3,0.0],[99300.0,3,0.0],[99450.0,2,0.0],[99600.0,3,0.0],[99900.0,3,0.0],[100200.0,3,0.0],[100500.0,2,0.0],[100650.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[100800.0,7,600.0],[100800.0,0,0.0],[101100.0,3,0.0],[101400.0,3,0.0],[101700.0,3,0.0],[101850.0,2,0.0],[102000.0,0,0.0],[102300.0,3,0.0],[102600.0,3,0.0],[102900.0,3,0.0],[103050.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[103200.0,0,0.0],[103500.0,3,0.0],[103800.0,3,0.0],[104100.0,3,0.0],[104250.0,2,0.0],[104400.0,3,0.0],[104700.0,3,0.0],[105000.0,3,0.0],[105300.0,2,0.0],[105450.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[105600.0,3,600.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":9600,"name":"Camera Movement"},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"time":28800,"name":"Camera Movement"},{"params":[0],"time":38400,"name":"Camera Movement"},{"params":[1],"time":48000,"name":"Camera Movement"},{"params":[0],"time":57600,"name":"Camera Movement"},{"params":[1],"time":67200,"name":"Camera Movement"},{"params":[0],"time":76800,"name":"Camera Movement"},{"params":[1],"time":81600,"name":"Camera Movement"},{"params":[0],"time":86400,"name":"Camera Movement"},{"params":[1],"time":91200,"name":"Camera Movement"},{"params":[0],"time":96000,"name":"Camera Movement"},{"params":[1],"time":100800,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":2,"sLen":0,"time":600,"type":0},{"id":0,"sLen":0,"time":900,"type":0},{"id":1,"sLen":0,"time":1350,"type":0},{"id":3,"sLen":0,"time":1650,"type":0},{"id":0,"sLen":0,"time":2100,"type":0},{"id":0,"sLen":0,"time":2400,"type":0},{"id":2,"sLen":0,"time":3000,"type":0},{"id":0,"sLen":0,"time":3300,"type":0},{"id":1,"sLen":0,"time":3750,"type":0},{"id":3,"sLen":0,"time":4050,"type":0},{"id":0,"sLen":0,"time":4500,"type":0},{"id":0,"sLen":0,"time":4800,"type":0},{"id":0,"sLen":0,"time":5100,"type":0},{"id":2,"sLen":0,"time":5400,"type":0},{"id":0,"sLen":0,"time":5700,"type":0},{"id":0,"sLen":0,"time":6000,"type":0},{"id":2,"sLen":0,"time":6300,"type":0},{"id":0,"sLen":0,"time":6600,"type":0},{"id":3,"sLen":0,"time":6750,"type":0},{"id":0,"sLen":0,"time":6900,"type":0},{"id":3,"sLen":0,"time":7050,"type":0},{"id":2,"sLen":0,"time":7200,"type":0},{"id":0,"sLen":0,"time":7500,"type":0},{"id":0,"sLen":0,"time":7800,"type":0},{"id":2,"sLen":0,"time":8100,"type":0},{"id":0,"sLen":0,"time":8400,"type":0},{"id":0,"sLen":0,"time":8700,"type":0},{"id":2,"sLen":0,"time":9000,"type":0},{"id":1,"sLen":0,"time":9300,"type":0},{"id":3,"sLen":150,"time":19500,"type":0},{"id":1,"sLen":450,"time":19800,"type":0},{"id":2,"sLen":1050,"time":20400,"type":0},{"id":1,"sLen":0,"time":22500,"type":0},{"id":0,"sLen":150,"time":22800,"type":0},{"id":1,"sLen":0,"time":23100,"type":0},{"id":3,"sLen":0,"time":23400,"type":0},{"id":1,"sLen":0,"time":23700,"type":0},{"id":0,"sLen":0,"time":24000,"type":0},{"id":0,"sLen":0,"time":24300,"type":0},{"id":2,"sLen":0,"time":24600,"type":0},{"id":0,"sLen":0,"time":24900,"type":0},{"id":0,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":25500,"type":0},{"id":0,"sLen":0,"time":25800,"type":0},{"id":0,"sLen":0,"time":26100,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":0,"sLen":0,"time":26700,"type":0},{"id":0,"sLen":0,"time":27000,"type":0},{"id":2,"sLen":0,"time":27300,"type":0},{"id":0,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":27900,"type":0},{"id":2,"sLen":0,"time":28200,"type":0},{"id":1,"sLen":0,"time":28500,"type":0},{"id":2,"sLen":600,"time":28800,"type":0},{"id":0,"sLen":0,"time":39000,"type":0},{"id":3,"sLen":0,"time":39450,"type":0},{"id":3,"sLen":0,"time":39750,"type":0},{"id":2,"sLen":0,"time":40050,"type":0},{"id":0,"sLen":0,"time":40500,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":0,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":41850,"type":0},{"id":3,"sLen":0,"time":42150,"type":0},{"id":2,"sLen":0,"time":42450,"type":0},{"id":0,"sLen":0,"time":42900,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":0,"sLen":0,"time":43800,"type":0},{"id":3,"sLen":0,"time":44250,"type":0},{"id":3,"sLen":0,"time":44550,"type":0},{"id":2,"sLen":0,"time":44850,"type":0},{"id":0,"sLen":0,"time":45300,"type":0},{"id":0,"sLen":0,"time":45600,"type":0},{"id":0,"sLen":0,"time":46050,"type":0},{"id":3,"sLen":0,"time":46200,"type":0},{"id":0,"sLen":0,"time":46500,"type":0},{"id":0,"sLen":0,"time":46950,"type":0},{"id":3,"sLen":0,"time":47100,"type":0},{"id":0,"sLen":0,"time":47400,"type":0},{"id":0,"sLen":0,"time":57750,"type":0},{"id":2,"sLen":450,"time":58200,"type":0},{"id":3,"sLen":450,"time":58800,"type":0},{"id":2,"sLen":150,"time":59400,"type":0},{"id":0,"sLen":750,"time":59700,"type":0},{"id":2,"sLen":300,"time":60600,"type":0},{"id":3,"sLen":450,"time":61200,"type":0},{"id":2,"sLen":450,"time":61800,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":0,"sLen":0,"time":62700,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":0,"sLen":0,"time":63600,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":0,"sLen":0,"time":64200,"type":0},{"id":0,"sLen":0,"time":64500,"type":0},{"id":2,"sLen":0,"time":64800,"type":0},{"id":0,"sLen":0,"time":65100,"type":0},{"id":0,"sLen":0,"time":65400,"type":0},{"id":2,"sLen":0,"time":65700,"type":0},{"id":0,"sLen":0,"time":66000,"type":0},{"id":0,"sLen":0,"time":66300,"type":0},{"id":2,"sLen":0,"time":66600,"type":0},{"id":1,"sLen":0,"time":66900,"type":0},{"id":0,"sLen":0,"time":76800,"type":0},{"id":2,"sLen":0,"time":77400,"type":0},{"id":0,"sLen":0,"time":77700,"type":0},{"id":1,"sLen":0,"time":78150,"type":0},{"id":3,"sLen":0,"time":78450,"type":0},{"id":0,"sLen":0,"time":78900,"type":0},{"id":0,"sLen":0,"time":79200,"type":0},{"id":2,"sLen":0,"time":79800,"type":0},{"id":0,"sLen":0,"time":80100,"type":0},{"id":1,"sLen":0,"time":80550,"type":0},{"id":3,"sLen":0,"time":80850,"type":0},{"id":0,"sLen":0,"time":81300,"type":0},{"id":0,"sLen":0,"time":86400,"type":0},{"id":3,"sLen":0,"time":86700,"type":0},{"id":3,"sLen":0,"time":87000,"type":0},{"id":3,"sLen":0,"time":87300,"type":0},{"id":2,"sLen":0,"time":87450,"type":0},{"id":0,"sLen":0,"time":87600,"type":0},{"id":3,"sLen":0,"time":87900,"type":0},{"id":3,"sLen":0,"time":88200,"type":0},{"id":3,"sLen":0,"time":88500,"type":0},{"id":2,"sLen":0,"time":88650,"type":0},{"id":0,"sLen":0,"time":88800,"type":0},{"id":3,"sLen":0,"time":89100,"type":0},{"id":3,"sLen":0,"time":89400,"type":0},{"id":3,"sLen":0,"time":89700,"type":0},{"id":2,"sLen":0,"time":89850,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":3,"sLen":0,"time":90300,"type":0},{"id":3,"sLen":0,"time":90600,"type":0},{"id":2,"sLen":0,"time":90900,"type":0},{"id":0,"sLen":0,"time":91050,"type":0},{"id":0,"sLen":0,"time":96000,"type":0},{"id":3,"sLen":0,"time":96300,"type":0},{"id":3,"sLen":0,"time":96600,"type":0},{"id":3,"sLen":0,"time":96900,"type":0},{"id":2,"sLen":0,"time":97050,"type":0},{"id":0,"sLen":0,"time":97200,"type":0},{"id":3,"sLen":0,"time":97500,"type":0},{"id":3,"sLen":0,"time":97800,"type":0},{"id":3,"sLen":0,"time":98100,"type":0},{"id":2,"sLen":0,"time":98250,"type":0},{"id":0,"sLen":0,"time":98400,"type":0},{"id":3,"sLen":0,"time":98700,"type":0},{"id":3,"sLen":0,"time":99000,"type":0},{"id":3,"sLen":0,"time":99300,"type":0},{"id":2,"sLen":0,"time":99450,"type":0},{"id":3,"sLen":0,"time":99600,"type":0},{"id":3,"sLen":0,"time":99900,"type":0},{"id":3,"sLen":0,"time":100200,"type":0},{"id":2,"sLen":0,"time":100500,"type":0},{"id":0,"sLen":0,"time":100650,"type":0},{"id":3,"sLen":600,"time":100800,"type":0}],"position":"dad","type":0,"characters":["parents-christmas"]},{"visible":true,"keyCount":4,"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":2,"sLen":0,"time":10200,"type":0},{"id":0,"sLen":0,"time":10500,"type":0},{"id":1,"sLen":0,"time":10950,"type":0},{"id":3,"sLen":0,"time":11250,"type":0},{"id":0,"sLen":0,"time":11700,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":2,"sLen":0,"time":12600,"type":0},{"id":0,"sLen":0,"time":12900,"type":0},{"id":1,"sLen":0,"time":13350,"type":0},{"id":3,"sLen":0,"time":13650,"type":0},{"id":0,"sLen":0,"time":14100,"type":0},{"id":0,"sLen":0,"time":14400,"type":0},{"id":0,"sLen":0,"time":14700,"type":0},{"id":2,"sLen":0,"time":15000,"type":0},{"id":0,"sLen":0,"time":15300,"type":0},{"id":0,"sLen":0,"time":15600,"type":0},{"id":2,"sLen":0,"time":15900,"type":0},{"id":0,"sLen":0,"time":16200,"type":0},{"id":3,"sLen":0,"time":16350,"type":0},{"id":0,"sLen":0,"time":16500,"type":0},{"id":3,"sLen":0,"time":16650,"type":0},{"id":2,"sLen":0,"time":16800,"type":0},{"id":0,"sLen":0,"time":17100,"type":0},{"id":0,"sLen":0,"time":17400,"type":0},{"id":2,"sLen":0,"time":17700,"type":0},{"id":0,"sLen":0,"time":18000,"type":0},{"id":0,"sLen":0,"time":18300,"type":0},{"id":2,"sLen":0,"time":18600,"type":0},{"id":1,"sLen":0,"time":18900,"type":0},{"id":3,"sLen":150,"time":29100,"type":0},{"id":1,"sLen":450,"time":29400,"type":0},{"id":2,"sLen":1050,"time":30000,"type":0},{"id":1,"sLen":0,"time":32100,"type":0},{"id":0,"sLen":150,"time":32400,"type":0},{"id":1,"sLen":0,"time":32700,"type":0},{"id":3,"sLen":0,"time":33000,"type":0},{"id":1,"sLen":0,"time":33300,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":0,"sLen":0,"time":33900,"type":0},{"id":2,"sLen":0,"time":34200,"type":0},{"id":0,"sLen":0,"time":34500,"type":0},{"id":0,"sLen":0,"time":34800,"type":0},{"id":2,"sLen":0,"time":35100,"type":0},{"id":0,"sLen":0,"time":35400,"type":0},{"id":0,"sLen":0,"time":35700,"type":0},{"id":2,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36300,"type":0},{"id":0,"sLen":0,"time":36600,"type":0},{"id":2,"sLen":0,"time":36900,"type":0},{"id":0,"sLen":0,"time":37200,"type":0},{"id":0,"sLen":0,"time":37500,"type":0},{"id":2,"sLen":0,"time":37800,"type":0},{"id":1,"sLen":0,"time":38100,"type":0},{"id":0,"sLen":0,"time":48600,"type":0},{"id":3,"sLen":0,"time":49050,"type":0},{"id":3,"sLen":0,"time":49350,"type":0},{"id":2,"sLen":0,"time":49650,"type":0},{"id":0,"sLen":0,"time":50100,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":0,"sLen":0,"time":51000,"type":0},{"id":3,"sLen":0,"time":51450,"type":0},{"id":3,"sLen":0,"time":51750,"type":0},{"id":2,"sLen":0,"time":52050,"type":0},{"id":0,"sLen":0,"time":52500,"type":0},{"id":1,"sLen":0,"time":52800,"type":0},{"id":0,"sLen":0,"time":53400,"type":0},{"id":3,"sLen":0,"time":53850,"type":0},{"id":3,"sLen":0,"time":54150,"type":0},{"id":2,"sLen":0,"time":54450,"type":0},{"id":0,"sLen":0,"time":54900,"type":0},{"id":0,"sLen":0,"time":55200,"type":0},{"id":0,"sLen":0,"time":55650,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56100,"type":0},{"id":0,"sLen":0,"time":56550,"type":0},{"id":3,"sLen":0,"time":56700,"type":0},{"id":0,"sLen":0,"time":57000,"type":0},{"id":0,"sLen":0,"time":67350,"type":0},{"id":2,"sLen":450,"time":67800,"type":0},{"id":3,"sLen":450,"time":68400,"type":0},{"id":2,"sLen":150,"time":69000,"type":0},{"id":0,"sLen":750,"time":69300,"type":0},{"id":2,"sLen":300,"time":70200,"type":0},{"id":3,"sLen":450,"time":70800,"type":0},{"id":2,"sLen":450,"time":71400,"type":0},{"id":0,"sLen":0,"time":72000,"type":0},{"id":0,"sLen":0,"time":72300,"type":0},{"id":2,"sLen":0,"time":72600,"type":0},{"id":0,"sLen":0,"time":72900,"type":0},{"id":0,"sLen":0,"time":73200,"type":0},{"id":2,"sLen":0,"time":73500,"type":0},{"id":0,"sLen":0,"time":73800,"type":0},{"id":0,"sLen":0,"time":74100,"type":0},{"id":2,"sLen":0,"time":74400,"type":0},{"id":0,"sLen":0,"time":74700,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":2,"sLen":0,"time":75300,"type":0},{"id":0,"sLen":0,"time":75600,"type":0},{"id":0,"sLen":0,"time":75900,"type":0},{"id":2,"sLen":0,"time":76200,"type":0},{"id":1,"sLen":0,"time":76500,"type":0},{"id":0,"sLen":0,"time":81600,"type":0},{"id":2,"sLen":0,"time":82200,"type":0},{"id":0,"sLen":0,"time":82500,"type":0},{"id":1,"sLen":0,"time":82950,"type":0},{"id":3,"sLen":0,"time":83250,"type":0},{"id":0,"sLen":0,"time":83700,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":2,"sLen":0,"time":84600,"type":0},{"id":0,"sLen":0,"time":84900,"type":0},{"id":1,"sLen":0,"time":85350,"type":0},{"id":3,"sLen":0,"time":85650,"type":0},{"id":0,"sLen":0,"time":86100,"type":0},{"id":0,"sLen":0,"time":91200,"type":0},{"id":3,"sLen":0,"time":91500,"type":0},{"id":3,"sLen":0,"time":91800,"type":0},{"id":3,"sLen":0,"time":92100,"type":0},{"id":2,"sLen":0,"time":92250,"type":0},{"id":0,"sLen":0,"time":92400,"type":0},{"id":3,"sLen":0,"time":92700,"type":0},{"id":3,"sLen":0,"time":93000,"type":0},{"id":3,"sLen":0,"time":93300,"type":0},{"id":2,"sLen":0,"time":93450,"type":0},{"id":0,"sLen":0,"time":93600,"type":0},{"id":3,"sLen":0,"time":93900,"type":0},{"id":3,"sLen":0,"time":94200,"type":0},{"id":3,"sLen":0,"time":94500,"type":0},{"id":2,"sLen":0,"time":94650,"type":0},{"id":3,"sLen":0,"time":94800,"type":0},{"id":3,"sLen":0,"time":95100,"type":0},{"id":3,"sLen":0,"time":95400,"type":0},{"id":2,"sLen":0,"time":95700,"type":0},{"id":0,"sLen":0,"time":95850,"type":0},{"id":0,"sLen":0,"time":100800,"type":0},{"id":3,"sLen":0,"time":101100,"type":0},{"id":3,"sLen":0,"time":101400,"type":0},{"id":3,"sLen":0,"time":101700,"type":0},{"id":2,"sLen":0,"time":101850,"type":0},{"id":0,"sLen":0,"time":102000,"type":0},{"id":3,"sLen":0,"time":102300,"type":0},{"id":3,"sLen":0,"time":102600,"type":0},{"id":3,"sLen":0,"time":102900,"type":0},{"id":2,"sLen":0,"time":103050,"type":0},{"id":0,"sLen":0,"time":103200,"type":0},{"id":3,"sLen":0,"time":103500,"type":0},{"id":3,"sLen":0,"time":103800,"type":0},{"id":3,"sLen":0,"time":104100,"type":0},{"id":2,"sLen":0,"time":104250,"type":0},{"id":3,"sLen":0,"time":104400,"type":0},{"id":3,"sLen":0,"time":104700,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":2,"sLen":0,"time":105300,"type":0},{"id":0,"sLen":0,"time":105450,"type":0},{"id":3,"sLen":600,"time":105600,"type":0}],"position":"boyfriend","type":1,"characters":["bf-christmas"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf-christmas"]}],"scrollSpeed":1.3,"chartVersion":"1.6.0","stage":"mall","codenameChart":true,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/cocoa/events.json b/assets/songs/cocoa/events.json new file mode 100644 index 00000000..5a785db8 --- /dev/null +++ b/assets/songs/cocoa/events.json @@ -0,0 +1 @@ +{"events":[{"params":[true,false,0],"time":19200,"name":"Alt Animation Toggle"},{"params":[false,false,0],"time":38400,"name":"Alt Animation Toggle"},{"params":[true,false,0],"time":57600,"name":"Alt Animation Toggle"},{"params":[false,false,0],"time":76800,"name":"Alt Animation Toggle"},{"params":[true,false,0],"time":86400,"name":"Alt Animation Toggle"}]} \ No newline at end of file diff --git a/assets/songs/cocoa/scripts/week5.hx b/assets/songs/cocoa/scripts/week5.hx deleted file mode 100644 index 8c2f4fb3..00000000 --- a/assets/songs/cocoa/scripts/week5.hx +++ /dev/null @@ -1,4 +0,0 @@ -function onNoteCreation(event) { - if (event.mustHit) - event.animSuffix = ""; -} \ No newline at end of file diff --git a/assets/songs/eggnog/charts/easy.json b/assets/songs/eggnog/charts/easy.json index 8c929650..674d25be 100644 --- a/assets/songs/eggnog/charts/easy.json +++ b/assets/songs/eggnog/charts/easy.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall","song":"Eggnog","bpm":150.0,"needsVoices":true,"player1":"bf-christmas","player2":"parents-christmas","speed":1.4,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[3399.99976,1,0.0],[3799.99976,1,0.0],[4200.0,1,0.0],[4400.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[5000.0,1,0.0],[5400.0,1,0.0],[5600.0,3,0.0],[6000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[6599.99951,1,0.0],[6999.99951,1,0.0],[7399.99951,1,0.0],[7599.99951,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[8200.0,1,0.0],[8600.0,1,0.0],[8800.0,3,0.0],[9000.0,2,0.0],[9400.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[9800.0,1,0.0],[10200.0,1,0.0],[10600.0,1,0.0],[10800.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[11400.0,1,0.0],[11800.0,1,0.0],[12000.0,3,0.0],[12400.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12999.999,1,0.0],[13399.999,1,0.0],[13799.999,1,0.0],[13999.999,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[14599.999,1,0.0],[14999.999,1,0.0],[15199.999,3,0.0],[15399.999,2,0.0],[15799.999,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[15999.999,2,0.0],[16400.0,1,0.0],[16800.0,3,100.0],[17200.0,0,0.0],[17400.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[17800.0,0,0.0],[18200.0,0,0.0],[18600.0,0,0.0],[19000.0,3,400.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19600.0,1,100.0],[20000.0,3,100.0],[20400.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21000.0,0,0.0],[21400.0,1,0.0],[22000.0,2,400.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[22400.0,4,400.0],[22400.0,2,0.0],[22800.0,1,0.0],[23200.0,3,100.0],[23600.0,0,0.0],[23800.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[24200.0,0,0.0],[24600.0,0,0.0],[25000.0,0,0.0],[25400.0,3,400.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[25999.998,1,100.0],[26399.998,3,100.0],[26799.998,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27399.998,0,0.0],[27799.998,1,0.0],[28399.998,2,300.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[28799.998,4,400.0],[28999.998,3,0.0],[29199.998,1,0.0],[29399.998,0,0.0],[29799.998,3,0.0],[30199.998,0,0.0],[30299.998,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[30599.998,3,0.0],[30799.998,1,0.0],[30999.998,0,0.0],[31099.998,3,0.0],[31599.998,3,0.0],[31799.998,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[31999.998,3,0.0],[32399.998,1,0.0],[32599.998,0,0.0],[32699.998,3,0.0],[33000.0,3,0.0],[33400.0,0,0.0],[33500.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33800.0,3,0.0],[34000.0,1,0.0],[34200.0,0,0.0],[34400.0,1,0.0],[34800.0,3,0.0],[35000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[35200.0,7,400.0],[35400.0,3,0.0],[35600.0,1,0.0],[35800.0,0,0.0],[36200.0,3,0.0],[36600.0,0,0.0],[36700.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[37000.0,3,0.0],[37200.0,1,0.0],[37400.0,0,0.0],[37500.0,3,0.0],[38000.0,3,0.0],[38200.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[38400.0,3,0.0],[38800.0,1,0.0],[39000.0,0,0.0],[39100.0,3,0.0],[39400.0,3,0.0],[39800.0,0,0.0],[39900.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[40200.0,3,0.0],[40400.0,1,0.0],[40600.0,0,0.0],[40800.0,1,0.0],[41200.0,3,0.0],[41400.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[41600.0,7,400.0],[41600.0,0,0.0],[42000.0,3,0.0],[42200.0,0,0.0],[42600.0,0,0.0],[43000.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43200.0,7,400.0],[43200.0,0,0.0],[43600.0,3,0.0],[43800.0,0,0.0],[44200.0,0,0.0],[44600.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[44800.0,7,400.0],[44800.0,0,0.0],[45200.0,3,0.0],[45400.0,0,0.0],[45800.0,0,0.0],[46200.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46400.0,7,400.0],[46400.0,0,0.0],[46800.0,3,0.0],[47000.0,0,0.0],[47400.0,0,0.0],[47800.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48000.0,7,400.0],[48200.0,1,0.0],[48600.0,1,0.0],[49000.0,1,0.0],[49400.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49800.0,1,0.0],[50200.0,1,0.0],[50600.0,1,0.0],[50800.0,3,0.0],[51000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[51399.9961,1,0.0],[51799.9961,1,0.0],[52199.9961,1,0.0],[52599.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52999.9961,1,0.0],[53399.9961,1,0.0],[53799.9961,1,0.0],[53999.9961,3,0.0],[54199.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54599.9961,1,0.0],[54999.9961,1,0.0],[55399.9961,1,0.0],[55799.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56199.9961,1,0.0],[56599.9961,1,0.0],[56999.9961,1,0.0],[57199.9961,3,0.0],[57399.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[57799.9961,1,0.0],[58199.9961,1,0.0],[58599.9961,1,0.0],[58999.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[59399.9961,1,0.0],[59799.9961,1,0.0],[60199.9961,1,0.0],[60399.9961,3,0.0],[60599.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[60799.9961,2,0.0],[60999.9961,3,0.0],[61199.9961,0,0.0],[61599.9961,1,0.0],[61999.9961,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62399.9961,0,0.0],[62799.9961,0,0.0],[62999.9961,3,100.0],[63199.9961,1,0.0],[63599.9961,3,200.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[63999.9961,2,0.0],[64199.9961,3,0.0],[64399.9961,0,0.0],[64799.9961,1,0.0],[65199.9961,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[65600.0,0,0.0],[66000.0,0,0.0],[66200.0,3,100.0],[66400.0,1,0.0],[66800.0,3,200.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[67200.0,1,0.0],[67400.0,3,0.0],[67600.0,0,200.0],[68000.0,1,0.0],[68400.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[68800.0,3,0.0],[68900.0,0,100.0],[69200.0,1,0.0],[69600.0,2,300.0],[70000.0,2,300.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[70400.0,1,0.0],[70600.0,3,0.0],[70800.0,0,200.0],[71200.0,1,0.0],[71600.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[72000.0,3,0.0],[72100.0,0,100.0],[72400.0,1,0.0],[72800.0,2,300.0],[73200.0,2,300.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[73600.0,2,0.0],[73800.0,7,0.0],[74000.0,5,0.0],[74000.0,1,0.0],[74200.0,4,0.0],[74400.0,3,100.0],[74600.0,7,0.0],[74800.0,0,0.0],[75000.0,4,0.0],[75000.0,2,0.0],[75100.0,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75400.0,7,0.0],[75400.0,0,0.0],[75600.0,5,0.0],[75800.0,4,0.0],[75800.0,0,0.0],[75900.0,7,0.0],[76200.0,0,0.0],[76400.0,7,0.0],[76600.0,4,0.0],[76600.0,3,400.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[76800.0,7,0.0],[77200.0,5,0.0],[77200.0,1,100.0],[77400.0,4,0.0],[77500.0,7,0.0],[77600.0,3,100.0],[77800.0,7,0.0],[78000.0,1,0.0],[78200.0,4,0.0],[78200.0,0,0.0],[78300.0,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78600.0,7,0.0],[78600.0,0,0.0],[78800.0,5,0.0],[79000.0,4,0.0],[79000.0,1,0.0],[79200.0,5,0.0],[79400.0,1,0.0],[79600.0,7,0.0],[79600.0,2,300.0],[79800.0,5,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80000.0,7,400.0],[80200.0,3,0.0],[80400.0,1,0.0],[80600.0,0,0.0],[81000.0,3,0.0],[81400.0,0,0.0],[81500.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[81800.0,3,0.0],[82000.0,1,0.0],[82200.0,0,0.0],[82300.0,3,0.0],[82800.0,3,0.0],[83000.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83200.0,3,0.0],[83600.0,1,0.0],[83800.0,0,0.0],[83900.0,3,0.0],[84200.0,3,0.0],[84600.0,0,0.0],[84700.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[85000.0,3,0.0],[85200.0,1,0.0],[85400.0,0,0.0],[85600.0,1,0.0],[86000.0,3,0.0],[86200.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[86400.0,0,0.0],[86400.0,7,400.0],[86800.0,3,0.0],[87000.0,0,0.0],[87400.0,0,0.0],[87800.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[88000.0,7,400.0],[88000.0,0,0.0],[88400.0,3,0.0],[88600.0,0,0.0],[89000.0,0,0.0],[89400.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89600.0,7,400.0],[89600.0,0,0.0],[90000.0,3,0.0],[90200.0,0,0.0],[90600.0,0,0.0],[91000.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[91200.0,7,400.0],[91200.0,0,0.0],[91600.0,3,0.0],[91800.0,0,0.0],[92200.0,0,0.0],[92600.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92800.0,3,800.0]]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":0,"name":"Camera Movement"},{"params":[0],"time":3200,"name":"Camera Movement"},{"params":[1],"time":9600,"name":"Camera Movement"},{"params":[0],"time":16000,"name":"Camera Movement"},{"params":[1],"time":22400,"name":"Camera Movement"},{"params":[0],"time":28800,"name":"Camera Movement"},{"params":[1],"time":35200,"name":"Camera Movement"},{"params":[0],"time":41600,"name":"Camera Movement"},{"params":[1],"time":43200,"name":"Camera Movement"},{"params":[0],"time":44800,"name":"Camera Movement"},{"params":[1],"time":46400,"name":"Camera Movement"},{"params":[0],"time":48000,"name":"Camera Movement"},{"params":[1],"time":54400,"name":"Camera Movement"},{"params":[0],"time":60800,"name":"Camera Movement"},{"params":[1],"time":64000,"name":"Camera Movement"},{"params":[0],"time":67200,"name":"Camera Movement"},{"params":[1],"time":70400,"name":"Camera Movement"},{"params":[0],"time":86400,"name":"Camera Movement"},{"params":[1],"time":88000,"name":"Camera Movement"},{"params":[0],"time":89600,"name":"Camera Movement"},{"params":[1],"time":91200,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":1,"sLen":0,"time":3399.99976,"type":0},{"id":1,"sLen":0,"time":3799.99976,"type":0},{"id":1,"sLen":0,"time":4200,"type":0},{"id":3,"sLen":0,"time":4400,"type":0},{"id":1,"sLen":0,"time":5000,"type":0},{"id":1,"sLen":0,"time":5400,"type":0},{"id":3,"sLen":0,"time":5600,"type":0},{"id":3,"sLen":0,"time":6000,"type":0},{"id":1,"sLen":0,"time":6599.99951,"type":0},{"id":1,"sLen":0,"time":6999.99951,"type":0},{"id":1,"sLen":0,"time":7399.99951,"type":0},{"id":3,"sLen":0,"time":7599.99951,"type":0},{"id":1,"sLen":0,"time":8200,"type":0},{"id":1,"sLen":0,"time":8600,"type":0},{"id":3,"sLen":0,"time":8800,"type":0},{"id":2,"sLen":0,"time":9000,"type":0},{"id":1,"sLen":0,"time":9400,"type":0},{"id":2,"sLen":0,"time":15999.999,"type":0},{"id":1,"sLen":0,"time":16400,"type":0},{"id":3,"sLen":100,"time":16800,"type":0},{"id":0,"sLen":0,"time":17200,"type":0},{"id":2,"sLen":0,"time":17400,"type":0},{"id":0,"sLen":0,"time":17800,"type":0},{"id":0,"sLen":0,"time":18200,"type":0},{"id":0,"sLen":0,"time":18600,"type":0},{"id":3,"sLen":400,"time":19000,"type":0},{"id":1,"sLen":100,"time":19600,"type":0},{"id":3,"sLen":100,"time":20000,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":0,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21400,"type":0},{"id":2,"sLen":400,"time":22000,"type":0},{"id":0,"sLen":400,"time":22400,"type":0},{"id":3,"sLen":0,"time":28999.998,"type":0},{"id":1,"sLen":0,"time":29199.998,"type":0},{"id":0,"sLen":0,"time":29399.998,"type":0},{"id":3,"sLen":0,"time":29799.998,"type":0},{"id":0,"sLen":0,"time":30199.998,"type":0},{"id":3,"sLen":0,"time":30299.998,"type":0},{"id":3,"sLen":0,"time":30599.998,"type":0},{"id":1,"sLen":0,"time":30799.998,"type":0},{"id":0,"sLen":0,"time":30999.998,"type":0},{"id":3,"sLen":0,"time":31099.998,"type":0},{"id":3,"sLen":0,"time":31599.998,"type":0},{"id":0,"sLen":0,"time":31799.998,"type":0},{"id":3,"sLen":0,"time":31999.998,"type":0},{"id":1,"sLen":0,"time":32399.998,"type":0},{"id":0,"sLen":0,"time":32599.998,"type":0},{"id":3,"sLen":0,"time":32699.998,"type":0},{"id":3,"sLen":0,"time":33000,"type":0},{"id":0,"sLen":0,"time":33400,"type":0},{"id":3,"sLen":0,"time":33500,"type":0},{"id":3,"sLen":0,"time":33800,"type":0},{"id":1,"sLen":0,"time":34000,"type":0},{"id":0,"sLen":0,"time":34200,"type":0},{"id":1,"sLen":0,"time":34400,"type":0},{"id":3,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":35000,"type":0},{"id":3,"sLen":400,"time":35200,"type":0},{"id":0,"sLen":0,"time":41600,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":0,"sLen":0,"time":42200,"type":0},{"id":0,"sLen":0,"time":42600,"type":0},{"id":3,"sLen":0,"time":43000,"type":0},{"id":3,"sLen":400,"time":43200,"type":0},{"id":0,"sLen":0,"time":44800,"type":0},{"id":3,"sLen":0,"time":45200,"type":0},{"id":0,"sLen":0,"time":45400,"type":0},{"id":0,"sLen":0,"time":45800,"type":0},{"id":3,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":400,"time":46400,"type":0},{"id":1,"sLen":0,"time":48200,"type":0},{"id":1,"sLen":0,"time":48600,"type":0},{"id":1,"sLen":0,"time":49000,"type":0},{"id":3,"sLen":0,"time":49400,"type":0},{"id":1,"sLen":0,"time":49800,"type":0},{"id":1,"sLen":0,"time":50200,"type":0},{"id":1,"sLen":0,"time":50600,"type":0},{"id":3,"sLen":0,"time":50800,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":1,"sLen":0,"time":51399.9961,"type":0},{"id":1,"sLen":0,"time":51799.9961,"type":0},{"id":1,"sLen":0,"time":52199.9961,"type":0},{"id":3,"sLen":0,"time":52599.9961,"type":0},{"id":1,"sLen":0,"time":52999.9961,"type":0},{"id":1,"sLen":0,"time":53399.9961,"type":0},{"id":1,"sLen":0,"time":53799.9961,"type":0},{"id":3,"sLen":0,"time":53999.9961,"type":0},{"id":3,"sLen":0,"time":54199.9961,"type":0},{"id":2,"sLen":0,"time":60799.9961,"type":0},{"id":3,"sLen":0,"time":60999.9961,"type":0},{"id":0,"sLen":0,"time":61199.9961,"type":0},{"id":1,"sLen":0,"time":61599.9961,"type":0},{"id":1,"sLen":0,"time":61999.9961,"type":0},{"id":0,"sLen":0,"time":62399.9961,"type":0},{"id":0,"sLen":0,"time":62799.9961,"type":0},{"id":3,"sLen":100,"time":62999.9961,"type":0},{"id":1,"sLen":0,"time":63199.9961,"type":0},{"id":3,"sLen":200,"time":63599.9961,"type":0},{"id":1,"sLen":0,"time":67200,"type":0},{"id":3,"sLen":0,"time":67400,"type":0},{"id":0,"sLen":200,"time":67600,"type":0},{"id":1,"sLen":0,"time":68000,"type":0},{"id":1,"sLen":0,"time":68400,"type":0},{"id":3,"sLen":0,"time":68800,"type":0},{"id":0,"sLen":100,"time":68900,"type":0},{"id":1,"sLen":0,"time":69200,"type":0},{"id":2,"sLen":300,"time":69600,"type":0},{"id":2,"sLen":300,"time":70000,"type":0},{"id":3,"sLen":0,"time":73800,"type":0},{"id":1,"sLen":0,"time":74000,"type":0},{"id":0,"sLen":0,"time":74200,"type":0},{"id":3,"sLen":0,"time":74600,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":3,"sLen":0,"time":75100,"type":0},{"id":3,"sLen":0,"time":75400,"type":0},{"id":1,"sLen":0,"time":75600,"type":0},{"id":0,"sLen":0,"time":75800,"type":0},{"id":3,"sLen":0,"time":75900,"type":0},{"id":3,"sLen":0,"time":76400,"type":0},{"id":0,"sLen":0,"time":76600,"type":0},{"id":3,"sLen":0,"time":76800,"type":0},{"id":1,"sLen":0,"time":77200,"type":0},{"id":0,"sLen":0,"time":77400,"type":0},{"id":3,"sLen":0,"time":77500,"type":0},{"id":3,"sLen":0,"time":77800,"type":0},{"id":0,"sLen":0,"time":78200,"type":0},{"id":3,"sLen":0,"time":78300,"type":0},{"id":3,"sLen":0,"time":78600,"type":0},{"id":1,"sLen":0,"time":78800,"type":0},{"id":0,"sLen":0,"time":79000,"type":0},{"id":1,"sLen":0,"time":79200,"type":0},{"id":3,"sLen":0,"time":79600,"type":0},{"id":1,"sLen":0,"time":79800,"type":0},{"id":3,"sLen":400,"time":80000,"type":0},{"id":0,"sLen":0,"time":86400,"type":0},{"id":3,"sLen":0,"time":86800,"type":0},{"id":0,"sLen":0,"time":87000,"type":0},{"id":0,"sLen":0,"time":87400,"type":0},{"id":3,"sLen":0,"time":87800,"type":0},{"id":3,"sLen":400,"time":88000,"type":0},{"id":0,"sLen":0,"time":89600,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90200,"type":0},{"id":0,"sLen":0,"time":90600,"type":0},{"id":3,"sLen":0,"time":91000,"type":0},{"id":3,"sLen":400,"time":91200,"type":0}],"position":"dad","type":0,"characters":["parents-christmas"]},{"visible":true,"keyCount":4,"notes":[{"id":1,"sLen":0,"time":9800,"type":0},{"id":1,"sLen":0,"time":10200,"type":0},{"id":1,"sLen":0,"time":10600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":1,"sLen":0,"time":11400,"type":0},{"id":1,"sLen":0,"time":11800,"type":0},{"id":3,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":12400,"type":0},{"id":1,"sLen":0,"time":12999.999,"type":0},{"id":1,"sLen":0,"time":13399.999,"type":0},{"id":1,"sLen":0,"time":13799.999,"type":0},{"id":3,"sLen":0,"time":13999.999,"type":0},{"id":1,"sLen":0,"time":14599.999,"type":0},{"id":1,"sLen":0,"time":14999.999,"type":0},{"id":3,"sLen":0,"time":15199.999,"type":0},{"id":2,"sLen":0,"time":15399.999,"type":0},{"id":1,"sLen":0,"time":15799.999,"type":0},{"id":2,"sLen":0,"time":22400,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":3,"sLen":100,"time":23200,"type":0},{"id":0,"sLen":0,"time":23600,"type":0},{"id":2,"sLen":0,"time":23800,"type":0},{"id":0,"sLen":0,"time":24200,"type":0},{"id":0,"sLen":0,"time":24600,"type":0},{"id":0,"sLen":0,"time":25000,"type":0},{"id":3,"sLen":400,"time":25400,"type":0},{"id":1,"sLen":100,"time":25999.998,"type":0},{"id":3,"sLen":100,"time":26399.998,"type":0},{"id":1,"sLen":0,"time":26799.998,"type":0},{"id":0,"sLen":0,"time":27399.998,"type":0},{"id":1,"sLen":0,"time":27799.998,"type":0},{"id":2,"sLen":300,"time":28399.998,"type":0},{"id":0,"sLen":400,"time":28799.998,"type":0},{"id":3,"sLen":0,"time":35400,"type":0},{"id":1,"sLen":0,"time":35600,"type":0},{"id":0,"sLen":0,"time":35800,"type":0},{"id":3,"sLen":0,"time":36200,"type":0},{"id":0,"sLen":0,"time":36600,"type":0},{"id":3,"sLen":0,"time":36700,"type":0},{"id":3,"sLen":0,"time":37000,"type":0},{"id":1,"sLen":0,"time":37200,"type":0},{"id":0,"sLen":0,"time":37400,"type":0},{"id":3,"sLen":0,"time":37500,"type":0},{"id":3,"sLen":0,"time":38000,"type":0},{"id":0,"sLen":0,"time":38200,"type":0},{"id":3,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":38800,"type":0},{"id":0,"sLen":0,"time":39000,"type":0},{"id":3,"sLen":0,"time":39100,"type":0},{"id":3,"sLen":0,"time":39400,"type":0},{"id":0,"sLen":0,"time":39800,"type":0},{"id":3,"sLen":0,"time":39900,"type":0},{"id":3,"sLen":0,"time":40200,"type":0},{"id":1,"sLen":0,"time":40400,"type":0},{"id":0,"sLen":0,"time":40600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":3,"sLen":0,"time":41200,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":400,"time":41600,"type":0},{"id":0,"sLen":0,"time":43200,"type":0},{"id":3,"sLen":0,"time":43600,"type":0},{"id":0,"sLen":0,"time":43800,"type":0},{"id":0,"sLen":0,"time":44200,"type":0},{"id":3,"sLen":0,"time":44600,"type":0},{"id":3,"sLen":400,"time":44800,"type":0},{"id":0,"sLen":0,"time":46400,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":0,"sLen":0,"time":47000,"type":0},{"id":0,"sLen":0,"time":47400,"type":0},{"id":3,"sLen":0,"time":47800,"type":0},{"id":3,"sLen":400,"time":48000,"type":0},{"id":1,"sLen":0,"time":54599.9961,"type":0},{"id":1,"sLen":0,"time":54999.9961,"type":0},{"id":1,"sLen":0,"time":55399.9961,"type":0},{"id":3,"sLen":0,"time":55799.9961,"type":0},{"id":1,"sLen":0,"time":56199.9961,"type":0},{"id":1,"sLen":0,"time":56599.9961,"type":0},{"id":1,"sLen":0,"time":56999.9961,"type":0},{"id":3,"sLen":0,"time":57199.9961,"type":0},{"id":3,"sLen":0,"time":57399.9961,"type":0},{"id":1,"sLen":0,"time":57799.9961,"type":0},{"id":1,"sLen":0,"time":58199.9961,"type":0},{"id":1,"sLen":0,"time":58599.9961,"type":0},{"id":3,"sLen":0,"time":58999.9961,"type":0},{"id":1,"sLen":0,"time":59399.9961,"type":0},{"id":1,"sLen":0,"time":59799.9961,"type":0},{"id":1,"sLen":0,"time":60199.9961,"type":0},{"id":3,"sLen":0,"time":60399.9961,"type":0},{"id":3,"sLen":0,"time":60599.9961,"type":0},{"id":2,"sLen":0,"time":63999.9961,"type":0},{"id":3,"sLen":0,"time":64199.9961,"type":0},{"id":0,"sLen":0,"time":64399.9961,"type":0},{"id":1,"sLen":0,"time":64799.9961,"type":0},{"id":1,"sLen":0,"time":65199.9961,"type":0},{"id":0,"sLen":0,"time":65600,"type":0},{"id":0,"sLen":0,"time":66000,"type":0},{"id":3,"sLen":100,"time":66200,"type":0},{"id":1,"sLen":0,"time":66400,"type":0},{"id":3,"sLen":200,"time":66800,"type":0},{"id":1,"sLen":0,"time":70400,"type":0},{"id":3,"sLen":0,"time":70600,"type":0},{"id":0,"sLen":200,"time":70800,"type":0},{"id":1,"sLen":0,"time":71200,"type":0},{"id":1,"sLen":0,"time":71600,"type":0},{"id":3,"sLen":0,"time":72000,"type":0},{"id":0,"sLen":100,"time":72100,"type":0},{"id":1,"sLen":0,"time":72400,"type":0},{"id":2,"sLen":300,"time":72800,"type":0},{"id":2,"sLen":300,"time":73200,"type":0},{"id":2,"sLen":0,"time":73600,"type":0},{"id":1,"sLen":0,"time":74000,"type":0},{"id":3,"sLen":100,"time":74400,"type":0},{"id":0,"sLen":0,"time":74800,"type":0},{"id":2,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75400,"type":0},{"id":0,"sLen":0,"time":75800,"type":0},{"id":0,"sLen":0,"time":76200,"type":0},{"id":3,"sLen":400,"time":76600,"type":0},{"id":1,"sLen":100,"time":77200,"type":0},{"id":3,"sLen":100,"time":77600,"type":0},{"id":1,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78200,"type":0},{"id":0,"sLen":0,"time":78600,"type":0},{"id":1,"sLen":0,"time":79000,"type":0},{"id":1,"sLen":0,"time":79400,"type":0},{"id":2,"sLen":300,"time":79600,"type":0},{"id":3,"sLen":0,"time":80200,"type":0},{"id":1,"sLen":0,"time":80400,"type":0},{"id":0,"sLen":0,"time":80600,"type":0},{"id":3,"sLen":0,"time":81000,"type":0},{"id":0,"sLen":0,"time":81400,"type":0},{"id":3,"sLen":0,"time":81500,"type":0},{"id":3,"sLen":0,"time":81800,"type":0},{"id":1,"sLen":0,"time":82000,"type":0},{"id":0,"sLen":0,"time":82200,"type":0},{"id":3,"sLen":0,"time":82300,"type":0},{"id":3,"sLen":0,"time":82800,"type":0},{"id":0,"sLen":0,"time":83000,"type":0},{"id":3,"sLen":0,"time":83200,"type":0},{"id":1,"sLen":0,"time":83600,"type":0},{"id":0,"sLen":0,"time":83800,"type":0},{"id":3,"sLen":0,"time":83900,"type":0},{"id":3,"sLen":0,"time":84200,"type":0},{"id":0,"sLen":0,"time":84600,"type":0},{"id":3,"sLen":0,"time":84700,"type":0},{"id":3,"sLen":0,"time":85000,"type":0},{"id":1,"sLen":0,"time":85200,"type":0},{"id":0,"sLen":0,"time":85400,"type":0},{"id":1,"sLen":0,"time":85600,"type":0},{"id":3,"sLen":0,"time":86000,"type":0},{"id":1,"sLen":0,"time":86200,"type":0},{"id":3,"sLen":400,"time":86400,"type":0},{"id":0,"sLen":0,"time":88000,"type":0},{"id":3,"sLen":0,"time":88400,"type":0},{"id":0,"sLen":0,"time":88600,"type":0},{"id":0,"sLen":0,"time":89000,"type":0},{"id":3,"sLen":0,"time":89400,"type":0},{"id":3,"sLen":400,"time":89600,"type":0},{"id":0,"sLen":0,"time":91200,"type":0},{"id":3,"sLen":0,"time":91600,"type":0},{"id":0,"sLen":0,"time":91800,"type":0},{"id":0,"sLen":0,"time":92200,"type":0},{"id":3,"sLen":0,"time":92600,"type":0},{"id":3,"sLen":800,"time":92800,"type":0}],"position":"boyfriend","type":1,"characters":["bf-christmas"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf-christmas"]}],"scrollSpeed":1.4,"chartVersion":"1.6.0","stage":"mall","codenameChart":true,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/eggnog/charts/hard.json b/assets/songs/eggnog/charts/hard.json index 282cf3ab..eedf0024 100644 --- a/assets/songs/eggnog/charts/hard.json +++ b/assets/songs/eggnog/charts/hard.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall","song":"Eggnog","bpm":150.0,"needsVoices":true,"player1":"bf-christmas","player2":"parents-christmas","speed":1.9,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[3399.99976,1,0.0],[3799.99976,1,0.0],[4200.0,1,0.0],[4400.0,3,0.0],[4600.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[5000.0,1,0.0],[5400.0,1,0.0],[5600.0,3,0.0],[5800.0,2,0.0],[6000.0,3,0.0],[6200.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[6599.99951,1,0.0],[6999.99951,1,0.0],[7399.99951,1,0.0],[7599.99951,3,0.0],[7799.99951,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[8200.0,1,0.0],[8600.0,1,0.0],[8800.0,3,0.0],[9000.0,2,0.0],[9200.0,0,0.0],[9400.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[9800.0,1,0.0],[10200.0,1,0.0],[10600.0,1,0.0],[10800.0,3,0.0],[11000.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[11400.0,1,0.0],[11800.0,1,0.0],[12000.0,3,0.0],[12200.0,2,0.0],[12400.0,3,0.0],[12600.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12999.999,1,0.0],[13399.999,1,0.0],[13799.999,1,0.0],[13999.999,3,0.0],[14199.999,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[14599.999,1,0.0],[14999.999,1,0.0],[15199.999,3,0.0],[15399.999,2,0.0],[15599.999,0,0.0],[15799.999,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[15999.999,2,0.0],[16099.999,0,0.0],[16199.999,3,100.0],[16400.0,1,0.0],[16500.0,2,200.0],[16800.0,3,100.0],[17200.0,0,0.0],[17400.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[17800.0,0,0.0],[18200.0,0,0.0],[18600.0,0,0.0],[18700.0,1,0.0],[18800.0,2,0.0],[19000.0,3,400.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19600.0,1,100.0],[20000.0,3,100.0],[20400.0,1,0.0],[20600.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21000.0,0,0.0],[21400.0,1,0.0],[21800.0,1,0.0],[22000.0,2,300.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[22400.0,4,400.0],[22400.0,2,0.0],[22500.0,0,0.0],[22600.0,3,100.0],[22800.0,1,0.0],[22900.0,2,200.0],[23200.0,3,100.0],[23600.0,0,0.0],[23800.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[24200.0,0,0.0],[24600.0,0,0.0],[25000.0,0,0.0],[25100.0,1,0.0],[25200.0,2,0.0],[25400.0,3,400.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[25999.998,1,100.0],[26399.998,3,100.0],[26799.998,1,0.0],[26999.998,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27399.998,0,0.0],[27799.998,1,0.0],[28199.998,1,0.0],[28399.998,2,300.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[28799.998,4,400.0],[28999.998,3,0.0],[29199.998,1,0.0],[29399.998,0,0.0],[29499.998,3,0.0],[29599.998,2,0.0],[29799.998,3,0.0],[29999.998,1,0.0],[30199.998,0,0.0],[30299.998,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[30399.998,2,0.0],[30599.998,3,0.0],[30799.998,1,0.0],[30999.998,0,0.0],[31099.998,3,0.0],[31199.998,2,0.0],[31399.998,3,0.0],[31599.998,3,0.0],[31799.998,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[31999.998,3,0.0],[32199.998,3,0.0],[32399.998,1,0.0],[32599.998,0,0.0],[32699.998,3,0.0],[32800.0,2,0.0],[33000.0,3,0.0],[33200.0,1,0.0],[33400.0,0,0.0],[33500.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33600.0,2,0.0],[33800.0,3,0.0],[34000.0,1,0.0],[34200.0,0,0.0],[34400.0,1,0.0],[34500.0,0,0.0],[34600.0,2,100.0],[34800.0,3,0.0],[35000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[35200.0,7,400.0],[35400.0,3,0.0],[35600.0,1,0.0],[35800.0,0,0.0],[35900.0,3,0.0],[36000.0,2,0.0],[36200.0,3,0.0],[36400.0,1,0.0],[36600.0,0,0.0],[36700.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[36800.0,2,0.0],[37000.0,3,0.0],[37200.0,1,0.0],[37400.0,0,0.0],[37500.0,3,0.0],[37600.0,2,0.0],[37800.0,3,0.0],[38000.0,3,0.0],[38200.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[38400.0,3,0.0],[38600.0,3,0.0],[38800.0,1,0.0],[39000.0,0,0.0],[39100.0,3,0.0],[39200.0,2,0.0],[39400.0,3,0.0],[39600.0,1,0.0],[39800.0,0,0.0],[39900.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[40000.0,2,0.0],[40200.0,3,0.0],[40400.0,1,0.0],[40600.0,0,0.0],[40800.0,1,0.0],[40900.0,0,0.0],[41000.0,2,100.0],[41200.0,3,0.0],[41400.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[41600.0,0,0.0],[41600.0,7,400.0],[41700.0,2,0.0],[41800.0,3,0.0],[42000.0,2,0.0],[42200.0,0,0.0],[42400.0,1,0.0],[42600.0,0,0.0],[42800.0,1,0.0],[43000.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43200.0,7,400.0],[43200.0,0,0.0],[43300.0,2,0.0],[43400.0,3,0.0],[43600.0,2,0.0],[43800.0,0,0.0],[44000.0,1,0.0],[44200.0,0,0.0],[44400.0,1,0.0],[44600.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[44800.0,7,400.0],[44800.0,0,0.0],[44900.0,2,0.0],[45000.0,3,0.0],[45200.0,2,0.0],[45400.0,0,0.0],[45600.0,1,0.0],[45800.0,0,0.0],[46000.0,1,0.0],[46200.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46400.0,7,400.0],[46400.0,0,0.0],[46500.0,2,0.0],[46600.0,3,0.0],[46800.0,2,0.0],[47000.0,0,0.0],[47200.0,1,0.0],[47400.0,0,0.0],[47600.0,1,0.0],[47800.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48000.0,7,400.0],[48200.0,1,0.0],[48600.0,1,0.0],[49000.0,1,0.0],[49200.0,2,0.0],[49300.0,0,0.0],[49400.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49800.0,1,0.0],[50200.0,1,0.0],[50600.0,1,0.0],[50800.0,3,0.0],[50900.0,0,0.0],[51000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[51399.9961,1,0.0],[51799.9961,1,0.0],[52199.9961,1,0.0],[52399.9961,2,0.0],[52499.9961,0,0.0],[52599.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52999.9961,1,0.0],[53399.9961,1,0.0],[53799.9961,1,0.0],[53999.9961,3,0.0],[54099.9961,0,0.0],[54199.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54599.9961,1,0.0],[54999.9961,1,0.0],[55399.9961,1,0.0],[55599.9961,2,0.0],[55699.9961,0,0.0],[55799.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56199.9961,1,0.0],[56599.9961,1,0.0],[56999.9961,1,0.0],[57199.9961,3,0.0],[57299.9961,0,0.0],[57399.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[57799.9961,1,0.0],[58199.9961,1,0.0],[58599.9961,1,0.0],[58799.9961,2,0.0],[58899.9961,0,0.0],[58999.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[59399.9961,1,0.0],[59799.9961,1,0.0],[60199.9961,1,0.0],[60399.9961,3,0.0],[60499.9961,0,0.0],[60599.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[60799.9961,2,0.0],[60999.9961,3,0.0],[61199.9961,0,0.0],[61399.9961,3,0.0],[61599.9961,1,0.0],[61799.9961,0,0.0],[61999.9961,1,0.0],[62199.9961,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62399.9961,0,0.0],[62499.9961,3,0.0],[62599.9961,1,0.0],[62799.9961,0,0.0],[62899.9961,1,0.0],[62999.9961,3,100.0],[63199.9961,1,0.0],[63399.9961,2,0.0],[63599.9961,3,200.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[63999.9961,2,0.0],[64199.9961,3,0.0],[64399.9961,0,0.0],[64599.9961,3,0.0],[64799.9961,1,0.0],[64999.9961,0,0.0],[65199.9961,1,0.0],[65399.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[65600.0,0,0.0],[65700.0,3,0.0],[65800.0,1,0.0],[66000.0,0,0.0],[66100.0,1,0.0],[66200.0,3,100.0],[66400.0,1,0.0],[66600.0,2,0.0],[66800.0,3,200.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[67200.0,1,0.0],[67400.0,3,0.0],[67600.0,0,200.0],[68000.0,1,0.0],[68200.0,1,0.0],[68400.0,1,0.0],[68600.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[68800.0,3,0.0],[68900.0,0,0.0],[69000.0,3,0.0],[69200.0,1,0.0],[69300.0,0,0.0],[69400.0,1,0.0],[69500.0,0,0.0],[69600.0,2,300.0],[70000.0,2,300.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[70400.0,1,0.0],[70600.0,3,0.0],[70800.0,0,200.0],[71200.0,1,0.0],[71400.0,0,0.0],[71600.0,1,0.0],[71800.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[72000.0,3,0.0],[72100.0,0,0.0],[72200.0,3,0.0],[72400.0,1,0.0],[72500.0,0,0.0],[72600.0,1,0.0],[72700.0,0,0.0],[72800.0,2,300.0],[73200.0,2,300.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[73600.0,2,0.0],[73700.0,0,0.0],[73800.0,7,0.0],[73800.0,3,100.0],[74000.0,5,0.0],[74000.0,1,0.0],[74100.0,2,200.0],[74200.0,4,0.0],[74300.0,7,0.0],[74400.0,6,0.0],[74400.0,3,100.0],[74600.0,7,0.0],[74800.0,5,0.0],[74800.0,0,0.0],[75000.0,4,0.0],[75000.0,2,0.0],[75100.0,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75200.0,6,0.0],[75400.0,7,0.0],[75400.0,0,0.0],[75600.0,5,0.0],[75800.0,4,0.0],[75800.0,0,0.0],[75900.0,7,0.0],[76000.0,6,0.0],[76200.0,7,0.0],[76200.0,0,0.0],[76300.0,1,0.0],[76400.0,7,0.0],[76400.0,2,0.0],[76600.0,4,0.0],[76600.0,3,400.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[76800.0,7,0.0],[77000.0,7,0.0],[77200.0,5,0.0],[77200.0,1,100.0],[77400.0,4,0.0],[77500.0,7,0.0],[77600.0,6,0.0],[77600.0,3,100.0],[77800.0,7,0.0],[78000.0,5,0.0],[78000.0,1,0.0],[78200.0,4,0.0],[78200.0,0,0.0],[78300.0,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78400.0,6,0.0],[78600.0,7,0.0],[78600.0,0,0.0],[78800.0,5,0.0],[79000.0,4,0.0],[79000.0,1,0.0],[79200.0,5,0.0],[79300.0,4,0.0],[79400.0,6,100.0],[79400.0,1,0.0],[79600.0,7,0.0],[79600.0,2,300.0],[79800.0,5,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80000.0,7,400.0],[80200.0,3,0.0],[80400.0,1,0.0],[80600.0,0,0.0],[80700.0,3,0.0],[80800.0,2,0.0],[81000.0,3,0.0],[81200.0,1,0.0],[81400.0,0,0.0],[81500.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[81600.0,2,0.0],[81800.0,3,0.0],[82000.0,1,0.0],[82200.0,0,0.0],[82300.0,3,0.0],[82400.0,2,0.0],[82600.0,3,0.0],[82800.0,3,0.0],[83000.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83200.0,3,0.0],[83400.0,3,0.0],[83600.0,1,0.0],[83800.0,0,0.0],[83900.0,3,0.0],[84000.0,2,0.0],[84200.0,3,0.0],[84400.0,1,0.0],[84600.0,0,0.0],[84700.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[84800.0,2,0.0],[85000.0,3,0.0],[85200.0,1,0.0],[85400.0,0,0.0],[85600.0,1,0.0],[85700.0,0,0.0],[85800.0,2,100.0],[86000.0,3,0.0],[86200.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[86400.0,0,0.0],[86400.0,7,400.0],[86500.0,2,0.0],[86600.0,3,0.0],[86800.0,2,0.0],[87000.0,0,0.0],[87200.0,1,0.0],[87400.0,0,0.0],[87600.0,1,0.0],[87800.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[88000.0,7,400.0],[88000.0,0,0.0],[88100.0,2,0.0],[88200.0,3,0.0],[88400.0,2,0.0],[88600.0,0,0.0],[88800.0,1,0.0],[89000.0,0,0.0],[89200.0,1,0.0],[89400.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89600.0,7,400.0],[89600.0,0,0.0],[89700.0,2,0.0],[89800.0,3,0.0],[90000.0,2,0.0],[90200.0,0,0.0],[90400.0,1,0.0],[90600.0,0,0.0],[90800.0,1,0.0],[91000.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[91200.0,7,400.0],[91200.0,0,0.0],[91300.0,2,0.0],[91400.0,3,0.0],[91600.0,2,0.0],[91800.0,0,0.0],[92000.0,1,0.0],[92200.0,0,0.0],[92400.0,1,0.0],[92600.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92800.0,3,800.0]]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":0,"name":"Camera Movement"},{"params":[0],"time":3200,"name":"Camera Movement"},{"params":[1],"time":9600,"name":"Camera Movement"},{"params":[0],"time":16000,"name":"Camera Movement"},{"params":[1],"time":22400,"name":"Camera Movement"},{"params":[0],"time":28800,"name":"Camera Movement"},{"params":[1],"time":35200,"name":"Camera Movement"},{"params":[0],"time":41600,"name":"Camera Movement"},{"params":[1],"time":43200,"name":"Camera Movement"},{"params":[0],"time":44800,"name":"Camera Movement"},{"params":[1],"time":46400,"name":"Camera Movement"},{"params":[0],"time":48000,"name":"Camera Movement"},{"params":[1],"time":54400,"name":"Camera Movement"},{"params":[0],"time":60800,"name":"Camera Movement"},{"params":[1],"time":64000,"name":"Camera Movement"},{"params":[0],"time":67200,"name":"Camera Movement"},{"params":[1],"time":70400,"name":"Camera Movement"},{"params":[0],"time":86400,"name":"Camera Movement"},{"params":[1],"time":88000,"name":"Camera Movement"},{"params":[0],"time":89600,"name":"Camera Movement"},{"params":[1],"time":91200,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":1,"sLen":0,"time":3399.99976,"type":0},{"id":1,"sLen":0,"time":3799.99976,"type":0},{"id":1,"sLen":0,"time":4200,"type":0},{"id":3,"sLen":0,"time":4400,"type":0},{"id":2,"sLen":0,"time":4600,"type":0},{"id":1,"sLen":0,"time":5000,"type":0},{"id":1,"sLen":0,"time":5400,"type":0},{"id":3,"sLen":0,"time":5600,"type":0},{"id":2,"sLen":0,"time":5800,"type":0},{"id":3,"sLen":0,"time":6000,"type":0},{"id":1,"sLen":0,"time":6200,"type":0},{"id":1,"sLen":0,"time":6599.99951,"type":0},{"id":1,"sLen":0,"time":6999.99951,"type":0},{"id":1,"sLen":0,"time":7399.99951,"type":0},{"id":3,"sLen":0,"time":7599.99951,"type":0},{"id":2,"sLen":0,"time":7799.99951,"type":0},{"id":1,"sLen":0,"time":8200,"type":0},{"id":1,"sLen":0,"time":8600,"type":0},{"id":3,"sLen":0,"time":8800,"type":0},{"id":2,"sLen":0,"time":9000,"type":0},{"id":0,"sLen":0,"time":9200,"type":0},{"id":1,"sLen":0,"time":9400,"type":0},{"id":2,"sLen":0,"time":15999.999,"type":0},{"id":0,"sLen":0,"time":16099.999,"type":0},{"id":3,"sLen":100,"time":16199.999,"type":0},{"id":1,"sLen":0,"time":16400,"type":0},{"id":2,"sLen":200,"time":16500,"type":0},{"id":3,"sLen":100,"time":16800,"type":0},{"id":0,"sLen":0,"time":17200,"type":0},{"id":2,"sLen":0,"time":17400,"type":0},{"id":0,"sLen":0,"time":17800,"type":0},{"id":0,"sLen":0,"time":18200,"type":0},{"id":0,"sLen":0,"time":18600,"type":0},{"id":1,"sLen":0,"time":18700,"type":0},{"id":2,"sLen":0,"time":18800,"type":0},{"id":3,"sLen":400,"time":19000,"type":0},{"id":1,"sLen":100,"time":19600,"type":0},{"id":3,"sLen":100,"time":20000,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":0,"sLen":0,"time":20600,"type":0},{"id":0,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21400,"type":0},{"id":1,"sLen":0,"time":21800,"type":0},{"id":2,"sLen":300,"time":22000,"type":0},{"id":0,"sLen":400,"time":22400,"type":0},{"id":3,"sLen":0,"time":28999.998,"type":0},{"id":1,"sLen":0,"time":29199.998,"type":0},{"id":0,"sLen":0,"time":29399.998,"type":0},{"id":3,"sLen":0,"time":29499.998,"type":0},{"id":2,"sLen":0,"time":29599.998,"type":0},{"id":3,"sLen":0,"time":29799.998,"type":0},{"id":1,"sLen":0,"time":29999.998,"type":0},{"id":0,"sLen":0,"time":30199.998,"type":0},{"id":3,"sLen":0,"time":30299.998,"type":0},{"id":2,"sLen":0,"time":30399.998,"type":0},{"id":3,"sLen":0,"time":30599.998,"type":0},{"id":1,"sLen":0,"time":30799.998,"type":0},{"id":0,"sLen":0,"time":30999.998,"type":0},{"id":3,"sLen":0,"time":31099.998,"type":0},{"id":2,"sLen":0,"time":31199.998,"type":0},{"id":3,"sLen":0,"time":31399.998,"type":0},{"id":3,"sLen":0,"time":31599.998,"type":0},{"id":0,"sLen":0,"time":31799.998,"type":0},{"id":3,"sLen":0,"time":31999.998,"type":0},{"id":3,"sLen":0,"time":32199.998,"type":0},{"id":1,"sLen":0,"time":32399.998,"type":0},{"id":0,"sLen":0,"time":32599.998,"type":0},{"id":3,"sLen":0,"time":32699.998,"type":0},{"id":2,"sLen":0,"time":32800,"type":0},{"id":3,"sLen":0,"time":33000,"type":0},{"id":1,"sLen":0,"time":33200,"type":0},{"id":0,"sLen":0,"time":33400,"type":0},{"id":3,"sLen":0,"time":33500,"type":0},{"id":2,"sLen":0,"time":33600,"type":0},{"id":3,"sLen":0,"time":33800,"type":0},{"id":1,"sLen":0,"time":34000,"type":0},{"id":0,"sLen":0,"time":34200,"type":0},{"id":1,"sLen":0,"time":34400,"type":0},{"id":0,"sLen":0,"time":34500,"type":0},{"id":2,"sLen":100,"time":34600,"type":0},{"id":3,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":35000,"type":0},{"id":3,"sLen":400,"time":35200,"type":0},{"id":0,"sLen":0,"time":41600,"type":0},{"id":2,"sLen":0,"time":41700,"type":0},{"id":3,"sLen":0,"time":41800,"type":0},{"id":2,"sLen":0,"time":42000,"type":0},{"id":0,"sLen":0,"time":42200,"type":0},{"id":1,"sLen":0,"time":42400,"type":0},{"id":0,"sLen":0,"time":42600,"type":0},{"id":1,"sLen":0,"time":42800,"type":0},{"id":3,"sLen":0,"time":43000,"type":0},{"id":3,"sLen":400,"time":43200,"type":0},{"id":0,"sLen":0,"time":44800,"type":0},{"id":2,"sLen":0,"time":44900,"type":0},{"id":3,"sLen":0,"time":45000,"type":0},{"id":2,"sLen":0,"time":45200,"type":0},{"id":0,"sLen":0,"time":45400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":0,"sLen":0,"time":45800,"type":0},{"id":1,"sLen":0,"time":46000,"type":0},{"id":3,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":400,"time":46400,"type":0},{"id":1,"sLen":0,"time":48200,"type":0},{"id":1,"sLen":0,"time":48600,"type":0},{"id":1,"sLen":0,"time":49000,"type":0},{"id":2,"sLen":0,"time":49200,"type":0},{"id":0,"sLen":0,"time":49300,"type":0},{"id":3,"sLen":0,"time":49400,"type":0},{"id":1,"sLen":0,"time":49800,"type":0},{"id":1,"sLen":0,"time":50200,"type":0},{"id":1,"sLen":0,"time":50600,"type":0},{"id":3,"sLen":0,"time":50800,"type":0},{"id":0,"sLen":0,"time":50900,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":1,"sLen":0,"time":51399.9961,"type":0},{"id":1,"sLen":0,"time":51799.9961,"type":0},{"id":1,"sLen":0,"time":52199.9961,"type":0},{"id":2,"sLen":0,"time":52399.9961,"type":0},{"id":0,"sLen":0,"time":52499.9961,"type":0},{"id":3,"sLen":0,"time":52599.9961,"type":0},{"id":1,"sLen":0,"time":52999.9961,"type":0},{"id":1,"sLen":0,"time":53399.9961,"type":0},{"id":1,"sLen":0,"time":53799.9961,"type":0},{"id":3,"sLen":0,"time":53999.9961,"type":0},{"id":0,"sLen":0,"time":54099.9961,"type":0},{"id":3,"sLen":0,"time":54199.9961,"type":0},{"id":2,"sLen":0,"time":60799.9961,"type":0},{"id":3,"sLen":0,"time":60999.9961,"type":0},{"id":0,"sLen":0,"time":61199.9961,"type":0},{"id":3,"sLen":0,"time":61399.9961,"type":0},{"id":1,"sLen":0,"time":61599.9961,"type":0},{"id":0,"sLen":0,"time":61799.9961,"type":0},{"id":1,"sLen":0,"time":61999.9961,"type":0},{"id":3,"sLen":0,"time":62199.9961,"type":0},{"id":0,"sLen":0,"time":62399.9961,"type":0},{"id":3,"sLen":0,"time":62499.9961,"type":0},{"id":1,"sLen":0,"time":62599.9961,"type":0},{"id":0,"sLen":0,"time":62799.9961,"type":0},{"id":1,"sLen":0,"time":62899.9961,"type":0},{"id":3,"sLen":100,"time":62999.9961,"type":0},{"id":1,"sLen":0,"time":63199.9961,"type":0},{"id":2,"sLen":0,"time":63399.9961,"type":0},{"id":3,"sLen":200,"time":63599.9961,"type":0},{"id":1,"sLen":0,"time":67200,"type":0},{"id":3,"sLen":0,"time":67400,"type":0},{"id":0,"sLen":200,"time":67600,"type":0},{"id":1,"sLen":0,"time":68000,"type":0},{"id":1,"sLen":0,"time":68200,"type":0},{"id":1,"sLen":0,"time":68400,"type":0},{"id":2,"sLen":0,"time":68600,"type":0},{"id":3,"sLen":0,"time":68800,"type":0},{"id":0,"sLen":0,"time":68900,"type":0},{"id":3,"sLen":0,"time":69000,"type":0},{"id":1,"sLen":0,"time":69200,"type":0},{"id":0,"sLen":0,"time":69300,"type":0},{"id":1,"sLen":0,"time":69400,"type":0},{"id":0,"sLen":0,"time":69500,"type":0},{"id":2,"sLen":300,"time":69600,"type":0},{"id":2,"sLen":300,"time":70000,"type":0},{"id":3,"sLen":0,"time":73800,"type":0},{"id":1,"sLen":0,"time":74000,"type":0},{"id":0,"sLen":0,"time":74200,"type":0},{"id":3,"sLen":0,"time":74300,"type":0},{"id":2,"sLen":0,"time":74400,"type":0},{"id":3,"sLen":0,"time":74600,"type":0},{"id":1,"sLen":0,"time":74800,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":3,"sLen":0,"time":75100,"type":0},{"id":2,"sLen":0,"time":75200,"type":0},{"id":3,"sLen":0,"time":75400,"type":0},{"id":1,"sLen":0,"time":75600,"type":0},{"id":0,"sLen":0,"time":75800,"type":0},{"id":3,"sLen":0,"time":75900,"type":0},{"id":2,"sLen":0,"time":76000,"type":0},{"id":3,"sLen":0,"time":76200,"type":0},{"id":3,"sLen":0,"time":76400,"type":0},{"id":0,"sLen":0,"time":76600,"type":0},{"id":3,"sLen":0,"time":76800,"type":0},{"id":3,"sLen":0,"time":77000,"type":0},{"id":1,"sLen":0,"time":77200,"type":0},{"id":0,"sLen":0,"time":77400,"type":0},{"id":3,"sLen":0,"time":77500,"type":0},{"id":2,"sLen":0,"time":77600,"type":0},{"id":3,"sLen":0,"time":77800,"type":0},{"id":1,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78200,"type":0},{"id":3,"sLen":0,"time":78300,"type":0},{"id":2,"sLen":0,"time":78400,"type":0},{"id":3,"sLen":0,"time":78600,"type":0},{"id":1,"sLen":0,"time":78800,"type":0},{"id":0,"sLen":0,"time":79000,"type":0},{"id":1,"sLen":0,"time":79200,"type":0},{"id":0,"sLen":0,"time":79300,"type":0},{"id":2,"sLen":100,"time":79400,"type":0},{"id":3,"sLen":0,"time":79600,"type":0},{"id":1,"sLen":0,"time":79800,"type":0},{"id":3,"sLen":400,"time":80000,"type":0},{"id":0,"sLen":0,"time":86400,"type":0},{"id":2,"sLen":0,"time":86500,"type":0},{"id":3,"sLen":0,"time":86600,"type":0},{"id":2,"sLen":0,"time":86800,"type":0},{"id":0,"sLen":0,"time":87000,"type":0},{"id":1,"sLen":0,"time":87200,"type":0},{"id":0,"sLen":0,"time":87400,"type":0},{"id":1,"sLen":0,"time":87600,"type":0},{"id":3,"sLen":0,"time":87800,"type":0},{"id":3,"sLen":400,"time":88000,"type":0},{"id":0,"sLen":0,"time":89600,"type":0},{"id":2,"sLen":0,"time":89700,"type":0},{"id":3,"sLen":0,"time":89800,"type":0},{"id":2,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90200,"type":0},{"id":1,"sLen":0,"time":90400,"type":0},{"id":0,"sLen":0,"time":90600,"type":0},{"id":1,"sLen":0,"time":90800,"type":0},{"id":3,"sLen":0,"time":91000,"type":0},{"id":3,"sLen":400,"time":91200,"type":0}],"position":"dad","type":0,"characters":["parents-christmas"]},{"visible":true,"keyCount":4,"notes":[{"id":1,"sLen":0,"time":9800,"type":0},{"id":1,"sLen":0,"time":10200,"type":0},{"id":1,"sLen":0,"time":10600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":2,"sLen":0,"time":11000,"type":0},{"id":1,"sLen":0,"time":11400,"type":0},{"id":1,"sLen":0,"time":11800,"type":0},{"id":3,"sLen":0,"time":12000,"type":0},{"id":2,"sLen":0,"time":12200,"type":0},{"id":3,"sLen":0,"time":12400,"type":0},{"id":1,"sLen":0,"time":12600,"type":0},{"id":1,"sLen":0,"time":12999.999,"type":0},{"id":1,"sLen":0,"time":13399.999,"type":0},{"id":1,"sLen":0,"time":13799.999,"type":0},{"id":3,"sLen":0,"time":13999.999,"type":0},{"id":2,"sLen":0,"time":14199.999,"type":0},{"id":1,"sLen":0,"time":14599.999,"type":0},{"id":1,"sLen":0,"time":14999.999,"type":0},{"id":3,"sLen":0,"time":15199.999,"type":0},{"id":2,"sLen":0,"time":15399.999,"type":0},{"id":0,"sLen":0,"time":15599.999,"type":0},{"id":1,"sLen":0,"time":15799.999,"type":0},{"id":2,"sLen":0,"time":22400,"type":0},{"id":0,"sLen":0,"time":22500,"type":0},{"id":3,"sLen":100,"time":22600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":2,"sLen":200,"time":22900,"type":0},{"id":3,"sLen":100,"time":23200,"type":0},{"id":0,"sLen":0,"time":23600,"type":0},{"id":2,"sLen":0,"time":23800,"type":0},{"id":0,"sLen":0,"time":24200,"type":0},{"id":0,"sLen":0,"time":24600,"type":0},{"id":0,"sLen":0,"time":25000,"type":0},{"id":1,"sLen":0,"time":25100,"type":0},{"id":2,"sLen":0,"time":25200,"type":0},{"id":3,"sLen":400,"time":25400,"type":0},{"id":1,"sLen":100,"time":25999.998,"type":0},{"id":3,"sLen":100,"time":26399.998,"type":0},{"id":1,"sLen":0,"time":26799.998,"type":0},{"id":0,"sLen":0,"time":26999.998,"type":0},{"id":0,"sLen":0,"time":27399.998,"type":0},{"id":1,"sLen":0,"time":27799.998,"type":0},{"id":1,"sLen":0,"time":28199.998,"type":0},{"id":2,"sLen":300,"time":28399.998,"type":0},{"id":0,"sLen":400,"time":28799.998,"type":0},{"id":3,"sLen":0,"time":35400,"type":0},{"id":1,"sLen":0,"time":35600,"type":0},{"id":0,"sLen":0,"time":35800,"type":0},{"id":3,"sLen":0,"time":35900,"type":0},{"id":2,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":36200,"type":0},{"id":1,"sLen":0,"time":36400,"type":0},{"id":0,"sLen":0,"time":36600,"type":0},{"id":3,"sLen":0,"time":36700,"type":0},{"id":2,"sLen":0,"time":36800,"type":0},{"id":3,"sLen":0,"time":37000,"type":0},{"id":1,"sLen":0,"time":37200,"type":0},{"id":0,"sLen":0,"time":37400,"type":0},{"id":3,"sLen":0,"time":37500,"type":0},{"id":2,"sLen":0,"time":37600,"type":0},{"id":3,"sLen":0,"time":37800,"type":0},{"id":3,"sLen":0,"time":38000,"type":0},{"id":0,"sLen":0,"time":38200,"type":0},{"id":3,"sLen":0,"time":38400,"type":0},{"id":3,"sLen":0,"time":38600,"type":0},{"id":1,"sLen":0,"time":38800,"type":0},{"id":0,"sLen":0,"time":39000,"type":0},{"id":3,"sLen":0,"time":39100,"type":0},{"id":2,"sLen":0,"time":39200,"type":0},{"id":3,"sLen":0,"time":39400,"type":0},{"id":1,"sLen":0,"time":39600,"type":0},{"id":0,"sLen":0,"time":39800,"type":0},{"id":3,"sLen":0,"time":39900,"type":0},{"id":2,"sLen":0,"time":40000,"type":0},{"id":3,"sLen":0,"time":40200,"type":0},{"id":1,"sLen":0,"time":40400,"type":0},{"id":0,"sLen":0,"time":40600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":0,"sLen":0,"time":40900,"type":0},{"id":2,"sLen":100,"time":41000,"type":0},{"id":3,"sLen":0,"time":41200,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":400,"time":41600,"type":0},{"id":0,"sLen":0,"time":43200,"type":0},{"id":2,"sLen":0,"time":43300,"type":0},{"id":3,"sLen":0,"time":43400,"type":0},{"id":2,"sLen":0,"time":43600,"type":0},{"id":0,"sLen":0,"time":43800,"type":0},{"id":1,"sLen":0,"time":44000,"type":0},{"id":0,"sLen":0,"time":44200,"type":0},{"id":1,"sLen":0,"time":44400,"type":0},{"id":3,"sLen":0,"time":44600,"type":0},{"id":3,"sLen":400,"time":44800,"type":0},{"id":0,"sLen":0,"time":46400,"type":0},{"id":2,"sLen":0,"time":46500,"type":0},{"id":3,"sLen":0,"time":46600,"type":0},{"id":2,"sLen":0,"time":46800,"type":0},{"id":0,"sLen":0,"time":47000,"type":0},{"id":1,"sLen":0,"time":47200,"type":0},{"id":0,"sLen":0,"time":47400,"type":0},{"id":1,"sLen":0,"time":47600,"type":0},{"id":3,"sLen":0,"time":47800,"type":0},{"id":3,"sLen":400,"time":48000,"type":0},{"id":1,"sLen":0,"time":54599.9961,"type":0},{"id":1,"sLen":0,"time":54999.9961,"type":0},{"id":1,"sLen":0,"time":55399.9961,"type":0},{"id":2,"sLen":0,"time":55599.9961,"type":0},{"id":0,"sLen":0,"time":55699.9961,"type":0},{"id":3,"sLen":0,"time":55799.9961,"type":0},{"id":1,"sLen":0,"time":56199.9961,"type":0},{"id":1,"sLen":0,"time":56599.9961,"type":0},{"id":1,"sLen":0,"time":56999.9961,"type":0},{"id":3,"sLen":0,"time":57199.9961,"type":0},{"id":0,"sLen":0,"time":57299.9961,"type":0},{"id":3,"sLen":0,"time":57399.9961,"type":0},{"id":1,"sLen":0,"time":57799.9961,"type":0},{"id":1,"sLen":0,"time":58199.9961,"type":0},{"id":1,"sLen":0,"time":58599.9961,"type":0},{"id":2,"sLen":0,"time":58799.9961,"type":0},{"id":0,"sLen":0,"time":58899.9961,"type":0},{"id":3,"sLen":0,"time":58999.9961,"type":0},{"id":1,"sLen":0,"time":59399.9961,"type":0},{"id":1,"sLen":0,"time":59799.9961,"type":0},{"id":1,"sLen":0,"time":60199.9961,"type":0},{"id":3,"sLen":0,"time":60399.9961,"type":0},{"id":0,"sLen":0,"time":60499.9961,"type":0},{"id":3,"sLen":0,"time":60599.9961,"type":0},{"id":2,"sLen":0,"time":63999.9961,"type":0},{"id":3,"sLen":0,"time":64199.9961,"type":0},{"id":0,"sLen":0,"time":64399.9961,"type":0},{"id":3,"sLen":0,"time":64599.9961,"type":0},{"id":1,"sLen":0,"time":64799.9961,"type":0},{"id":0,"sLen":0,"time":64999.9961,"type":0},{"id":1,"sLen":0,"time":65199.9961,"type":0},{"id":3,"sLen":0,"time":65399.9961,"type":0},{"id":0,"sLen":0,"time":65600,"type":0},{"id":3,"sLen":0,"time":65700,"type":0},{"id":1,"sLen":0,"time":65800,"type":0},{"id":0,"sLen":0,"time":66000,"type":0},{"id":1,"sLen":0,"time":66100,"type":0},{"id":3,"sLen":100,"time":66200,"type":0},{"id":1,"sLen":0,"time":66400,"type":0},{"id":2,"sLen":0,"time":66600,"type":0},{"id":3,"sLen":200,"time":66800,"type":0},{"id":1,"sLen":0,"time":70400,"type":0},{"id":3,"sLen":0,"time":70600,"type":0},{"id":0,"sLen":200,"time":70800,"type":0},{"id":1,"sLen":0,"time":71200,"type":0},{"id":0,"sLen":0,"time":71400,"type":0},{"id":1,"sLen":0,"time":71600,"type":0},{"id":2,"sLen":0,"time":71800,"type":0},{"id":3,"sLen":0,"time":72000,"type":0},{"id":0,"sLen":0,"time":72100,"type":0},{"id":3,"sLen":0,"time":72200,"type":0},{"id":1,"sLen":0,"time":72400,"type":0},{"id":0,"sLen":0,"time":72500,"type":0},{"id":1,"sLen":0,"time":72600,"type":0},{"id":0,"sLen":0,"time":72700,"type":0},{"id":2,"sLen":300,"time":72800,"type":0},{"id":2,"sLen":300,"time":73200,"type":0},{"id":2,"sLen":0,"time":73600,"type":0},{"id":0,"sLen":0,"time":73700,"type":0},{"id":3,"sLen":100,"time":73800,"type":0},{"id":1,"sLen":0,"time":74000,"type":0},{"id":2,"sLen":200,"time":74100,"type":0},{"id":3,"sLen":100,"time":74400,"type":0},{"id":0,"sLen":0,"time":74800,"type":0},{"id":2,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75400,"type":0},{"id":0,"sLen":0,"time":75800,"type":0},{"id":0,"sLen":0,"time":76200,"type":0},{"id":1,"sLen":0,"time":76300,"type":0},{"id":2,"sLen":0,"time":76400,"type":0},{"id":3,"sLen":400,"time":76600,"type":0},{"id":1,"sLen":100,"time":77200,"type":0},{"id":3,"sLen":100,"time":77600,"type":0},{"id":1,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78200,"type":0},{"id":0,"sLen":0,"time":78600,"type":0},{"id":1,"sLen":0,"time":79000,"type":0},{"id":1,"sLen":0,"time":79400,"type":0},{"id":2,"sLen":300,"time":79600,"type":0},{"id":3,"sLen":0,"time":80200,"type":0},{"id":1,"sLen":0,"time":80400,"type":0},{"id":0,"sLen":0,"time":80600,"type":0},{"id":3,"sLen":0,"time":80700,"type":0},{"id":2,"sLen":0,"time":80800,"type":0},{"id":3,"sLen":0,"time":81000,"type":0},{"id":1,"sLen":0,"time":81200,"type":0},{"id":0,"sLen":0,"time":81400,"type":0},{"id":3,"sLen":0,"time":81500,"type":0},{"id":2,"sLen":0,"time":81600,"type":0},{"id":3,"sLen":0,"time":81800,"type":0},{"id":1,"sLen":0,"time":82000,"type":0},{"id":0,"sLen":0,"time":82200,"type":0},{"id":3,"sLen":0,"time":82300,"type":0},{"id":2,"sLen":0,"time":82400,"type":0},{"id":3,"sLen":0,"time":82600,"type":0},{"id":3,"sLen":0,"time":82800,"type":0},{"id":0,"sLen":0,"time":83000,"type":0},{"id":3,"sLen":0,"time":83200,"type":0},{"id":3,"sLen":0,"time":83400,"type":0},{"id":1,"sLen":0,"time":83600,"type":0},{"id":0,"sLen":0,"time":83800,"type":0},{"id":3,"sLen":0,"time":83900,"type":0},{"id":2,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84200,"type":0},{"id":1,"sLen":0,"time":84400,"type":0},{"id":0,"sLen":0,"time":84600,"type":0},{"id":3,"sLen":0,"time":84700,"type":0},{"id":2,"sLen":0,"time":84800,"type":0},{"id":3,"sLen":0,"time":85000,"type":0},{"id":1,"sLen":0,"time":85200,"type":0},{"id":0,"sLen":0,"time":85400,"type":0},{"id":1,"sLen":0,"time":85600,"type":0},{"id":0,"sLen":0,"time":85700,"type":0},{"id":2,"sLen":100,"time":85800,"type":0},{"id":3,"sLen":0,"time":86000,"type":0},{"id":1,"sLen":0,"time":86200,"type":0},{"id":3,"sLen":400,"time":86400,"type":0},{"id":0,"sLen":0,"time":88000,"type":0},{"id":2,"sLen":0,"time":88100,"type":0},{"id":3,"sLen":0,"time":88200,"type":0},{"id":2,"sLen":0,"time":88400,"type":0},{"id":0,"sLen":0,"time":88600,"type":0},{"id":1,"sLen":0,"time":88800,"type":0},{"id":0,"sLen":0,"time":89000,"type":0},{"id":1,"sLen":0,"time":89200,"type":0},{"id":3,"sLen":0,"time":89400,"type":0},{"id":3,"sLen":400,"time":89600,"type":0},{"id":0,"sLen":0,"time":91200,"type":0},{"id":2,"sLen":0,"time":91300,"type":0},{"id":3,"sLen":0,"time":91400,"type":0},{"id":2,"sLen":0,"time":91600,"type":0},{"id":0,"sLen":0,"time":91800,"type":0},{"id":1,"sLen":0,"time":92000,"type":0},{"id":0,"sLen":0,"time":92200,"type":0},{"id":1,"sLen":0,"time":92400,"type":0},{"id":3,"sLen":0,"time":92600,"type":0},{"id":3,"sLen":800,"time":92800,"type":0}],"position":"boyfriend","type":1,"characters":["bf-christmas"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf-christmas"]}],"codenameChart":true,"scrollSpeed":1.9,"chartVersion":"1.6.0","stage":"mall","noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/eggnog/charts/normal.json b/assets/songs/eggnog/charts/normal.json index 12973852..609cf537 100644 --- a/assets/songs/eggnog/charts/normal.json +++ b/assets/songs/eggnog/charts/normal.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall","song":"Eggnog","bpm":150.0,"needsVoices":true,"player1":"bf-christmas","player2":"parents-christmas","speed":1.6,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[3399.99976,1,0.0],[3799.99976,1,0.0],[4200.0,1,0.0],[4400.0,3,0.0],[4600.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[5000.0,1,0.0],[5400.0,1,0.0],[5600.0,3,0.0],[5800.0,2,0.0],[6000.0,3,0.0],[6200.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[6599.99951,1,0.0],[6999.99951,1,0.0],[7399.99951,1,0.0],[7599.99951,3,0.0],[7799.99951,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[8200.0,1,0.0],[8600.0,1,0.0],[8800.0,3,0.0],[9000.0,2,0.0],[9400.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[9800.0,1,0.0],[10200.0,1,0.0],[10600.0,1,0.0],[10800.0,3,0.0],[11000.0,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[11400.0,1,0.0],[11800.0,1,0.0],[12000.0,3,0.0],[12200.0,2,0.0],[12400.0,3,0.0],[12600.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[12999.999,1,0.0],[13399.999,1,0.0],[13799.999,1,0.0],[13999.999,3,0.0],[14199.999,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[14599.999,1,0.0],[14999.999,1,0.0],[15199.999,3,0.0],[15399.999,2,0.0],[15799.999,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[15999.999,2,0.0],[16199.999,3,100.0],[16400.0,1,0.0],[16800.0,3,100.0],[17200.0,0,0.0],[17400.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[17800.0,0,0.0],[18200.0,0,0.0],[18600.0,0,0.0],[19000.0,3,400.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[19600.0,1,100.0],[20000.0,3,100.0],[20400.0,1,0.0],[20600.0,0,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21000.0,0,0.0],[21400.0,1,0.0],[21800.0,1,0.0],[22000.0,2,300.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[22400.0,4,400.0],[22400.0,2,0.0],[22600.0,3,100.0],[22800.0,1,0.0],[23200.0,3,100.0],[23600.0,0,0.0],[23800.0,2,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[24200.0,0,0.0],[24600.0,0,0.0],[25000.0,0,0.0],[25400.0,3,400.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[25999.998,1,100.0],[26399.998,3,100.0],[26799.998,1,0.0],[26999.998,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27399.998,0,0.0],[27799.998,1,0.0],[28199.998,1,0.0],[28399.998,2,300.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[28799.998,4,400.0],[28999.998,3,0.0],[29199.998,1,0.0],[29399.998,0,0.0],[29499.998,3,0.0],[29799.998,3,0.0],[29999.998,1,0.0],[30199.998,0,0.0],[30299.998,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[30599.998,3,0.0],[30799.998,1,0.0],[30999.998,0,0.0],[31099.998,3,0.0],[31399.998,3,0.0],[31599.998,3,0.0],[31799.998,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[31999.998,3,0.0],[32199.998,3,0.0],[32399.998,1,0.0],[32599.998,0,0.0],[32699.998,3,0.0],[33000.0,3,0.0],[33200.0,1,0.0],[33400.0,0,0.0],[33500.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33800.0,3,0.0],[34000.0,1,0.0],[34200.0,0,0.0],[34400.0,1,0.0],[34500.0,0,0.0],[34800.0,3,0.0],[35000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[35200.0,7,400.0],[35400.0,3,0.0],[35600.0,1,0.0],[35800.0,0,0.0],[35900.0,3,0.0],[36200.0,3,0.0],[36400.0,1,0.0],[36600.0,0,0.0],[36700.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[37000.0,3,0.0],[37200.0,1,0.0],[37400.0,0,0.0],[37500.0,3,0.0],[37800.0,3,0.0],[38000.0,3,0.0],[38200.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[38400.0,3,0.0],[38600.0,3,0.0],[38800.0,1,0.0],[39000.0,0,0.0],[39100.0,3,0.0],[39400.0,3,0.0],[39600.0,1,0.0],[39800.0,0,0.0],[39900.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[40200.0,3,0.0],[40400.0,1,0.0],[40600.0,0,0.0],[40800.0,1,0.0],[41000.0,2,100.0],[41200.0,3,0.0],[41400.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[41600.0,0,0.0],[41600.0,7,400.0],[41800.0,3,0.0],[42000.0,3,0.0],[42200.0,0,0.0],[42600.0,0,0.0],[43000.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43200.0,7,400.0],[43200.0,0,0.0],[43400.0,3,0.0],[43600.0,3,0.0],[43800.0,0,0.0],[44200.0,0,0.0],[44600.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[44800.0,7,400.0],[44800.0,0,0.0],[45000.0,3,0.0],[45200.0,3,0.0],[45400.0,0,0.0],[45800.0,0,0.0],[46200.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46400.0,7,400.0],[46400.0,0,0.0],[46600.0,3,0.0],[46800.0,3,0.0],[47000.0,0,0.0],[47400.0,0,0.0],[47800.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48000.0,7,400.0],[48200.0,1,0.0],[48600.0,1,0.0],[49000.0,1,0.0],[49200.0,2,0.0],[49400.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49800.0,1,0.0],[50200.0,1,0.0],[50600.0,1,0.0],[50800.0,3,0.0],[50900.0,0,0.0],[51000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[51399.9961,1,0.0],[51799.9961,1,0.0],[52199.9961,1,0.0],[52399.9961,2,0.0],[52599.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52999.9961,1,0.0],[53399.9961,1,0.0],[53799.9961,1,0.0],[53999.9961,3,0.0],[54099.9961,0,0.0],[54199.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54599.9961,1,0.0],[54999.9961,1,0.0],[55399.9961,1,0.0],[55599.9961,2,0.0],[55799.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56199.9961,1,0.0],[56599.9961,1,0.0],[56999.9961,1,0.0],[57199.9961,3,0.0],[57299.9961,0,0.0],[57399.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[57799.9961,1,0.0],[58199.9961,1,0.0],[58599.9961,1,0.0],[58799.9961,2,0.0],[58999.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[59399.9961,1,0.0],[59799.9961,1,0.0],[60199.9961,1,0.0],[60399.9961,3,0.0],[60499.9961,0,0.0],[60599.9961,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[60799.9961,2,0.0],[60999.9961,3,0.0],[61199.9961,0,0.0],[61599.9961,1,0.0],[61799.9961,0,0.0],[61999.9961,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[62399.9961,0,0.0],[62599.9961,1,0.0],[62799.9961,0,0.0],[62999.9961,3,100.0],[63199.9961,1,0.0],[63399.9961,2,0.0],[63599.9961,3,200.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[63999.9961,2,0.0],[64199.9961,3,0.0],[64399.9961,0,0.0],[64799.9961,1,0.0],[64999.9961,0,0.0],[65199.9961,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[65600.0,0,0.0],[65800.0,1,0.0],[66000.0,0,0.0],[66200.0,3,100.0],[66400.0,1,0.0],[66600.0,2,0.0],[66800.0,3,200.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[67200.0,1,0.0],[67400.0,3,0.0],[67600.0,0,200.0],[68000.0,1,0.0],[68200.0,1,0.0],[68400.0,1,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[68800.0,3,0.0],[68900.0,0,100.0],[69200.0,1,0.0],[69400.0,1,0.0],[69600.0,2,300.0],[70000.0,2,300.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[70400.0,1,0.0],[70600.0,3,0.0],[70800.0,0,200.0],[71200.0,1,0.0],[71400.0,0,0.0],[71600.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[72000.0,3,0.0],[72100.0,0,100.0],[72400.0,1,0.0],[72600.0,1,0.0],[72800.0,2,300.0],[73200.0,2,300.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[73600.0,2,0.0],[73800.0,7,0.0],[73800.0,3,100.0],[74000.0,5,0.0],[74000.0,1,0.0],[74200.0,4,0.0],[74300.0,7,0.0],[74400.0,3,100.0],[74600.0,7,0.0],[74800.0,5,0.0],[74800.0,0,0.0],[75000.0,4,0.0],[75000.0,2,0.0],[75100.0,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75400.0,7,0.0],[75400.0,0,0.0],[75600.0,5,0.0],[75800.0,4,0.0],[75800.0,0,0.0],[75900.0,7,0.0],[76200.0,7,0.0],[76200.0,0,0.0],[76400.0,7,0.0],[76600.0,4,0.0],[76600.0,3,400.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[76800.0,7,0.0],[77000.0,7,0.0],[77200.0,5,0.0],[77200.0,1,100.0],[77400.0,4,0.0],[77500.0,7,0.0],[77600.0,3,100.0],[77800.0,7,0.0],[78000.0,5,0.0],[78000.0,1,0.0],[78200.0,4,0.0],[78200.0,0,0.0],[78300.0,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78600.0,7,0.0],[78600.0,0,0.0],[78800.0,5,0.0],[79000.0,4,0.0],[79000.0,1,0.0],[79200.0,5,0.0],[79300.0,4,0.0],[79400.0,1,0.0],[79600.0,7,0.0],[79600.0,2,300.0],[79800.0,5,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80000.0,7,400.0],[80200.0,3,0.0],[80400.0,1,0.0],[80600.0,0,0.0],[80700.0,3,0.0],[81000.0,3,0.0],[81200.0,1,0.0],[81400.0,0,0.0],[81500.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[81800.0,3,0.0],[82000.0,1,0.0],[82200.0,0,0.0],[82300.0,3,0.0],[82600.0,3,0.0],[82800.0,3,0.0],[83000.0,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83200.0,3,0.0],[83400.0,3,0.0],[83600.0,1,0.0],[83800.0,0,0.0],[83900.0,3,0.0],[84200.0,3,0.0],[84400.0,1,0.0],[84600.0,0,0.0],[84700.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[85000.0,3,0.0],[85200.0,1,0.0],[85400.0,0,0.0],[85600.0,1,0.0],[85700.0,0,0.0],[86000.0,3,0.0],[86200.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[86400.0,0,0.0],[86400.0,7,400.0],[86600.0,3,0.0],[86800.0,3,0.0],[87000.0,0,0.0],[87400.0,0,0.0],[87800.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[88000.0,7,400.0],[88000.0,0,0.0],[88200.0,3,0.0],[88400.0,3,0.0],[88600.0,0,0.0],[89000.0,0,0.0],[89400.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89600.0,7,400.0],[89600.0,0,0.0],[89800.0,3,0.0],[90000.0,3,0.0],[90200.0,0,0.0],[90600.0,0,0.0],[91000.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[91200.0,7,400.0],[91200.0,0,0.0],[91400.0,3,0.0],[91600.0,3,0.0],[91800.0,0,0.0],[92200.0,0,0.0],[92600.0,3,0.0]],"altAnim":true},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92800.0,3,800.0]]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"events":[{"params":[1],"time":0,"name":"Camera Movement"},{"params":[0],"time":3200,"name":"Camera Movement"},{"params":[1],"time":9600,"name":"Camera Movement"},{"params":[0],"time":16000,"name":"Camera Movement"},{"params":[1],"time":22400,"name":"Camera Movement"},{"params":[0],"time":28800,"name":"Camera Movement"},{"params":[1],"time":35200,"name":"Camera Movement"},{"params":[0],"time":41600,"name":"Camera Movement"},{"params":[1],"time":43200,"name":"Camera Movement"},{"params":[0],"time":44800,"name":"Camera Movement"},{"params":[1],"time":46400,"name":"Camera Movement"},{"params":[0],"time":48000,"name":"Camera Movement"},{"params":[1],"time":54400,"name":"Camera Movement"},{"params":[0],"time":60800,"name":"Camera Movement"},{"params":[1],"time":64000,"name":"Camera Movement"},{"params":[0],"time":67200,"name":"Camera Movement"},{"params":[1],"time":70400,"name":"Camera Movement"},{"params":[0],"time":86400,"name":"Camera Movement"},{"params":[1],"time":88000,"name":"Camera Movement"},{"params":[0],"time":89600,"name":"Camera Movement"},{"params":[1],"time":91200,"name":"Camera Movement"}],"strumLines":[{"visible":true,"keyCount":4,"notes":[{"id":1,"sLen":0,"time":3399.99976,"type":0},{"id":1,"sLen":0,"time":3799.99976,"type":0},{"id":1,"sLen":0,"time":4200,"type":0},{"id":3,"sLen":0,"time":4400,"type":0},{"id":2,"sLen":0,"time":4600,"type":0},{"id":1,"sLen":0,"time":5000,"type":0},{"id":1,"sLen":0,"time":5400,"type":0},{"id":3,"sLen":0,"time":5600,"type":0},{"id":2,"sLen":0,"time":5800,"type":0},{"id":3,"sLen":0,"time":6000,"type":0},{"id":1,"sLen":0,"time":6200,"type":0},{"id":1,"sLen":0,"time":6599.99951,"type":0},{"id":1,"sLen":0,"time":6999.99951,"type":0},{"id":1,"sLen":0,"time":7399.99951,"type":0},{"id":3,"sLen":0,"time":7599.99951,"type":0},{"id":2,"sLen":0,"time":7799.99951,"type":0},{"id":1,"sLen":0,"time":8200,"type":0},{"id":1,"sLen":0,"time":8600,"type":0},{"id":3,"sLen":0,"time":8800,"type":0},{"id":2,"sLen":0,"time":9000,"type":0},{"id":1,"sLen":0,"time":9400,"type":0},{"id":2,"sLen":0,"time":15999.999,"type":0},{"id":3,"sLen":100,"time":16199.999,"type":0},{"id":1,"sLen":0,"time":16400,"type":0},{"id":3,"sLen":100,"time":16800,"type":0},{"id":0,"sLen":0,"time":17200,"type":0},{"id":2,"sLen":0,"time":17400,"type":0},{"id":0,"sLen":0,"time":17800,"type":0},{"id":0,"sLen":0,"time":18200,"type":0},{"id":0,"sLen":0,"time":18600,"type":0},{"id":3,"sLen":400,"time":19000,"type":0},{"id":1,"sLen":100,"time":19600,"type":0},{"id":3,"sLen":100,"time":20000,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":0,"sLen":0,"time":20600,"type":0},{"id":0,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21400,"type":0},{"id":1,"sLen":0,"time":21800,"type":0},{"id":2,"sLen":300,"time":22000,"type":0},{"id":0,"sLen":400,"time":22400,"type":0},{"id":3,"sLen":0,"time":28999.998,"type":0},{"id":1,"sLen":0,"time":29199.998,"type":0},{"id":0,"sLen":0,"time":29399.998,"type":0},{"id":3,"sLen":0,"time":29499.998,"type":0},{"id":3,"sLen":0,"time":29799.998,"type":0},{"id":1,"sLen":0,"time":29999.998,"type":0},{"id":0,"sLen":0,"time":30199.998,"type":0},{"id":3,"sLen":0,"time":30299.998,"type":0},{"id":3,"sLen":0,"time":30599.998,"type":0},{"id":1,"sLen":0,"time":30799.998,"type":0},{"id":0,"sLen":0,"time":30999.998,"type":0},{"id":3,"sLen":0,"time":31099.998,"type":0},{"id":3,"sLen":0,"time":31399.998,"type":0},{"id":3,"sLen":0,"time":31599.998,"type":0},{"id":0,"sLen":0,"time":31799.998,"type":0},{"id":3,"sLen":0,"time":31999.998,"type":0},{"id":3,"sLen":0,"time":32199.998,"type":0},{"id":1,"sLen":0,"time":32399.998,"type":0},{"id":0,"sLen":0,"time":32599.998,"type":0},{"id":3,"sLen":0,"time":32699.998,"type":0},{"id":3,"sLen":0,"time":33000,"type":0},{"id":1,"sLen":0,"time":33200,"type":0},{"id":0,"sLen":0,"time":33400,"type":0},{"id":3,"sLen":0,"time":33500,"type":0},{"id":3,"sLen":0,"time":33800,"type":0},{"id":1,"sLen":0,"time":34000,"type":0},{"id":0,"sLen":0,"time":34200,"type":0},{"id":1,"sLen":0,"time":34400,"type":0},{"id":0,"sLen":0,"time":34500,"type":0},{"id":3,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":35000,"type":0},{"id":3,"sLen":400,"time":35200,"type":0},{"id":0,"sLen":0,"time":41600,"type":0},{"id":3,"sLen":0,"time":41800,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":0,"sLen":0,"time":42200,"type":0},{"id":0,"sLen":0,"time":42600,"type":0},{"id":3,"sLen":0,"time":43000,"type":0},{"id":3,"sLen":400,"time":43200,"type":0},{"id":0,"sLen":0,"time":44800,"type":0},{"id":3,"sLen":0,"time":45000,"type":0},{"id":3,"sLen":0,"time":45200,"type":0},{"id":0,"sLen":0,"time":45400,"type":0},{"id":0,"sLen":0,"time":45800,"type":0},{"id":3,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":400,"time":46400,"type":0},{"id":1,"sLen":0,"time":48200,"type":0},{"id":1,"sLen":0,"time":48600,"type":0},{"id":1,"sLen":0,"time":49000,"type":0},{"id":2,"sLen":0,"time":49200,"type":0},{"id":3,"sLen":0,"time":49400,"type":0},{"id":1,"sLen":0,"time":49800,"type":0},{"id":1,"sLen":0,"time":50200,"type":0},{"id":1,"sLen":0,"time":50600,"type":0},{"id":3,"sLen":0,"time":50800,"type":0},{"id":0,"sLen":0,"time":50900,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":1,"sLen":0,"time":51399.9961,"type":0},{"id":1,"sLen":0,"time":51799.9961,"type":0},{"id":1,"sLen":0,"time":52199.9961,"type":0},{"id":2,"sLen":0,"time":52399.9961,"type":0},{"id":3,"sLen":0,"time":52599.9961,"type":0},{"id":1,"sLen":0,"time":52999.9961,"type":0},{"id":1,"sLen":0,"time":53399.9961,"type":0},{"id":1,"sLen":0,"time":53799.9961,"type":0},{"id":3,"sLen":0,"time":53999.9961,"type":0},{"id":0,"sLen":0,"time":54099.9961,"type":0},{"id":3,"sLen":0,"time":54199.9961,"type":0},{"id":2,"sLen":0,"time":60799.9961,"type":0},{"id":3,"sLen":0,"time":60999.9961,"type":0},{"id":0,"sLen":0,"time":61199.9961,"type":0},{"id":1,"sLen":0,"time":61599.9961,"type":0},{"id":0,"sLen":0,"time":61799.9961,"type":0},{"id":1,"sLen":0,"time":61999.9961,"type":0},{"id":0,"sLen":0,"time":62399.9961,"type":0},{"id":1,"sLen":0,"time":62599.9961,"type":0},{"id":0,"sLen":0,"time":62799.9961,"type":0},{"id":3,"sLen":100,"time":62999.9961,"type":0},{"id":1,"sLen":0,"time":63199.9961,"type":0},{"id":2,"sLen":0,"time":63399.9961,"type":0},{"id":3,"sLen":200,"time":63599.9961,"type":0},{"id":1,"sLen":0,"time":67200,"type":0},{"id":3,"sLen":0,"time":67400,"type":0},{"id":0,"sLen":200,"time":67600,"type":0},{"id":1,"sLen":0,"time":68000,"type":0},{"id":1,"sLen":0,"time":68200,"type":0},{"id":1,"sLen":0,"time":68400,"type":0},{"id":3,"sLen":0,"time":68800,"type":0},{"id":0,"sLen":100,"time":68900,"type":0},{"id":1,"sLen":0,"time":69200,"type":0},{"id":1,"sLen":0,"time":69400,"type":0},{"id":2,"sLen":300,"time":69600,"type":0},{"id":2,"sLen":300,"time":70000,"type":0},{"id":3,"sLen":0,"time":73800,"type":0},{"id":1,"sLen":0,"time":74000,"type":0},{"id":0,"sLen":0,"time":74200,"type":0},{"id":3,"sLen":0,"time":74300,"type":0},{"id":3,"sLen":0,"time":74600,"type":0},{"id":1,"sLen":0,"time":74800,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":3,"sLen":0,"time":75100,"type":0},{"id":3,"sLen":0,"time":75400,"type":0},{"id":1,"sLen":0,"time":75600,"type":0},{"id":0,"sLen":0,"time":75800,"type":0},{"id":3,"sLen":0,"time":75900,"type":0},{"id":3,"sLen":0,"time":76200,"type":0},{"id":3,"sLen":0,"time":76400,"type":0},{"id":0,"sLen":0,"time":76600,"type":0},{"id":3,"sLen":0,"time":76800,"type":0},{"id":3,"sLen":0,"time":77000,"type":0},{"id":1,"sLen":0,"time":77200,"type":0},{"id":0,"sLen":0,"time":77400,"type":0},{"id":3,"sLen":0,"time":77500,"type":0},{"id":3,"sLen":0,"time":77800,"type":0},{"id":1,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78200,"type":0},{"id":3,"sLen":0,"time":78300,"type":0},{"id":3,"sLen":0,"time":78600,"type":0},{"id":1,"sLen":0,"time":78800,"type":0},{"id":0,"sLen":0,"time":79000,"type":0},{"id":1,"sLen":0,"time":79200,"type":0},{"id":0,"sLen":0,"time":79300,"type":0},{"id":3,"sLen":0,"time":79600,"type":0},{"id":1,"sLen":0,"time":79800,"type":0},{"id":3,"sLen":400,"time":80000,"type":0},{"id":0,"sLen":0,"time":86400,"type":0},{"id":3,"sLen":0,"time":86600,"type":0},{"id":3,"sLen":0,"time":86800,"type":0},{"id":0,"sLen":0,"time":87000,"type":0},{"id":0,"sLen":0,"time":87400,"type":0},{"id":3,"sLen":0,"time":87800,"type":0},{"id":3,"sLen":400,"time":88000,"type":0},{"id":0,"sLen":0,"time":89600,"type":0},{"id":3,"sLen":0,"time":89800,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90200,"type":0},{"id":0,"sLen":0,"time":90600,"type":0},{"id":3,"sLen":0,"time":91000,"type":0},{"id":3,"sLen":400,"time":91200,"type":0}],"position":"dad","type":0,"characters":["parents-christmas"]},{"visible":true,"keyCount":4,"notes":[{"id":1,"sLen":0,"time":9800,"type":0},{"id":1,"sLen":0,"time":10200,"type":0},{"id":1,"sLen":0,"time":10600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":2,"sLen":0,"time":11000,"type":0},{"id":1,"sLen":0,"time":11400,"type":0},{"id":1,"sLen":0,"time":11800,"type":0},{"id":3,"sLen":0,"time":12000,"type":0},{"id":2,"sLen":0,"time":12200,"type":0},{"id":3,"sLen":0,"time":12400,"type":0},{"id":1,"sLen":0,"time":12600,"type":0},{"id":1,"sLen":0,"time":12999.999,"type":0},{"id":1,"sLen":0,"time":13399.999,"type":0},{"id":1,"sLen":0,"time":13799.999,"type":0},{"id":3,"sLen":0,"time":13999.999,"type":0},{"id":2,"sLen":0,"time":14199.999,"type":0},{"id":1,"sLen":0,"time":14599.999,"type":0},{"id":1,"sLen":0,"time":14999.999,"type":0},{"id":3,"sLen":0,"time":15199.999,"type":0},{"id":2,"sLen":0,"time":15399.999,"type":0},{"id":1,"sLen":0,"time":15799.999,"type":0},{"id":2,"sLen":0,"time":22400,"type":0},{"id":3,"sLen":100,"time":22600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":3,"sLen":100,"time":23200,"type":0},{"id":0,"sLen":0,"time":23600,"type":0},{"id":2,"sLen":0,"time":23800,"type":0},{"id":0,"sLen":0,"time":24200,"type":0},{"id":0,"sLen":0,"time":24600,"type":0},{"id":0,"sLen":0,"time":25000,"type":0},{"id":3,"sLen":400,"time":25400,"type":0},{"id":1,"sLen":100,"time":25999.998,"type":0},{"id":3,"sLen":100,"time":26399.998,"type":0},{"id":1,"sLen":0,"time":26799.998,"type":0},{"id":0,"sLen":0,"time":26999.998,"type":0},{"id":0,"sLen":0,"time":27399.998,"type":0},{"id":1,"sLen":0,"time":27799.998,"type":0},{"id":1,"sLen":0,"time":28199.998,"type":0},{"id":2,"sLen":300,"time":28399.998,"type":0},{"id":0,"sLen":400,"time":28799.998,"type":0},{"id":3,"sLen":0,"time":35400,"type":0},{"id":1,"sLen":0,"time":35600,"type":0},{"id":0,"sLen":0,"time":35800,"type":0},{"id":3,"sLen":0,"time":35900,"type":0},{"id":3,"sLen":0,"time":36200,"type":0},{"id":1,"sLen":0,"time":36400,"type":0},{"id":0,"sLen":0,"time":36600,"type":0},{"id":3,"sLen":0,"time":36700,"type":0},{"id":3,"sLen":0,"time":37000,"type":0},{"id":1,"sLen":0,"time":37200,"type":0},{"id":0,"sLen":0,"time":37400,"type":0},{"id":3,"sLen":0,"time":37500,"type":0},{"id":3,"sLen":0,"time":37800,"type":0},{"id":3,"sLen":0,"time":38000,"type":0},{"id":0,"sLen":0,"time":38200,"type":0},{"id":3,"sLen":0,"time":38400,"type":0},{"id":3,"sLen":0,"time":38600,"type":0},{"id":1,"sLen":0,"time":38800,"type":0},{"id":0,"sLen":0,"time":39000,"type":0},{"id":3,"sLen":0,"time":39100,"type":0},{"id":3,"sLen":0,"time":39400,"type":0},{"id":1,"sLen":0,"time":39600,"type":0},{"id":0,"sLen":0,"time":39800,"type":0},{"id":3,"sLen":0,"time":39900,"type":0},{"id":3,"sLen":0,"time":40200,"type":0},{"id":1,"sLen":0,"time":40400,"type":0},{"id":0,"sLen":0,"time":40600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":2,"sLen":100,"time":41000,"type":0},{"id":3,"sLen":0,"time":41200,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":400,"time":41600,"type":0},{"id":0,"sLen":0,"time":43200,"type":0},{"id":3,"sLen":0,"time":43400,"type":0},{"id":3,"sLen":0,"time":43600,"type":0},{"id":0,"sLen":0,"time":43800,"type":0},{"id":0,"sLen":0,"time":44200,"type":0},{"id":3,"sLen":0,"time":44600,"type":0},{"id":3,"sLen":400,"time":44800,"type":0},{"id":0,"sLen":0,"time":46400,"type":0},{"id":3,"sLen":0,"time":46600,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":0,"sLen":0,"time":47000,"type":0},{"id":0,"sLen":0,"time":47400,"type":0},{"id":3,"sLen":0,"time":47800,"type":0},{"id":3,"sLen":400,"time":48000,"type":0},{"id":1,"sLen":0,"time":54599.9961,"type":0},{"id":1,"sLen":0,"time":54999.9961,"type":0},{"id":1,"sLen":0,"time":55399.9961,"type":0},{"id":2,"sLen":0,"time":55599.9961,"type":0},{"id":3,"sLen":0,"time":55799.9961,"type":0},{"id":1,"sLen":0,"time":56199.9961,"type":0},{"id":1,"sLen":0,"time":56599.9961,"type":0},{"id":1,"sLen":0,"time":56999.9961,"type":0},{"id":3,"sLen":0,"time":57199.9961,"type":0},{"id":0,"sLen":0,"time":57299.9961,"type":0},{"id":3,"sLen":0,"time":57399.9961,"type":0},{"id":1,"sLen":0,"time":57799.9961,"type":0},{"id":1,"sLen":0,"time":58199.9961,"type":0},{"id":1,"sLen":0,"time":58599.9961,"type":0},{"id":2,"sLen":0,"time":58799.9961,"type":0},{"id":3,"sLen":0,"time":58999.9961,"type":0},{"id":1,"sLen":0,"time":59399.9961,"type":0},{"id":1,"sLen":0,"time":59799.9961,"type":0},{"id":1,"sLen":0,"time":60199.9961,"type":0},{"id":3,"sLen":0,"time":60399.9961,"type":0},{"id":0,"sLen":0,"time":60499.9961,"type":0},{"id":3,"sLen":0,"time":60599.9961,"type":0},{"id":2,"sLen":0,"time":63999.9961,"type":0},{"id":3,"sLen":0,"time":64199.9961,"type":0},{"id":0,"sLen":0,"time":64399.9961,"type":0},{"id":1,"sLen":0,"time":64799.9961,"type":0},{"id":0,"sLen":0,"time":64999.9961,"type":0},{"id":1,"sLen":0,"time":65199.9961,"type":0},{"id":0,"sLen":0,"time":65600,"type":0},{"id":1,"sLen":0,"time":65800,"type":0},{"id":0,"sLen":0,"time":66000,"type":0},{"id":3,"sLen":100,"time":66200,"type":0},{"id":1,"sLen":0,"time":66400,"type":0},{"id":2,"sLen":0,"time":66600,"type":0},{"id":3,"sLen":200,"time":66800,"type":0},{"id":1,"sLen":0,"time":70400,"type":0},{"id":3,"sLen":0,"time":70600,"type":0},{"id":0,"sLen":200,"time":70800,"type":0},{"id":1,"sLen":0,"time":71200,"type":0},{"id":0,"sLen":0,"time":71400,"type":0},{"id":1,"sLen":0,"time":71600,"type":0},{"id":3,"sLen":0,"time":72000,"type":0},{"id":0,"sLen":100,"time":72100,"type":0},{"id":1,"sLen":0,"time":72400,"type":0},{"id":1,"sLen":0,"time":72600,"type":0},{"id":2,"sLen":300,"time":72800,"type":0},{"id":2,"sLen":300,"time":73200,"type":0},{"id":2,"sLen":0,"time":73600,"type":0},{"id":3,"sLen":100,"time":73800,"type":0},{"id":1,"sLen":0,"time":74000,"type":0},{"id":3,"sLen":100,"time":74400,"type":0},{"id":0,"sLen":0,"time":74800,"type":0},{"id":2,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75400,"type":0},{"id":0,"sLen":0,"time":75800,"type":0},{"id":0,"sLen":0,"time":76200,"type":0},{"id":3,"sLen":400,"time":76600,"type":0},{"id":1,"sLen":100,"time":77200,"type":0},{"id":3,"sLen":100,"time":77600,"type":0},{"id":1,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78200,"type":0},{"id":0,"sLen":0,"time":78600,"type":0},{"id":1,"sLen":0,"time":79000,"type":0},{"id":1,"sLen":0,"time":79400,"type":0},{"id":2,"sLen":300,"time":79600,"type":0},{"id":3,"sLen":0,"time":80200,"type":0},{"id":1,"sLen":0,"time":80400,"type":0},{"id":0,"sLen":0,"time":80600,"type":0},{"id":3,"sLen":0,"time":80700,"type":0},{"id":3,"sLen":0,"time":81000,"type":0},{"id":1,"sLen":0,"time":81200,"type":0},{"id":0,"sLen":0,"time":81400,"type":0},{"id":3,"sLen":0,"time":81500,"type":0},{"id":3,"sLen":0,"time":81800,"type":0},{"id":1,"sLen":0,"time":82000,"type":0},{"id":0,"sLen":0,"time":82200,"type":0},{"id":3,"sLen":0,"time":82300,"type":0},{"id":3,"sLen":0,"time":82600,"type":0},{"id":3,"sLen":0,"time":82800,"type":0},{"id":0,"sLen":0,"time":83000,"type":0},{"id":3,"sLen":0,"time":83200,"type":0},{"id":3,"sLen":0,"time":83400,"type":0},{"id":1,"sLen":0,"time":83600,"type":0},{"id":0,"sLen":0,"time":83800,"type":0},{"id":3,"sLen":0,"time":83900,"type":0},{"id":3,"sLen":0,"time":84200,"type":0},{"id":1,"sLen":0,"time":84400,"type":0},{"id":0,"sLen":0,"time":84600,"type":0},{"id":3,"sLen":0,"time":84700,"type":0},{"id":3,"sLen":0,"time":85000,"type":0},{"id":1,"sLen":0,"time":85200,"type":0},{"id":0,"sLen":0,"time":85400,"type":0},{"id":1,"sLen":0,"time":85600,"type":0},{"id":0,"sLen":0,"time":85700,"type":0},{"id":3,"sLen":0,"time":86000,"type":0},{"id":1,"sLen":0,"time":86200,"type":0},{"id":3,"sLen":400,"time":86400,"type":0},{"id":0,"sLen":0,"time":88000,"type":0},{"id":3,"sLen":0,"time":88200,"type":0},{"id":3,"sLen":0,"time":88400,"type":0},{"id":0,"sLen":0,"time":88600,"type":0},{"id":0,"sLen":0,"time":89000,"type":0},{"id":3,"sLen":0,"time":89400,"type":0},{"id":3,"sLen":400,"time":89600,"type":0},{"id":0,"sLen":0,"time":91200,"type":0},{"id":3,"sLen":0,"time":91400,"type":0},{"id":3,"sLen":0,"time":91600,"type":0},{"id":0,"sLen":0,"time":91800,"type":0},{"id":0,"sLen":0,"time":92200,"type":0},{"id":3,"sLen":0,"time":92600,"type":0},{"id":3,"sLen":800,"time":92800,"type":0}],"position":"boyfriend","type":1,"characters":["bf-christmas"]},{"keyCount":4,"notes":[],"visible":false,"position":"girlfriend","type":2,"characters":["gf-christmas"]}],"scrollSpeed":1.6,"chartVersion":"1.6.0","stage":"mall","codenameChart":true,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/eggnog/events.json b/assets/songs/eggnog/events.json new file mode 100644 index 00000000..8d13b58b --- /dev/null +++ b/assets/songs/eggnog/events.json @@ -0,0 +1 @@ +{"events":[{"params":[true,false,0],"time":16000,"name":"Alt Animation Toggle"},{"params":[false,false,0],"time":28800,"name":"Alt Animation Toggle"},{"params":[true,false,0],"time":41600,"name":"Alt Animation Toggle"},{"params":[false,false,0],"time":48000,"name":"Alt Animation Toggle"},{"params":[true,false,0],"time":60800,"name":"Alt Animation Toggle"},{"params":[false,false,0],"time":73600,"name":"Alt Animation Toggle"},{"params":[true,false,0],"time":86400,"name":"Alt Animation Toggle"}]} \ No newline at end of file diff --git a/assets/songs/eggnog/scripts/week5.hx b/assets/songs/eggnog/scripts/week5.hx deleted file mode 100644 index 8c2f4fb3..00000000 --- a/assets/songs/eggnog/scripts/week5.hx +++ /dev/null @@ -1,4 +0,0 @@ -function onNoteCreation(event) { - if (event.mustHit) - event.animSuffix = ""; -} \ No newline at end of file diff --git a/assets/songs/milf/events.json b/assets/songs/milf/events.json new file mode 100644 index 00000000..67952fcd --- /dev/null +++ b/assets/songs/milf/events.json @@ -0,0 +1 @@ +{"events":[{"params":[1,1],"time":56000,"name":"Camera Modulo Change"},{"params":[4,1],"time":66666.6666666667,"name":"Camera Modulo Change"}]} \ No newline at end of file diff --git a/assets/songs/milf/scripts/bump.hx b/assets/songs/milf/scripts/bump.hx deleted file mode 100644 index 51c4a158..00000000 --- a/assets/songs/milf/scripts/bump.hx +++ /dev/null @@ -1,8 +0,0 @@ -function beatHit(curBeat:Int) { - switch(curBeat) { - case 168: - camZoomingInterval = 1; - case 200: - camZoomingInterval = 4; - } -} \ No newline at end of file diff --git a/assets/songs/stress/charts/easy.json b/assets/songs/stress/charts/easy.json index fcd5b581..9b9183f1 100644 --- a/assets/songs/stress/charts/easy.json +++ b/assets/songs/stress/charts/easy.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":5393.25842696629,"name":"Camera Movement"},{"params":[0],"time":10786.5168539326,"name":"Camera Movement"},{"params":[1],"time":14831.4606741573,"name":"Camera Movement"},{"params":[0],"time":21573.0337078652,"name":"Camera Movement"},{"params":[1],"time":24269.6629213483,"name":"Camera Movement"},{"params":[0],"time":26966.2921348315,"name":"Camera Movement"},{"params":[1],"time":28314.606741573,"name":"Camera Movement"},{"params":[0],"time":31011.2359550562,"name":"Camera Movement"},{"params":[1],"time":37752.808988764,"name":"Camera Movement"},{"params":[0],"time":43146.0674157303,"name":"Camera Movement"},{"params":[1],"time":48539.3258426966,"name":"Camera Movement"},{"params":[0],"time":53932.5842696629,"name":"Camera Movement"},{"params":[1],"time":59325.8426966292,"name":"Camera Movement"},{"params":[0],"time":62022.4719101124,"name":"Camera Movement"},{"params":[0,"good!"],"time":62022.4719101124,"name":"Play Animation"},{"params":[1],"time":67415.7303370787,"name":"Camera Movement"},{"params":[0],"time":75505.6179775281,"name":"Camera Movement"},{"params":[1],"time":80898.8764044945,"name":"Camera Movement"},{"params":[0],"time":86292.1348314608,"name":"Camera Movement"},{"params":[1],"time":91685.3932584271,"name":"Camera Movement"},{"params":[0],"time":97078.6516853934,"name":"Camera Movement"},{"params":[1],"time":102471.91011236,"name":"Camera Movement"},{"params":[0],"time":107865.168539326,"name":"Camera Movement"},{"params":[1],"time":113258.426966292,"name":"Camera Movement"}],"scrollSpeed":1.75,"strumLines":[{"notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":0,"sLen":0,"time":337.078644,"type":0},{"id":0,"sLen":0,"time":674.1573,"type":0},{"id":2,"sLen":0,"time":1011.23596,"type":0},{"id":1,"sLen":0,"time":1348.31458,"type":0},{"id":3,"sLen":0,"time":1685.39331,"type":0},{"id":1,"sLen":0,"time":2022.47192,"type":0},{"id":1,"sLen":0,"time":2359.55054,"type":0},{"id":0,"sLen":0,"time":2528.08984,"type":0},{"id":2,"sLen":0,"time":2696.62915,"type":0},{"id":0,"sLen":0,"time":3033.70776,"type":0},{"id":0,"sLen":0,"time":3370.78662,"type":0},{"id":2,"sLen":0,"time":3707.86523,"type":0},{"id":1,"sLen":0,"time":4044.94385,"type":0},{"id":3,"sLen":0,"time":4382.02246,"type":0},{"id":1,"sLen":0,"time":4719.101,"type":0},{"id":1,"sLen":0,"time":5056.17969,"type":0},{"id":0,"sLen":0,"time":5224.719,"type":0},{"id":0,"sLen":0,"time":11123.5957,"type":0},{"id":1,"sLen":0,"time":11292.1348,"type":0},{"id":3,"sLen":252.808989999996,"time":11460.6738,"type":0},{"id":1,"sLen":252.80899,"time":11797.7529,"type":0},{"id":0,"sLen":0,"time":12134.8311,"type":0},{"id":0,"sLen":0,"time":12471.91,"type":0},{"id":0,"sLen":0,"time":12808.9883,"type":0},{"id":3,"sLen":0,"time":13146.0674,"type":0},{"id":1,"sLen":0,"time":13483.1465,"type":0},{"id":3,"sLen":0,"time":13820.2246,"type":0},{"id":1,"sLen":0,"time":13988.7637,"type":0},{"id":3,"sLen":0,"time":14325.8428,"type":0},{"id":1,"sLen":0,"time":14494.3818,"type":0},{"id":1,"sLen":0,"time":14831.4609,"type":0},{"id":3,"sLen":0,"time":15168.5391,"type":0},{"id":1,"sLen":0,"time":15505.6182,"type":0},{"id":1,"sLen":0,"time":15842.6963,"type":0},{"id":0,"sLen":0,"time":16011.2363,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":0,"sLen":0,"time":21910.1133,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":0,"sLen":0,"time":22584.27,"type":0},{"id":3,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":0,"time":23595.5059,"type":0},{"id":1,"sLen":0,"time":23932.584,"type":0},{"id":0,"sLen":0,"time":24269.6621,"type":0},{"id":3,"sLen":0,"time":26966.293,"type":0},{"id":0,"sLen":0,"time":27303.3711,"type":0},{"id":3,"sLen":0,"time":27640.45,"type":0},{"id":0,"sLen":0,"time":27977.5273,"type":0},{"id":3,"sLen":0,"time":28314.6074,"type":0},{"id":2,"sLen":0,"time":28651.6855,"type":0},{"id":3,"sLen":0,"time":28988.7637,"type":0},{"id":1,"sLen":0,"time":29325.8418,"type":0},{"id":0,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":31011.2363,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":0,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":0,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":0,"sLen":0,"time":32359.55,"type":0},{"id":2,"sLen":0,"time":32696.6289,"type":0},{"id":0,"sLen":0,"time":33033.707,"type":0},{"id":2,"sLen":0,"time":33370.7852,"type":0},{"id":3,"sLen":0,"time":33539.3242,"type":0},{"id":1,"sLen":0,"time":33707.8633,"type":0},{"id":0,"sLen":0,"time":34044.9453,"type":0},{"id":1,"sLen":0,"time":34382.0234,"type":0},{"id":1,"sLen":0,"time":34719.1,"type":0},{"id":0,"sLen":0,"time":34887.64,"type":0},{"id":3,"sLen":0,"time":35056.18,"type":0},{"id":2,"sLen":0,"time":35393.2578,"type":0},{"id":0,"sLen":0,"time":35730.3359,"type":0},{"id":2,"sLen":0,"time":36067.4141,"type":0},{"id":3,"sLen":0,"time":36404.4961,"type":0},{"id":0,"sLen":0,"time":36741.5742,"type":0},{"id":0,"sLen":0,"time":37078.6523,"type":0},{"id":1,"sLen":0,"time":37247.19,"type":0},{"id":1,"sLen":0,"time":37415.73,"type":0},{"id":0,"sLen":0,"time":37584.27,"type":0},{"id":3,"sLen":0,"time":43146.0664,"type":0},{"id":1,"sLen":0,"time":43314.6055,"type":0},{"id":0,"sLen":0,"time":43483.1445,"type":0},{"id":1,"sLen":0,"time":43820.2266,"type":0},{"id":0,"sLen":0,"time":44157.3047,"type":0},{"id":2,"sLen":0,"time":44494.3828,"type":0},{"id":3,"sLen":0,"time":44662.92,"type":0},{"id":2,"sLen":0,"time":44831.46,"type":0},{"id":3,"sLen":0,"time":45168.54,"type":0},{"id":2,"sLen":0,"time":45505.6172,"type":0},{"id":3,"sLen":0,"time":45842.6953,"type":0},{"id":1,"sLen":0,"time":46179.7734,"type":0},{"id":0,"sLen":0,"time":46516.8555,"type":0},{"id":1,"sLen":0,"time":46853.9336,"type":0},{"id":2,"sLen":0,"time":47191.01,"type":0},{"id":1,"sLen":0,"time":47528.09,"type":0},{"id":1,"sLen":0,"time":47865.168,"type":0},{"id":1,"sLen":0,"time":48202.2461,"type":0},{"id":2,"sLen":0,"time":53932.5859,"type":0},{"id":0,"sLen":0,"time":54269.6641,"type":0},{"id":0,"sLen":0,"time":54606.7422,"type":0},{"id":2,"sLen":0,"time":54943.82,"type":0},{"id":1,"sLen":0,"time":55280.9,"type":0},{"id":3,"sLen":0,"time":55617.9766,"type":0},{"id":1,"sLen":0,"time":55955.0547,"type":0},{"id":1,"sLen":0,"time":56292.1367,"type":0},{"id":0,"sLen":0,"time":56460.6758,"type":0},{"id":2,"sLen":0,"time":56629.2148,"type":0},{"id":0,"sLen":0,"time":56966.293,"type":0},{"id":0,"sLen":0,"time":57303.37,"type":0},{"id":2,"sLen":0,"time":57640.45,"type":0},{"id":1,"sLen":0,"time":57977.5273,"type":0},{"id":3,"sLen":0,"time":58314.6055,"type":0},{"id":1,"sLen":0,"time":58651.6836,"type":0},{"id":1,"sLen":0,"time":58988.7656,"type":0},{"id":0,"sLen":0,"time":59157.3047,"type":0},{"id":0,"sLen":168.539321999997,"time":59325.8438,"type":0},{"id":0,"sLen":168.539321999997,"time":60000,"type":0},{"id":0,"sLen":168.539322000004,"time":60674.1563,"type":0},{"id":0,"sLen":168.539321999997,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":62022.4719101124,"type":1},{"id":1,"sLen":0,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":3,"sLen":0,"time":65393.2578,"type":0},{"id":1,"sLen":0,"time":65730.3359,"type":0},{"id":0,"sLen":0,"time":66067.4141,"type":0},{"id":0,"sLen":0,"time":66404.49,"type":0},{"id":0,"sLen":0,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":66910.11,"type":0},{"id":0,"sLen":0,"time":67078.65,"type":0},{"id":1,"sLen":0,"time":67415.73,"type":0},{"id":3,"sLen":0,"time":67752.81,"type":0},{"id":1,"sLen":0,"time":67921.35,"type":0},{"id":3,"sLen":0,"time":68258.43,"type":0},{"id":1,"sLen":0,"time":68426.97,"type":0},{"id":1,"sLen":0,"time":68764.05,"type":0},{"id":3,"sLen":0,"time":69101.125,"type":0},{"id":1,"sLen":0,"time":69438.2,"type":0},{"id":1,"sLen":0,"time":69775.28,"type":0},{"id":0,"sLen":0,"time":69943.82,"type":0},{"id":0,"sLen":0,"time":75505.62,"type":0},{"id":0,"sLen":0,"time":75842.6953,"type":0},{"id":3,"sLen":0,"time":76179.77,"type":0},{"id":0,"sLen":0,"time":76516.85,"type":0},{"id":3,"sLen":0,"time":76853.93,"type":0},{"id":3,"sLen":0,"time":77191.01,"type":0},{"id":0,"sLen":0,"time":77359.55,"type":0},{"id":1,"sLen":252.808990000005,"time":77528.0859,"type":0},{"id":0,"sLen":0,"time":77865.17,"type":0},{"id":3,"sLen":0,"time":78202.25,"type":0},{"id":1,"sLen":0,"time":78539.33,"type":0},{"id":0,"sLen":0,"time":78876.41,"type":0},{"id":3,"sLen":0,"time":79213.4844,"type":0},{"id":1,"sLen":0,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":79803.37,"type":0},{"id":1,"sLen":0,"time":80056.18,"type":0},{"id":0,"sLen":0,"time":80224.72,"type":0},{"id":2,"sLen":0,"time":80561.8,"type":0},{"id":0,"sLen":0,"time":86292.13,"type":0},{"id":1,"sLen":0,"time":86629.21,"type":0},{"id":3,"sLen":0,"time":86966.29,"type":0},{"id":0,"sLen":0,"time":87303.37,"type":0},{"id":0,"sLen":0,"time":87640.45,"type":0},{"id":1,"sLen":0,"time":87977.53,"type":0},{"id":3,"sLen":0,"time":88314.61,"type":0},{"id":0,"sLen":0,"time":88651.69,"type":0},{"id":2,"sLen":0,"time":88988.7656,"type":0},{"id":3,"sLen":0,"time":89325.84,"type":0},{"id":0,"sLen":0,"time":89662.92,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.08,"type":0},{"id":3,"sLen":0,"time":90674.16,"type":0},{"id":0,"sLen":0,"time":91011.2344,"type":0},{"id":3,"sLen":337.078643999994,"time":91348.31,"type":0},{"id":2,"sLen":0,"time":97078.65,"type":0},{"id":0,"sLen":0,"time":97415.73,"type":0},{"id":2,"sLen":0,"time":97584.27,"type":0},{"id":0,"sLen":0,"time":97921.35,"type":0},{"id":2,"sLen":0,"time":98089.89,"type":0},{"id":0,"sLen":0,"time":98426.97,"type":0},{"id":2,"sLen":0,"time":98595.51,"type":0},{"id":0,"sLen":0,"time":98932.5859,"type":0},{"id":1,"sLen":0,"time":99101.125,"type":0},{"id":1,"sLen":0,"time":99438.2,"type":0},{"id":2,"sLen":0,"time":99775.28,"type":0},{"id":0,"sLen":0,"time":100112.359,"type":0},{"id":2,"sLen":0,"time":100280.9,"type":0},{"id":0,"sLen":0,"time":100617.977,"type":0},{"id":2,"sLen":0,"time":100786.516,"type":0},{"id":0,"sLen":0,"time":101123.594,"type":0},{"id":2,"sLen":0,"time":101292.133,"type":0},{"id":0,"sLen":0,"time":101629.211,"type":0},{"id":1,"sLen":0,"time":101797.75,"type":0},{"id":1,"sLen":0,"time":102134.828,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":0,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":0,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":0,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":0,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104831.461,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":0,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":0,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":0,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":0,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107528.086,"type":0},{"id":1,"sLen":0,"time":107865.172,"type":0},{"id":1,"sLen":0,"time":108202.25,"type":0},{"id":0,"sLen":0,"time":108370.789,"type":0},{"id":1,"sLen":0,"time":108539.328,"type":0},{"id":1,"sLen":0,"time":108876.406,"type":0},{"id":0,"sLen":0,"time":109044.945,"type":0},{"id":1,"sLen":0,"time":109213.484,"type":0},{"id":1,"sLen":0,"time":109550.563,"type":0},{"id":0,"sLen":0,"time":109719.1,"type":0},{"id":1,"sLen":0,"time":109887.641,"type":0},{"id":1,"sLen":0,"time":110224.719,"type":0},{"id":0,"sLen":0,"time":110393.258,"type":0},{"id":2,"sLen":0,"time":113258.43,"type":0},{"id":3,"sLen":0,"time":113595.508,"type":0},{"id":0,"sLen":0,"time":113932.586,"type":0},{"id":1,"sLen":0,"time":114269.664,"type":0},{"id":1,"sLen":0,"time":114606.742,"type":0},{"id":3,"sLen":0,"time":114943.82,"type":0},{"id":1,"sLen":0,"time":115280.9,"type":0},{"id":1,"sLen":0,"time":115617.977,"type":0},{"id":0,"sLen":0,"time":115786.516,"type":0},{"id":2,"sLen":589.887633999984,"time":115955.055,"type":0}],"position":"dad","characters":["tankman"],"visible":true,"type":0},{"notes":[{"id":2,"sLen":0,"time":5393.2583,"type":0},{"id":0,"sLen":0,"time":5730.337,"type":0},{"id":0,"sLen":0,"time":6067.41553,"type":0},{"id":2,"sLen":0,"time":6404.494,"type":0},{"id":1,"sLen":0,"time":6741.573,"type":0},{"id":3,"sLen":0,"time":7078.652,"type":0},{"id":1,"sLen":0,"time":7415.73047,"type":0},{"id":1,"sLen":0,"time":7752.809,"type":0},{"id":0,"sLen":0,"time":7921.348,"type":0},{"id":2,"sLen":0,"time":8089.8877,"type":0},{"id":0,"sLen":0,"time":8426.966,"type":0},{"id":0,"sLen":0,"time":8764.045,"type":0},{"id":2,"sLen":0,"time":9101.124,"type":0},{"id":1,"sLen":0,"time":9438.202,"type":0},{"id":3,"sLen":0,"time":9775.281,"type":0},{"id":1,"sLen":0,"time":10112.3594,"type":0},{"id":1,"sLen":0,"time":10449.4385,"type":0},{"id":0,"sLen":0,"time":10617.9775,"type":0},{"id":0,"sLen":252.808990000001,"time":14831.4609,"type":0},{"id":1,"sLen":252.80899,"time":15168.5391,"type":0},{"id":3,"sLen":252.80899,"time":15505.6182,"type":0},{"id":1,"sLen":252.808990000001,"time":15842.6963,"type":0},{"id":3,"sLen":0,"time":16179.7754,"type":0},{"id":0,"sLen":0,"time":16516.8535,"type":0},{"id":1,"sLen":0,"time":16685.3926,"type":0},{"id":3,"sLen":252.808990000001,"time":16853.9316,"type":0},{"id":1,"sLen":252.808990000001,"time":17191.0117,"type":0},{"id":0,"sLen":0,"time":17528.09,"type":0},{"id":0,"sLen":0,"time":17865.168,"type":0},{"id":0,"sLen":0,"time":18202.248,"type":0},{"id":3,"sLen":0,"time":18539.3262,"type":0},{"id":1,"sLen":0,"time":18876.4043,"type":0},{"id":3,"sLen":0,"time":19213.4824,"type":0},{"id":1,"sLen":0,"time":19382.0215,"type":0},{"id":3,"sLen":0,"time":19719.1016,"type":0},{"id":1,"sLen":0,"time":19887.64,"type":0},{"id":1,"sLen":0,"time":20224.7188,"type":0},{"id":3,"sLen":0,"time":20561.7969,"type":0},{"id":1,"sLen":0,"time":20898.877,"type":0},{"id":1,"sLen":0,"time":21235.9551,"type":0},{"id":0,"sLen":0,"time":21404.4941,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":3,"sLen":0,"time":21910.1133,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":3,"sLen":0,"time":22584.27,"type":0},{"id":1,"sLen":0,"time":22921.3477,"type":0},{"id":1,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":252.808990000001,"time":23595.5059,"type":0},{"id":0,"sLen":252.808990000012,"time":23932.584,"type":0},{"id":3,"sLen":0,"time":24269.6621,"type":0},{"id":0,"sLen":0,"time":24606.7422,"type":0},{"id":3,"sLen":0,"time":24943.82,"type":0},{"id":0,"sLen":0,"time":25280.8984,"type":0},{"id":3,"sLen":0,"time":25617.9766,"type":0},{"id":2,"sLen":0,"time":25955.0566,"type":0},{"id":3,"sLen":0,"time":26292.1348,"type":0},{"id":1,"sLen":0,"time":26629.2129,"type":0},{"id":2,"sLen":589.88763400001,"time":26966.293,"type":0},{"id":3,"sLen":589.887633999992,"time":27640.45,"type":0},{"id":1,"sLen":589.887633999999,"time":28314.6074,"type":0},{"id":0,"sLen":337.078644000008,"time":28988.7637,"type":0},{"id":3,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":3,"sLen":0,"time":31011.2363,"type":0},{"id":2,"sLen":0,"time":31348.3145,"type":0},{"id":3,"sLen":0,"time":31685.3926,"type":0},{"id":1,"sLen":0,"time":32022.4727,"type":0},{"id":1,"sLen":674.157300000003,"time":32359.55,"type":0},{"id":0,"sLen":0,"time":37752.81,"type":0},{"id":2,"sLen":0,"time":38089.8867,"type":0},{"id":0,"sLen":0,"time":38426.9648,"type":0},{"id":2,"sLen":0,"time":38764.043,"type":0},{"id":3,"sLen":0,"time":38932.5859,"type":0},{"id":1,"sLen":0,"time":39101.125,"type":0},{"id":0,"sLen":0,"time":39438.2031,"type":0},{"id":1,"sLen":0,"time":39775.28,"type":0},{"id":1,"sLen":0,"time":40112.36,"type":0},{"id":0,"sLen":0,"time":40280.9,"type":0},{"id":3,"sLen":0,"time":40449.4375,"type":0},{"id":2,"sLen":0,"time":40786.5156,"type":0},{"id":0,"sLen":0,"time":41123.5938,"type":0},{"id":2,"sLen":0,"time":41460.6758,"type":0},{"id":3,"sLen":0,"time":41797.7539,"type":0},{"id":0,"sLen":0,"time":42134.832,"type":0},{"id":0,"sLen":0,"time":42471.91,"type":0},{"id":1,"sLen":0,"time":42640.45,"type":0},{"id":1,"sLen":0,"time":42808.99,"type":0},{"id":0,"sLen":0,"time":42977.5273,"type":0},{"id":3,"sLen":0,"time":48539.3242,"type":0},{"id":1,"sLen":0,"time":48707.8633,"type":0},{"id":0,"sLen":0,"time":48876.4063,"type":0},{"id":1,"sLen":0,"time":49213.4844,"type":0},{"id":0,"sLen":0,"time":49550.5625,"type":0},{"id":2,"sLen":0,"time":49887.64,"type":0},{"id":3,"sLen":0,"time":50056.18,"type":0},{"id":2,"sLen":0,"time":50224.72,"type":0},{"id":3,"sLen":0,"time":50561.7969,"type":0},{"id":2,"sLen":0,"time":50898.875,"type":0},{"id":3,"sLen":0,"time":51235.9531,"type":0},{"id":1,"sLen":0,"time":51573.0352,"type":0},{"id":0,"sLen":0,"time":51910.1133,"type":0},{"id":1,"sLen":0,"time":52247.19,"type":0},{"id":2,"sLen":0,"time":52584.27,"type":0},{"id":1,"sLen":0,"time":52921.3477,"type":0},{"id":1,"sLen":0,"time":53258.4258,"type":0},{"id":1,"sLen":0,"time":53595.5039,"type":0},{"id":1,"sLen":1011.23595999998,"time":53932.5859,"type":0},{"id":2,"sLen":505.617981000003,"time":55955.0547,"type":0},{"id":1,"sLen":674.157299999999,"time":56629.2148,"type":0},{"id":0,"sLen":252.808989999998,"time":58651.6836,"type":0},{"id":2,"sLen":0,"time":58988.7656,"type":0},{"id":2,"sLen":0,"time":59325.8438,"type":0},{"id":0,"sLen":0,"time":59662.92,"type":0},{"id":0,"sLen":0,"time":60000,"type":0},{"id":2,"sLen":0,"time":60337.08,"type":0},{"id":1,"sLen":0,"time":60674.1563,"type":0},{"id":3,"sLen":0,"time":61011.2344,"type":0},{"id":1,"sLen":0,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":61685.3945,"type":0},{"id":0,"sLen":0,"time":61853.9336,"type":0},{"id":2,"sLen":0,"time":62022.4727,"type":0},{"id":0,"sLen":0,"time":62359.55,"type":0},{"id":0,"sLen":0,"time":62696.63,"type":0},{"id":2,"sLen":0,"time":63033.707,"type":0},{"id":1,"sLen":0,"time":63370.7852,"type":0},{"id":3,"sLen":0,"time":63707.8633,"type":0},{"id":1,"sLen":0,"time":64044.9453,"type":0},{"id":1,"sLen":0,"time":64382.0234,"type":0},{"id":0,"sLen":0,"time":64550.5625,"type":0},{"id":1,"sLen":0,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":3,"sLen":0,"time":65393.2578,"type":0},{"id":1,"sLen":0,"time":65730.3359,"type":0},{"id":3,"sLen":505.617981000003,"time":66067.4141,"type":0},{"id":1,"sLen":505.617981000003,"time":66741.57,"type":0},{"id":0,"sLen":505.617981000003,"time":67415.73,"type":0},{"id":1,"sLen":0,"time":68089.89,"type":0},{"id":0,"sLen":0,"time":68426.97,"type":0},{"id":3,"sLen":589.887633999999,"time":68764.05,"type":0},{"id":0,"sLen":337.078643999994,"time":69438.2,"type":0},{"id":1,"sLen":0,"time":70112.36,"type":0},{"id":0,"sLen":0,"time":70449.44,"type":0},{"id":3,"sLen":0,"time":70786.5156,"type":0},{"id":1,"sLen":0,"time":71123.59,"type":0},{"id":0,"sLen":0,"time":71460.67,"type":0},{"id":0,"sLen":0,"time":71797.75,"type":0},{"id":0,"sLen":0,"time":72134.83,"type":0},{"id":0,"sLen":0,"time":72303.37,"type":0},{"id":0,"sLen":0,"time":72471.91,"type":0},{"id":1,"sLen":0,"time":72808.99,"type":0},{"id":3,"sLen":0,"time":73146.07,"type":0},{"id":1,"sLen":0,"time":73314.61,"type":0},{"id":3,"sLen":0,"time":73651.69,"type":0},{"id":1,"sLen":0,"time":73820.23,"type":0},{"id":1,"sLen":0,"time":74157.3047,"type":0},{"id":3,"sLen":0,"time":74494.38,"type":0},{"id":1,"sLen":0,"time":74831.46,"type":0},{"id":1,"sLen":0,"time":75168.54,"type":0},{"id":0,"sLen":0,"time":75337.08,"type":0},{"id":1,"sLen":1264.04492,"time":75505.62,"type":0},{"id":0,"sLen":1264.04492,"time":76853.93,"type":0},{"id":3,"sLen":1264.04492,"time":78202.25,"type":0},{"id":0,"sLen":1011.23596000001,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":80898.875,"type":0},{"id":0,"sLen":0,"time":81235.95,"type":0},{"id":3,"sLen":0,"time":81573.03,"type":0},{"id":0,"sLen":0,"time":81910.11,"type":0},{"id":3,"sLen":0,"time":82247.19,"type":0},{"id":3,"sLen":0,"time":82584.27,"type":0},{"id":0,"sLen":0,"time":82752.81,"type":0},{"id":1,"sLen":252.808990000005,"time":82921.35,"type":0},{"id":0,"sLen":0,"time":83258.43,"type":0},{"id":3,"sLen":0,"time":83595.51,"type":0},{"id":1,"sLen":0,"time":83932.5859,"type":0},{"id":0,"sLen":0,"time":84269.6641,"type":0},{"id":3,"sLen":0,"time":84606.74,"type":0},{"id":1,"sLen":0,"time":84943.82,"type":0},{"id":0,"sLen":0,"time":85196.63,"type":0},{"id":1,"sLen":0,"time":85449.44,"type":0},{"id":0,"sLen":0,"time":85617.98,"type":0},{"id":2,"sLen":0,"time":85955.0547,"type":0},{"id":0,"sLen":0,"time":91685.39,"type":0},{"id":1,"sLen":0,"time":92022.47,"type":0},{"id":3,"sLen":0,"time":92359.55,"type":0},{"id":0,"sLen":0,"time":92696.63,"type":0},{"id":0,"sLen":0,"time":93033.71,"type":0},{"id":1,"sLen":0,"time":93370.79,"type":0},{"id":3,"sLen":0,"time":93707.87,"type":0},{"id":0,"sLen":0,"time":94044.9453,"type":0},{"id":2,"sLen":0,"time":94382.02,"type":0},{"id":3,"sLen":0,"time":94719.1,"type":0},{"id":0,"sLen":0,"time":95056.18,"type":0},{"id":3,"sLen":0,"time":95393.26,"type":0},{"id":0,"sLen":0,"time":95730.3359,"type":0},{"id":3,"sLen":0,"time":96067.4141,"type":0},{"id":0,"sLen":0,"time":96404.49,"type":0},{"id":3,"sLen":337.078643999994,"time":96741.57,"type":0},{"id":0,"sLen":589.887633999955,"time":98426.97,"type":0},{"id":2,"sLen":589.887633999999,"time":99101.125,"type":0},{"id":0,"sLen":589.887633999999,"time":101123.594,"type":0},{"id":2,"sLen":337.078644000008,"time":101797.75,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":3,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":3,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":3,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":3,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104831.461,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":3,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":3,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":3,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":3,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107528.086,"type":0},{"id":3,"sLen":589.887634000028,"time":107865.172,"type":0},{"id":1,"sLen":589.887634000028,"time":108539.328,"type":0},{"id":0,"sLen":589.887633999999,"time":109213.484,"type":0},{"id":1,"sLen":589.887633999999,"time":109887.641,"type":0},{"id":1,"sLen":0,"time":110561.8,"type":0},{"id":1,"sLen":0,"time":110898.875,"type":0},{"id":0,"sLen":0,"time":111067.414,"type":0},{"id":1,"sLen":0,"time":111235.953,"type":0},{"id":1,"sLen":0,"time":111573.031,"type":0},{"id":0,"sLen":0,"time":111741.57,"type":0},{"id":1,"sLen":0,"time":111910.109,"type":0},{"id":1,"sLen":0,"time":112247.188,"type":0},{"id":0,"sLen":0,"time":112415.727,"type":0},{"id":1,"sLen":0,"time":112584.273,"type":0},{"id":1,"sLen":0,"time":112921.352,"type":0},{"id":0,"sLen":0,"time":113089.891,"type":0},{"id":3,"sLen":589.887633999999,"time":113258.43,"type":0},{"id":1,"sLen":589.887633999999,"time":113932.586,"type":0},{"id":0,"sLen":589.887633999984,"time":114606.742,"type":0},{"id":1,"sLen":337.078643999994,"time":115280.9,"type":0},{"id":2,"sLen":0,"time":115955.055,"type":0},{"id":3,"sLen":0,"time":116292.133,"type":0},{"id":0,"sLen":0,"time":116629.211,"type":0},{"id":1,"sLen":0,"time":116966.289,"type":0},{"id":1,"sLen":0,"time":117303.367,"type":0},{"id":3,"sLen":0,"time":117640.453,"type":0},{"id":1,"sLen":0,"time":117977.531,"type":0},{"id":1,"sLen":0,"time":118314.609,"type":0},{"id":0,"sLen":0,"time":118483.148,"type":0}],"position":"boyfriend","characters":["bf-holding-gf"],"visible":true,"type":1},{"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539325842697,"type":0},{"id":3,"sLen":0,"time":252.808988764045,"type":0},{"id":3,"sLen":0,"time":421.348314606742,"type":0},{"id":0,"sLen":0,"time":589.887640449438,"type":0},{"id":3,"sLen":0,"time":758.426966292135,"type":0},{"id":3,"sLen":0,"time":842.696629213483,"type":0},{"id":0,"sLen":0,"time":1011.23595505618,"type":0},{"id":0,"sLen":0,"time":1179.77528089888,"type":0},{"id":0,"sLen":0,"time":1264.04494382022,"type":0},{"id":3,"sLen":0,"time":1348.31460674157,"type":0},{"id":0,"sLen":0,"time":1516.85393258427,"type":0},{"id":0,"sLen":0,"time":1601.12359550562,"type":0},{"id":3,"sLen":0,"time":1853.93258426966,"type":0},{"id":0,"sLen":0,"time":2022.47191011236,"type":0},{"id":3,"sLen":0,"time":2106.74157303371,"type":0},{"id":3,"sLen":0,"time":2191.01123595506,"type":0},{"id":0,"sLen":0,"time":2359.55056179775,"type":0},{"id":0,"sLen":0,"time":2696.62921348315,"type":0},{"id":3,"sLen":0,"time":2865.16853932584,"type":0},{"id":3,"sLen":0,"time":2949.43820224719,"type":0},{"id":3,"sLen":0,"time":3117.97752808989,"type":0},{"id":0,"sLen":0,"time":3286.51685393258,"type":0},{"id":3,"sLen":0,"time":3455.05617977528,"type":0},{"id":3,"sLen":0,"time":3539.32584269663,"type":0},{"id":0,"sLen":0,"time":3707.86516853933,"type":0},{"id":0,"sLen":0,"time":3876.40449438202,"type":0},{"id":0,"sLen":0,"time":3960.67415730337,"type":0},{"id":3,"sLen":0,"time":4044.94382022472,"type":0},{"id":0,"sLen":0,"time":4213.48314606742,"type":0},{"id":0,"sLen":0,"time":4297.75280898876,"type":0},{"id":3,"sLen":0,"time":4550.56179775281,"type":0},{"id":0,"sLen":0,"time":4719.10112359551,"type":0},{"id":3,"sLen":0,"time":4803.37078651685,"type":0},{"id":3,"sLen":0,"time":4887.6404494382,"type":0},{"id":0,"sLen":0,"time":5056.1797752809,"type":0},{"id":0,"sLen":0,"time":5140.44943820225,"type":0},{"id":0,"sLen":0,"time":5224.7191011236,"type":0},{"id":0,"sLen":0,"time":5393.25842696629,"type":0},{"id":3,"sLen":0,"time":5561.79775280899,"type":0},{"id":3,"sLen":0,"time":5646.06741573034,"type":0},{"id":3,"sLen":0,"time":5814.60674157303,"type":0},{"id":0,"sLen":0,"time":5983.14606741573,"type":0},{"id":3,"sLen":0,"time":6151.68539325843,"type":0},{"id":3,"sLen":0,"time":6235.95505617978,"type":0},{"id":0,"sLen":0,"time":6404.49438202247,"type":0},{"id":0,"sLen":0,"time":6573.03370786517,"type":0},{"id":0,"sLen":0,"time":6657.30337078652,"type":0},{"id":3,"sLen":0,"time":6741.57303370786,"type":0},{"id":0,"sLen":0,"time":6910.11235955056,"type":0},{"id":0,"sLen":0,"time":6994.38202247191,"type":0},{"id":3,"sLen":0,"time":7247.19101123595,"type":0},{"id":0,"sLen":0,"time":7415.73033707865,"type":0},{"id":3,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7584.26966292135,"type":0},{"id":0,"sLen":0,"time":7752.80898876405,"type":0},{"id":0,"sLen":0,"time":8089.88764044944,"type":0},{"id":3,"sLen":0,"time":8258.42696629213,"type":0},{"id":3,"sLen":0,"time":8342.69662921348,"type":0},{"id":3,"sLen":0,"time":8511.23595505618,"type":0},{"id":0,"sLen":0,"time":8679.77528089888,"type":0},{"id":3,"sLen":0,"time":8848.31460674157,"type":0},{"id":3,"sLen":0,"time":8932.58426966292,"type":0},{"id":0,"sLen":0,"time":9101.12359550562,"type":0},{"id":0,"sLen":0,"time":9269.66292134831,"type":0},{"id":0,"sLen":0,"time":9353.93258426966,"type":0},{"id":3,"sLen":0,"time":9438.20224719101,"type":0},{"id":0,"sLen":0,"time":9606.74157303371,"type":0},{"id":0,"sLen":0,"time":9691.01123595505,"type":0},{"id":3,"sLen":0,"time":9943.8202247191,"type":0},{"id":0,"sLen":0,"time":10112.3595505618,"type":0},{"id":3,"sLen":0,"time":10196.6292134831,"type":0},{"id":3,"sLen":0,"time":10280.8988764045,"type":0},{"id":0,"sLen":0,"time":10449.4382022472,"type":0},{"id":0,"sLen":0,"time":21235.9550561798,"type":0},{"id":3,"sLen":0,"time":21320.2247191011,"type":0},{"id":0,"sLen":0,"time":21404.4943820225,"type":0},{"id":0,"sLen":0,"time":21573.0337078652,"type":0},{"id":3,"sLen":0,"time":21910.1123595506,"type":0},{"id":0,"sLen":0,"time":22247.191011236,"type":0},{"id":3,"sLen":0,"time":22584.2696629213,"type":0},{"id":0,"sLen":0,"time":22921.3483146067,"type":0},{"id":0,"sLen":0,"time":23258.4269662921,"type":0},{"id":3,"sLen":0,"time":23595.5056179775,"type":0},{"id":3,"sLen":0,"time":23932.5842696629,"type":0},{"id":0,"sLen":0,"time":24269.6629213483,"type":0},{"id":3,"sLen":0,"time":24606.7415730337,"type":0},{"id":0,"sLen":0,"time":24943.8202247191,"type":0},{"id":3,"sLen":0,"time":25280.8988764045,"type":0},{"id":0,"sLen":0,"time":25617.9775280899,"type":0},{"id":0,"sLen":0,"time":25955.0561797753,"type":0},{"id":3,"sLen":0,"time":26292.1348314607,"type":0},{"id":3,"sLen":0,"time":26629.2134831461,"type":0},{"id":3,"sLen":0,"time":26713.4831460674,"type":0},{"id":3,"sLen":0,"time":26797.7528089888,"type":0},{"id":3,"sLen":0,"time":26966.2921348315,"type":0},{"id":0,"sLen":0,"time":27303.3707865169,"type":0},{"id":0,"sLen":0,"time":27640.4494382022,"type":0},{"id":3,"sLen":0,"time":27977.5280898876,"type":0},{"id":3,"sLen":0,"time":28314.606741573,"type":0},{"id":0,"sLen":0,"time":28651.6853932584,"type":0},{"id":3,"sLen":0,"time":28988.7640449438,"type":0},{"id":0,"sLen":0,"time":29325.8426966292,"type":0},{"id":0,"sLen":0,"time":29662.9213483146,"type":0},{"id":0,"sLen":0,"time":29831.4606741573,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30168.5393258427,"type":0},{"id":3,"sLen":0,"time":30337.0786516854,"type":0},{"id":3,"sLen":0,"time":30505.6179775281,"type":0},{"id":3,"sLen":0,"time":30674.1573033708,"type":0},{"id":0,"sLen":0,"time":30842.6966292135,"type":0},{"id":0,"sLen":0,"time":31011.2359550562,"type":0},{"id":3,"sLen":0,"time":31348.3146067416,"type":0},{"id":3,"sLen":0,"time":31685.393258427,"type":0},{"id":0,"sLen":0,"time":31853.9325842697,"type":0},{"id":0,"sLen":0,"time":32359.5505617978,"type":0},{"id":3,"sLen":0,"time":32696.6292134831,"type":0},{"id":0,"sLen":0,"time":33033.7078651685,"type":0},{"id":0,"sLen":0,"time":33202.2471910112,"type":0},{"id":3,"sLen":0,"time":33370.7865168539,"type":0},{"id":0,"sLen":0,"time":33707.8651685393,"type":0},{"id":3,"sLen":0,"time":34044.9438202247,"type":0},{"id":3,"sLen":0,"time":34382.0224719101,"type":0},{"id":0,"sLen":0,"time":34550.5617977528,"type":0},{"id":3,"sLen":0,"time":34719.1011235955,"type":0},{"id":0,"sLen":0,"time":35056.1797752809,"type":0},{"id":3,"sLen":0,"time":35393.2584269663,"type":0},{"id":0,"sLen":0,"time":35730.3370786517,"type":0},{"id":0,"sLen":0,"time":35898.8764044944,"type":0},{"id":3,"sLen":0,"time":36067.4157303371,"type":0},{"id":0,"sLen":0,"time":36404.4943820225,"type":0},{"id":3,"sLen":0,"time":36741.5730337079,"type":0},{"id":3,"sLen":0,"time":37078.6516853933,"type":0},{"id":0,"sLen":0,"time":37247.191011236,"type":0},{"id":3,"sLen":0,"time":37415.7303370787,"type":0},{"id":0,"sLen":0,"time":37752.808988764,"type":0},{"id":3,"sLen":0,"time":38089.8876404494,"type":0},{"id":3,"sLen":0,"time":38426.9662921348,"type":0},{"id":0,"sLen":0,"time":38595.5056179775,"type":0},{"id":3,"sLen":0,"time":38764.0449438202,"type":0},{"id":0,"sLen":0,"time":39101.1235955056,"type":0},{"id":3,"sLen":0,"time":39438.202247191,"type":0},{"id":3,"sLen":0,"time":39775.2808988764,"type":0},{"id":0,"sLen":0,"time":39943.8202247191,"type":0},{"id":3,"sLen":0,"time":40112.3595505618,"type":0},{"id":0,"sLen":0,"time":40449.4382022472,"type":0},{"id":3,"sLen":0,"time":40786.5168539326,"type":0},{"id":3,"sLen":0,"time":41123.595505618,"type":0},{"id":0,"sLen":0,"time":41292.1348314607,"type":0},{"id":3,"sLen":0,"time":41460.6741573034,"type":0},{"id":0,"sLen":0,"time":41797.7528089888,"type":0},{"id":0,"sLen":0,"time":42134.8314606742,"type":0},{"id":0,"sLen":0,"time":42471.9101123596,"type":0},{"id":0,"sLen":0,"time":42640.4494382022,"type":0},{"id":3,"sLen":0,"time":42808.9887640449,"type":0},{"id":3,"sLen":0,"time":42893.2584269663,"type":0},{"id":3,"sLen":0,"time":42977.5280898876,"type":0},{"id":0,"sLen":0,"time":43146.0674157303,"type":0},{"id":3,"sLen":0,"time":43483.1460674157,"type":0},{"id":3,"sLen":0,"time":43820.2247191011,"type":0},{"id":0,"sLen":0,"time":43988.7640449438,"type":0},{"id":3,"sLen":0,"time":44157.3033707865,"type":0},{"id":0,"sLen":0,"time":44494.3820224719,"type":0},{"id":3,"sLen":0,"time":44831.4606741573,"type":0},{"id":0,"sLen":0,"time":45168.5393258427,"type":0},{"id":0,"sLen":0,"time":45337.0786516854,"type":0},{"id":3,"sLen":0,"time":45505.6179775281,"type":0},{"id":0,"sLen":0,"time":45842.6966292135,"type":0},{"id":3,"sLen":0,"time":46179.7752808989,"type":0},{"id":3,"sLen":0,"time":46516.8539325843,"type":0},{"id":0,"sLen":0,"time":46685.393258427,"type":0},{"id":3,"sLen":0,"time":46853.9325842697,"type":0},{"id":0,"sLen":0,"time":47191.0112359551,"type":0},{"id":3,"sLen":0,"time":47528.0898876404,"type":0},{"id":3,"sLen":0,"time":47865.1685393258,"type":0},{"id":0,"sLen":0,"time":48033.7078651685,"type":0},{"id":3,"sLen":0,"time":48202.2471910112,"type":0},{"id":0,"sLen":0,"time":48539.3258426966,"type":0},{"id":3,"sLen":0,"time":48876.404494382,"type":0},{"id":3,"sLen":0,"time":49213.4831460674,"type":0},{"id":0,"sLen":0,"time":49382.0224719101,"type":0},{"id":3,"sLen":0,"time":49550.5617977528,"type":0},{"id":0,"sLen":0,"time":49887.6404494382,"type":0},{"id":3,"sLen":0,"time":50224.7191011236,"type":0},{"id":0,"sLen":0,"time":50561.797752809,"type":0},{"id":0,"sLen":0,"time":50730.3370786517,"type":0},{"id":3,"sLen":0,"time":50898.8764044944,"type":0},{"id":0,"sLen":0,"time":51235.9550561798,"type":0},{"id":3,"sLen":0,"time":51573.0337078652,"type":0},{"id":3,"sLen":0,"time":51910.1123595506,"type":0},{"id":0,"sLen":0,"time":52078.6516853933,"type":0},{"id":3,"sLen":0,"time":52247.191011236,"type":0},{"id":0,"sLen":0,"time":52584.2696629213,"type":0},{"id":0,"sLen":0,"time":52921.3483146067,"type":0},{"id":0,"sLen":0,"time":53258.4269662921,"type":0},{"id":0,"sLen":0,"time":53426.9662921348,"type":0},{"id":3,"sLen":0,"time":53595.5056179775,"type":0},{"id":3,"sLen":0,"time":53679.7752808989,"type":0},{"id":3,"sLen":0,"time":53764.0449438202,"type":0},{"id":0,"sLen":0,"time":53932.5842696629,"type":0},{"id":3,"sLen":0,"time":54101.1235955056,"type":0},{"id":3,"sLen":0,"time":54185.393258427,"type":0},{"id":3,"sLen":0,"time":54353.9325842697,"type":0},{"id":0,"sLen":0,"time":54522.4719101124,"type":0},{"id":3,"sLen":0,"time":54691.0112359551,"type":0},{"id":3,"sLen":0,"time":54775.2808988764,"type":0},{"id":0,"sLen":0,"time":54943.8202247191,"type":0},{"id":0,"sLen":0,"time":55112.3595505618,"type":0},{"id":0,"sLen":0,"time":55196.6292134831,"type":0},{"id":3,"sLen":0,"time":55280.8988764045,"type":0},{"id":0,"sLen":0,"time":55449.4382022472,"type":0},{"id":0,"sLen":0,"time":55533.7078651685,"type":0},{"id":3,"sLen":0,"time":55786.5168539326,"type":0},{"id":0,"sLen":0,"time":55955.0561797753,"type":0},{"id":3,"sLen":0,"time":56039.3258426966,"type":0},{"id":3,"sLen":0,"time":56123.595505618,"type":0},{"id":0,"sLen":0,"time":56292.1348314607,"type":0},{"id":0,"sLen":0,"time":56629.2134831461,"type":0},{"id":3,"sLen":0,"time":56797.7528089888,"type":0},{"id":3,"sLen":0,"time":56882.0224719101,"type":0},{"id":3,"sLen":0,"time":57050.5617977528,"type":0},{"id":0,"sLen":0,"time":57219.1011235955,"type":0},{"id":3,"sLen":0,"time":57387.6404494382,"type":0},{"id":3,"sLen":0,"time":57471.9101123595,"type":0},{"id":0,"sLen":0,"time":57640.4494382022,"type":0},{"id":0,"sLen":0,"time":57808.9887640449,"type":0},{"id":0,"sLen":0,"time":57893.2584269663,"type":0},{"id":3,"sLen":0,"time":57977.5280898876,"type":0},{"id":0,"sLen":0,"time":58146.0674157303,"type":0},{"id":0,"sLen":0,"time":58230.3370786517,"type":0},{"id":3,"sLen":0,"time":58483.1460674157,"type":0},{"id":0,"sLen":0,"time":58651.6853932584,"type":0},{"id":3,"sLen":0,"time":58735.9550561798,"type":0},{"id":3,"sLen":0,"time":58820.2247191011,"type":0},{"id":0,"sLen":0,"time":58988.7640449438,"type":0},{"id":0,"sLen":0,"time":59073.0337078652,"type":0},{"id":0,"sLen":0,"time":59157.3033707865,"type":0},{"id":0,"sLen":0,"time":59325.8426966292,"type":0},{"id":3,"sLen":0,"time":59494.3820224719,"type":0},{"id":3,"sLen":0,"time":59578.6516853933,"type":0},{"id":3,"sLen":0,"time":59747.191011236,"type":0},{"id":0,"sLen":0,"time":59915.7303370786,"type":0},{"id":3,"sLen":0,"time":60084.2696629213,"type":0},{"id":3,"sLen":0,"time":60168.5393258427,"type":0},{"id":0,"sLen":0,"time":60337.0786516854,"type":0},{"id":0,"sLen":0,"time":60505.6179775281,"type":0},{"id":0,"sLen":0,"time":60589.8876404494,"type":0},{"id":3,"sLen":0,"time":60674.1573033708,"type":0},{"id":0,"sLen":0,"time":60842.6966292135,"type":0},{"id":0,"sLen":0,"time":60926.9662921348,"type":0},{"id":3,"sLen":0,"time":61179.7752808989,"type":0},{"id":0,"sLen":0,"time":61348.3146067416,"type":0},{"id":3,"sLen":0,"time":61432.5842696629,"type":0},{"id":3,"sLen":0,"time":61516.8539325843,"type":0},{"id":0,"sLen":0,"time":61685.393258427,"type":0},{"id":0,"sLen":0,"time":62022.4719101124,"type":0},{"id":3,"sLen":0,"time":62191.0112359551,"type":0},{"id":3,"sLen":0,"time":62275.2808988764,"type":0},{"id":3,"sLen":0,"time":62443.8202247191,"type":0},{"id":0,"sLen":0,"time":62612.3595505618,"type":0},{"id":3,"sLen":0,"time":62780.8988764045,"type":0},{"id":3,"sLen":0,"time":62865.1685393258,"type":0},{"id":0,"sLen":0,"time":63033.7078651685,"type":0},{"id":0,"sLen":0,"time":63202.2471910112,"type":0},{"id":0,"sLen":0,"time":63286.5168539326,"type":0},{"id":3,"sLen":0,"time":63370.7865168539,"type":0},{"id":0,"sLen":0,"time":63539.3258426966,"type":0},{"id":0,"sLen":0,"time":63623.595505618,"type":0},{"id":3,"sLen":0,"time":63876.404494382,"type":0},{"id":0,"sLen":0,"time":64044.9438202247,"type":0},{"id":3,"sLen":0,"time":64129.2134831461,"type":0},{"id":3,"sLen":0,"time":64213.4831460674,"type":0},{"id":0,"sLen":0,"time":64382.0224719101,"type":0},{"id":3,"sLen":0,"time":64719.1011235955,"type":0},{"id":0,"sLen":0,"time":65056.1797752809,"type":0},{"id":0,"sLen":0,"time":65393.2584269663,"type":0},{"id":0,"sLen":0,"time":65730.3370786517,"type":0},{"id":0,"sLen":0,"time":66067.4157303371,"type":0},{"id":3,"sLen":0,"time":66404.4943820225,"type":0},{"id":3,"sLen":0,"time":66741.5730337079,"type":0},{"id":3,"sLen":0,"time":67078.6516853933,"type":0},{"id":3,"sLen":0,"time":67415.7303370787,"type":0},{"id":0,"sLen":0,"time":67752.808988764,"type":0},{"id":0,"sLen":0,"time":68089.8876404494,"type":0},{"id":0,"sLen":0,"time":68426.9662921348,"type":0},{"id":0,"sLen":0,"time":68764.0449438202,"type":0},{"id":3,"sLen":0,"time":69101.1235955056,"type":0},{"id":3,"sLen":0,"time":69438.202247191,"type":0},{"id":3,"sLen":0,"time":69775.2808988764,"type":0},{"id":3,"sLen":0,"time":69859.5505617978,"type":0},{"id":3,"sLen":0,"time":69943.8202247191,"type":0},{"id":3,"sLen":0,"time":70112.3595505618,"type":0},{"id":0,"sLen":0,"time":70449.4382022472,"type":0},{"id":0,"sLen":0,"time":70786.5168539326,"type":0},{"id":0,"sLen":0,"time":71123.595505618,"type":0},{"id":0,"sLen":0,"time":71460.6741573034,"type":0},{"id":3,"sLen":0,"time":71797.7528089888,"type":0},{"id":3,"sLen":0,"time":72134.8314606741,"type":0},{"id":3,"sLen":0,"time":72471.9101123595,"type":0},{"id":3,"sLen":0,"time":72556.1797752809,"type":0},{"id":3,"sLen":0,"time":72640.4494382023,"type":0},{"id":3,"sLen":0,"time":72808.9887640449,"type":0},{"id":3,"sLen":0,"time":72893.2584269663,"type":0},{"id":3,"sLen":0,"time":72977.5280898877,"type":0},{"id":0,"sLen":0,"time":73146.0674157303,"type":0},{"id":0,"sLen":0,"time":73230.3370786517,"type":0},{"id":0,"sLen":0,"time":73314.6067415731,"type":0},{"id":0,"sLen":0,"time":73483.1460674157,"type":0},{"id":0,"sLen":0,"time":73567.4157303371,"type":0},{"id":0,"sLen":0,"time":73651.6853932585,"type":0},{"id":0,"sLen":0,"time":73820.2247191011,"type":0},{"id":0,"sLen":0,"time":73904.4943820225,"type":0},{"id":0,"sLen":0,"time":73988.7640449438,"type":0},{"id":0,"sLen":0,"time":74157.3033707866,"type":0},{"id":0,"sLen":0,"time":74241.5730337079,"type":0},{"id":0,"sLen":0,"time":74325.8426966292,"type":0},{"id":0,"sLen":0,"time":74410.1123595506,"type":0},{"id":3,"sLen":0,"time":74494.382022472,"type":0},{"id":3,"sLen":0,"time":74578.6516853933,"type":0},{"id":3,"sLen":0,"time":74662.9213483146,"type":0},{"id":3,"sLen":0,"time":74747.191011236,"type":0},{"id":0,"sLen":0,"time":74831.4606741573,"type":0},{"id":0,"sLen":0,"time":74915.7303370787,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75084.2696629214,"type":0},{"id":3,"sLen":0,"time":75168.5393258427,"type":0},{"id":0,"sLen":0,"time":75505.6179775281,"type":0},{"id":3,"sLen":0,"time":75842.6966292135,"type":0},{"id":0,"sLen":0,"time":76179.7752808989,"type":0},{"id":0,"sLen":0,"time":76516.8539325843,"type":0},{"id":3,"sLen":0,"time":76853.9325842697,"type":0},{"id":3,"sLen":0,"time":77191.0112359551,"type":0},{"id":0,"sLen":0,"time":77528.0898876405,"type":0},{"id":3,"sLen":0,"time":77865.1685393259,"type":0},{"id":3,"sLen":0,"time":78033.7078651686,"type":0},{"id":0,"sLen":0,"time":78202.2471910113,"type":0},{"id":0,"sLen":0,"time":78539.3258426967,"type":0},{"id":3,"sLen":0,"time":78876.4044943821,"type":0},{"id":0,"sLen":0,"time":79213.4831460675,"type":0},{"id":0,"sLen":0,"time":79550.5617977529,"type":0},{"id":3,"sLen":0,"time":79887.6404494383,"type":0},{"id":0,"sLen":0,"time":80140.4494382023,"type":0},{"id":0,"sLen":0,"time":80224.7191011237,"type":0},{"id":0,"sLen":0,"time":80308.988764045,"type":0},{"id":0,"sLen":0,"time":80477.5280898877,"type":0},{"id":3,"sLen":84.269662921346,"time":80730.3370786517,"type":0},{"id":0,"sLen":0,"time":80898.8764044945,"type":0},{"id":3,"sLen":0,"time":81067.4157303371,"type":0},{"id":0,"sLen":0,"time":81235.9550561799,"type":0},{"id":3,"sLen":0,"time":81404.4943820225,"type":0},{"id":0,"sLen":0,"time":81573.0337078652,"type":0},{"id":3,"sLen":0,"time":81741.5730337079,"type":0},{"id":0,"sLen":0,"time":81910.1123595506,"type":0},{"id":3,"sLen":0,"time":82078.6516853933,"type":0},{"id":3,"sLen":0,"time":82247.191011236,"type":0},{"id":0,"sLen":0,"time":82415.7303370787,"type":0},{"id":3,"sLen":0,"time":82584.2696629214,"type":0},{"id":0,"sLen":0,"time":82752.8089887641,"type":0},{"id":3,"sLen":0,"time":82921.3483146068,"type":0},{"id":0,"sLen":0,"time":83089.8876404495,"type":0},{"id":3,"sLen":0,"time":83258.4269662922,"type":0},{"id":3,"sLen":0,"time":83426.9662921349,"type":0},{"id":0,"sLen":0,"time":83595.5056179776,"type":0},{"id":0,"sLen":0,"time":83764.0449438203,"type":0},{"id":0,"sLen":0,"time":83932.584269663,"type":0},{"id":3,"sLen":0,"time":84269.6629213484,"type":0},{"id":3,"sLen":0,"time":84606.7415730338,"type":0},{"id":3,"sLen":0,"time":84775.2808988765,"type":0},{"id":3,"sLen":0,"time":84943.8202247192,"type":0},{"id":3,"sLen":0,"time":85280.8988764046,"type":0},{"id":0,"sLen":0,"time":85617.97752809,"type":0},{"id":0,"sLen":0,"time":85702.2471910113,"type":0},{"id":3,"sLen":0,"time":85870.786516854,"type":0},{"id":0,"sLen":0,"time":86039.3258426967,"type":0},{"id":0,"sLen":0,"time":86292.1348314607,"type":0},{"id":3,"sLen":0,"time":86629.2134831461,"type":0},{"id":0,"sLen":0,"time":87134.8314606741,"type":0},{"id":3,"sLen":0,"time":87303.3707865169,"type":0},{"id":0,"sLen":0,"time":87640.4494382023,"type":0},{"id":3,"sLen":0,"time":87977.5280898877,"type":0},{"id":0,"sLen":0,"time":88483.1460674157,"type":0},{"id":3,"sLen":0,"time":88651.6853932584,"type":0},{"id":0,"sLen":0,"time":88988.7640449438,"type":0},{"id":3,"sLen":0,"time":89325.8426966292,"type":0},{"id":0,"sLen":0,"time":89831.4606741573,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.0786516854,"type":0},{"id":3,"sLen":0,"time":90674.1573033708,"type":0},{"id":0,"sLen":0,"time":91179.7752808989,"type":0},{"id":3,"sLen":0,"time":91348.3146067416,"type":0},{"id":0,"sLen":0,"time":91685.393258427,"type":0},{"id":3,"sLen":0,"time":92022.4719101124,"type":0},{"id":0,"sLen":0,"time":92528.0898876405,"type":0},{"id":3,"sLen":0,"time":92696.6292134832,"type":0},{"id":0,"sLen":0,"time":93033.7078651686,"type":0},{"id":3,"sLen":0,"time":93370.786516854,"type":0},{"id":0,"sLen":0,"time":93876.404494382,"type":0},{"id":3,"sLen":0,"time":94044.9438202248,"type":0},{"id":0,"sLen":0,"time":94382.0224719102,"type":0},{"id":3,"sLen":0,"time":94719.1011235956,"type":0},{"id":0,"sLen":0,"time":95224.7191011236,"type":0},{"id":3,"sLen":0,"time":95393.2584269663,"type":0},{"id":0,"sLen":0,"time":95730.3370786517,"type":0},{"id":0,"sLen":0,"time":96067.4157303371,"type":0},{"id":0,"sLen":0,"time":96404.4943820225,"type":0},{"id":0,"sLen":0,"time":96573.0337078652,"type":0},{"id":3,"sLen":0,"time":96741.5730337079,"type":0},{"id":3,"sLen":0,"time":96825.8426966292,"type":0},{"id":3,"sLen":0,"time":96910.1123595505,"type":0},{"id":0,"sLen":0,"time":97078.6516853933,"type":0},{"id":3,"sLen":0,"time":97415.7303370787,"type":0},{"id":3,"sLen":0,"time":97752.808988764,"type":0},{"id":0,"sLen":0,"time":97921.3483146067,"type":0},{"id":3,"sLen":0,"time":98089.8876404494,"type":0},{"id":0,"sLen":0,"time":98426.9662921348,"type":0},{"id":3,"sLen":0,"time":98764.0449438202,"type":0},{"id":0,"sLen":0,"time":99101.1235955056,"type":0},{"id":0,"sLen":0,"time":99269.6629213483,"type":0},{"id":3,"sLen":0,"time":99438.202247191,"type":0},{"id":0,"sLen":0,"time":99775.2808988764,"type":0},{"id":3,"sLen":0,"time":100112.359550562,"type":0},{"id":3,"sLen":0,"time":100449.438202247,"type":0},{"id":0,"sLen":0,"time":100617.97752809,"type":0},{"id":3,"sLen":0,"time":100786.516853933,"type":0},{"id":0,"sLen":0,"time":101123.595505618,"type":0},{"id":3,"sLen":0,"time":101460.674157303,"type":0},{"id":3,"sLen":0,"time":101797.752808989,"type":0},{"id":0,"sLen":0,"time":101966.292134831,"type":0},{"id":3,"sLen":0,"time":102134.831460674,"type":0},{"id":0,"sLen":0,"time":102471.91011236,"type":0},{"id":3,"sLen":0,"time":102808.988764045,"type":0},{"id":3,"sLen":0,"time":103146.06741573,"type":0},{"id":0,"sLen":0,"time":103314.606741573,"type":0},{"id":3,"sLen":0,"time":103483.146067416,"type":0},{"id":0,"sLen":0,"time":103820.224719101,"type":0},{"id":3,"sLen":0,"time":104157.303370787,"type":0},{"id":0,"sLen":0,"time":104494.382022472,"type":0},{"id":0,"sLen":0,"time":104662.921348315,"type":0},{"id":3,"sLen":0,"time":104831.460674157,"type":0},{"id":0,"sLen":0,"time":105168.539325843,"type":0},{"id":3,"sLen":0,"time":105505.617977528,"type":0},{"id":3,"sLen":0,"time":105842.696629213,"type":0},{"id":0,"sLen":0,"time":106011.235955056,"type":0},{"id":3,"sLen":0,"time":106179.775280899,"type":0},{"id":0,"sLen":0,"time":106516.853932584,"type":0},{"id":0,"sLen":0,"time":106853.93258427,"type":0},{"id":0,"sLen":0,"time":107191.011235955,"type":0},{"id":3,"sLen":0,"time":107528.08988764,"type":0},{"id":0,"sLen":0,"time":107865.168539326,"type":0},{"id":0,"sLen":0,"time":108202.247191011,"type":0},{"id":0,"sLen":0,"time":108539.325842697,"type":0},{"id":0,"sLen":0,"time":108876.404494382,"type":0},{"id":3,"sLen":0,"time":109213.483146068,"type":0},{"id":3,"sLen":0,"time":109550.561797753,"type":0},{"id":3,"sLen":0,"time":109887.640449438,"type":0},{"id":3,"sLen":0,"time":110224.719101124,"type":0},{"id":0,"sLen":0,"time":110561.797752809,"type":0},{"id":0,"sLen":0,"time":110730.337078652,"type":0},{"id":0,"sLen":0,"time":110898.876404495,"type":0},{"id":3,"sLen":0,"time":111235.95505618,"type":0},{"id":3,"sLen":0,"time":111573.033707865,"type":0},{"id":3,"sLen":0,"time":111910.112359551,"type":0},{"id":3,"sLen":0,"time":112247.191011236,"type":0},{"id":0,"sLen":0,"time":112584.269662922,"type":0},{"id":3,"sLen":0,"time":112921.348314607,"type":0},{"id":0,"sLen":0,"time":113258.426966292,"type":0},{"id":3,"sLen":0,"time":113932.584269663,"type":0},{"id":0,"sLen":0,"time":114269.662921349,"type":0},{"id":3,"sLen":0,"time":114438.202247191,"type":0},{"id":0,"sLen":0,"time":114606.741573034,"type":0},{"id":3,"sLen":0,"time":114943.820224719,"type":0},{"id":0,"sLen":0,"time":115280.898876405,"type":0},{"id":3,"sLen":0,"time":115617.97752809,"type":0},{"id":3,"sLen":0,"time":115786.516853933,"type":0},{"id":0,"sLen":0,"time":115955.056179776,"type":0},{"id":3,"sLen":0,"time":116123.595505618,"type":0},{"id":0,"sLen":0,"time":116292.134831461,"type":0},{"id":0,"sLen":0,"time":116629.213483146,"type":0},{"id":3,"sLen":0,"time":116966.292134832,"type":0},{"id":3,"sLen":0,"time":117303.370786517,"type":0},{"id":0,"sLen":0,"time":117640.449438203,"type":0},{"id":3,"sLen":0,"time":117977.528089888,"type":0},{"id":3,"sLen":0,"time":118061.797752809,"type":0},{"id":0,"sLen":0,"time":118314.606741573,"type":0},{"id":3,"sLen":0,"time":118398.876404495,"type":0},{"id":0,"sLen":0,"time":118651.685393258,"type":0},{"id":3,"sLen":0,"time":118820.224719101,"type":0},{"id":3,"sLen":0,"time":118904.494382022,"type":0},{"id":3,"sLen":0,"time":119073.033707865,"type":0},{"id":0,"sLen":0,"time":119241.573033708,"type":0},{"id":3,"sLen":0,"time":119410.112359551,"type":0},{"id":3,"sLen":0,"time":119494.382022472,"type":0},{"id":0,"sLen":0,"time":119662.921348315,"type":0},{"id":0,"sLen":0,"time":119831.460674157,"type":0},{"id":0,"sLen":0,"time":119915.730337079,"type":0},{"id":3,"sLen":0,"time":120000,"type":0},{"id":0,"sLen":0,"time":120168.539325843,"type":0},{"id":0,"sLen":0,"time":120252.808988764,"type":0},{"id":3,"sLen":0,"time":120505.617977528,"type":0},{"id":0,"sLen":0,"time":120674.157303371,"type":0},{"id":3,"sLen":0,"time":120758.426966292,"type":0},{"id":3,"sLen":0,"time":120842.696629213,"type":0},{"id":0,"sLen":0,"time":121011.235955056,"type":0},{"id":0,"sLen":0,"time":121095.505617978,"type":0},{"id":0,"sLen":0,"time":121179.775280899,"type":0}],"position":"girlfriend","type":2,"characters":["pico-speakers"],"visible":false}],"codenameChart":true,"stage":"tank","noteTypes":["No Anim Note"]} \ No newline at end of file +{"events":[{"params":[1],"time":5393.25842696629,"name":"Camera Movement"},{"params":[0],"time":10786.5168539326,"name":"Camera Movement"},{"params":[1],"time":14831.4606741573,"name":"Camera Movement"},{"params":[0],"time":21573.0337078652,"name":"Camera Movement"},{"params":[1],"time":24269.6629213483,"name":"Camera Movement"},{"params":[0],"time":26966.2921348315,"name":"Camera Movement"},{"params":[1],"time":28314.606741573,"name":"Camera Movement"},{"params":[0],"time":31011.2359550562,"name":"Camera Movement"},{"params":[1],"time":37752.808988764,"name":"Camera Movement"},{"params":[0],"time":43146.0674157303,"name":"Camera Movement"},{"params":[1],"time":48539.3258426966,"name":"Camera Movement"},{"params":[0],"time":53932.5842696629,"name":"Camera Movement"},{"params":[1],"time":59325.8426966292,"name":"Camera Movement"},{"params":[0],"time":62022.4719101124,"name":"Camera Movement"},{"params":[1],"time":67415.7303370787,"name":"Camera Movement"},{"params":[0],"time":75505.6179775281,"name":"Camera Movement"},{"params":[1],"time":80898.8764044945,"name":"Camera Movement"},{"params":[0],"time":86292.1348314608,"name":"Camera Movement"},{"params":[1],"time":91685.3932584271,"name":"Camera Movement"},{"params":[0],"time":97078.6516853934,"name":"Camera Movement"},{"params":[1],"time":102471.91011236,"name":"Camera Movement"},{"params":[0],"time":107865.168539326,"name":"Camera Movement"},{"params":[1],"time":113258.426966292,"name":"Camera Movement"}],"scrollSpeed":1.75,"strumLines":[{"notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":0,"sLen":0,"time":337.078644,"type":0},{"id":0,"sLen":0,"time":674.1573,"type":0},{"id":2,"sLen":0,"time":1011.23596,"type":0},{"id":1,"sLen":0,"time":1348.31458,"type":0},{"id":3,"sLen":0,"time":1685.39331,"type":0},{"id":1,"sLen":0,"time":2022.47192,"type":0},{"id":1,"sLen":0,"time":2359.55054,"type":0},{"id":0,"sLen":0,"time":2528.08984,"type":0},{"id":2,"sLen":0,"time":2696.62915,"type":0},{"id":0,"sLen":0,"time":3033.70776,"type":0},{"id":0,"sLen":0,"time":3370.78662,"type":0},{"id":2,"sLen":0,"time":3707.86523,"type":0},{"id":1,"sLen":0,"time":4044.94385,"type":0},{"id":3,"sLen":0,"time":4382.02246,"type":0},{"id":1,"sLen":0,"time":4719.101,"type":0},{"id":1,"sLen":0,"time":5056.17969,"type":0},{"id":0,"sLen":0,"time":5224.719,"type":0},{"id":0,"sLen":0,"time":11123.5957,"type":0},{"id":1,"sLen":0,"time":11292.1348,"type":0},{"id":3,"sLen":252.808989999996,"time":11460.6738,"type":0},{"id":1,"sLen":252.80899,"time":11797.7529,"type":0},{"id":0,"sLen":0,"time":12134.8311,"type":0},{"id":0,"sLen":0,"time":12471.91,"type":0},{"id":0,"sLen":0,"time":12808.9883,"type":0},{"id":3,"sLen":0,"time":13146.0674,"type":0},{"id":1,"sLen":0,"time":13483.1465,"type":0},{"id":3,"sLen":0,"time":13820.2246,"type":0},{"id":1,"sLen":0,"time":13988.7637,"type":0},{"id":3,"sLen":0,"time":14325.8428,"type":0},{"id":1,"sLen":0,"time":14494.3818,"type":0},{"id":1,"sLen":0,"time":14831.4609,"type":0},{"id":3,"sLen":0,"time":15168.5391,"type":0},{"id":1,"sLen":0,"time":15505.6182,"type":0},{"id":1,"sLen":0,"time":15842.6963,"type":0},{"id":0,"sLen":0,"time":16011.2363,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":0,"sLen":0,"time":21910.1133,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":0,"sLen":0,"time":22584.27,"type":0},{"id":3,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":0,"time":23595.5059,"type":0},{"id":1,"sLen":0,"time":23932.584,"type":0},{"id":0,"sLen":0,"time":24269.6621,"type":0},{"id":3,"sLen":0,"time":26966.293,"type":0},{"id":0,"sLen":0,"time":27303.3711,"type":0},{"id":3,"sLen":0,"time":27640.45,"type":0},{"id":0,"sLen":0,"time":27977.5273,"type":0},{"id":3,"sLen":0,"time":28314.6074,"type":0},{"id":2,"sLen":0,"time":28651.6855,"type":0},{"id":3,"sLen":0,"time":28988.7637,"type":0},{"id":1,"sLen":0,"time":29325.8418,"type":0},{"id":0,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":31011.2363,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":0,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":0,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":0,"sLen":0,"time":32359.55,"type":0},{"id":2,"sLen":0,"time":32696.6289,"type":0},{"id":0,"sLen":0,"time":33033.707,"type":0},{"id":2,"sLen":0,"time":33370.7852,"type":0},{"id":3,"sLen":0,"time":33539.3242,"type":0},{"id":1,"sLen":0,"time":33707.8633,"type":0},{"id":0,"sLen":0,"time":34044.9453,"type":0},{"id":1,"sLen":0,"time":34382.0234,"type":0},{"id":1,"sLen":0,"time":34719.1,"type":0},{"id":0,"sLen":0,"time":34887.64,"type":0},{"id":3,"sLen":0,"time":35056.18,"type":0},{"id":2,"sLen":0,"time":35393.2578,"type":0},{"id":0,"sLen":0,"time":35730.3359,"type":0},{"id":2,"sLen":0,"time":36067.4141,"type":0},{"id":3,"sLen":0,"time":36404.4961,"type":0},{"id":0,"sLen":0,"time":36741.5742,"type":0},{"id":0,"sLen":0,"time":37078.6523,"type":0},{"id":1,"sLen":0,"time":37247.19,"type":0},{"id":1,"sLen":0,"time":37415.73,"type":0},{"id":0,"sLen":0,"time":37584.27,"type":0},{"id":3,"sLen":0,"time":43146.0664,"type":0},{"id":1,"sLen":0,"time":43314.6055,"type":0},{"id":0,"sLen":0,"time":43483.1445,"type":0},{"id":1,"sLen":0,"time":43820.2266,"type":0},{"id":0,"sLen":0,"time":44157.3047,"type":0},{"id":2,"sLen":0,"time":44494.3828,"type":0},{"id":3,"sLen":0,"time":44662.92,"type":0},{"id":2,"sLen":0,"time":44831.46,"type":0},{"id":3,"sLen":0,"time":45168.54,"type":0},{"id":2,"sLen":0,"time":45505.6172,"type":0},{"id":3,"sLen":0,"time":45842.6953,"type":0},{"id":1,"sLen":0,"time":46179.7734,"type":0},{"id":0,"sLen":0,"time":46516.8555,"type":0},{"id":1,"sLen":0,"time":46853.9336,"type":0},{"id":2,"sLen":0,"time":47191.01,"type":0},{"id":1,"sLen":0,"time":47528.09,"type":0},{"id":1,"sLen":0,"time":47865.168,"type":0},{"id":1,"sLen":0,"time":48202.2461,"type":0},{"id":2,"sLen":0,"time":53932.5859,"type":0},{"id":0,"sLen":0,"time":54269.6641,"type":0},{"id":0,"sLen":0,"time":54606.7422,"type":0},{"id":2,"sLen":0,"time":54943.82,"type":0},{"id":1,"sLen":0,"time":55280.9,"type":0},{"id":3,"sLen":0,"time":55617.9766,"type":0},{"id":1,"sLen":0,"time":55955.0547,"type":0},{"id":1,"sLen":0,"time":56292.1367,"type":0},{"id":0,"sLen":0,"time":56460.6758,"type":0},{"id":2,"sLen":0,"time":56629.2148,"type":0},{"id":0,"sLen":0,"time":56966.293,"type":0},{"id":0,"sLen":0,"time":57303.37,"type":0},{"id":2,"sLen":0,"time":57640.45,"type":0},{"id":1,"sLen":0,"time":57977.5273,"type":0},{"id":3,"sLen":0,"time":58314.6055,"type":0},{"id":1,"sLen":0,"time":58651.6836,"type":0},{"id":1,"sLen":0,"time":58988.7656,"type":0},{"id":0,"sLen":0,"time":59157.3047,"type":0},{"id":0,"sLen":168.539321999997,"time":59325.8438,"type":0},{"id":0,"sLen":168.539321999997,"time":60000,"type":0},{"id":0,"sLen":168.539322000004,"time":60674.1563,"type":0},{"id":0,"sLen":168.539321999997,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":62022.4719101124,"type":1},{"id":1,"sLen":0,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":3,"sLen":0,"time":65393.2578,"type":0},{"id":1,"sLen":0,"time":65730.3359,"type":0},{"id":0,"sLen":0,"time":66067.4141,"type":0},{"id":0,"sLen":0,"time":66404.49,"type":0},{"id":0,"sLen":0,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":66910.11,"type":0},{"id":0,"sLen":0,"time":67078.65,"type":0},{"id":1,"sLen":0,"time":67415.73,"type":0},{"id":3,"sLen":0,"time":67752.81,"type":0},{"id":1,"sLen":0,"time":67921.35,"type":0},{"id":3,"sLen":0,"time":68258.43,"type":0},{"id":1,"sLen":0,"time":68426.97,"type":0},{"id":1,"sLen":0,"time":68764.05,"type":0},{"id":3,"sLen":0,"time":69101.125,"type":0},{"id":1,"sLen":0,"time":69438.2,"type":0},{"id":1,"sLen":0,"time":69775.28,"type":0},{"id":0,"sLen":0,"time":69943.82,"type":0},{"id":0,"sLen":0,"time":75505.62,"type":0},{"id":0,"sLen":0,"time":75842.6953,"type":0},{"id":3,"sLen":0,"time":76179.77,"type":0},{"id":0,"sLen":0,"time":76516.85,"type":0},{"id":3,"sLen":0,"time":76853.93,"type":0},{"id":3,"sLen":0,"time":77191.01,"type":0},{"id":0,"sLen":0,"time":77359.55,"type":0},{"id":1,"sLen":252.808990000005,"time":77528.0859,"type":0},{"id":0,"sLen":0,"time":77865.17,"type":0},{"id":3,"sLen":0,"time":78202.25,"type":0},{"id":1,"sLen":0,"time":78539.33,"type":0},{"id":0,"sLen":0,"time":78876.41,"type":0},{"id":3,"sLen":0,"time":79213.4844,"type":0},{"id":1,"sLen":0,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":79803.37,"type":0},{"id":1,"sLen":0,"time":80056.18,"type":0},{"id":0,"sLen":0,"time":80224.72,"type":0},{"id":2,"sLen":0,"time":80561.8,"type":0},{"id":0,"sLen":0,"time":86292.13,"type":0},{"id":1,"sLen":0,"time":86629.21,"type":0},{"id":3,"sLen":0,"time":86966.29,"type":0},{"id":0,"sLen":0,"time":87303.37,"type":0},{"id":0,"sLen":0,"time":87640.45,"type":0},{"id":1,"sLen":0,"time":87977.53,"type":0},{"id":3,"sLen":0,"time":88314.61,"type":0},{"id":0,"sLen":0,"time":88651.69,"type":0},{"id":2,"sLen":0,"time":88988.7656,"type":0},{"id":3,"sLen":0,"time":89325.84,"type":0},{"id":0,"sLen":0,"time":89662.92,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.08,"type":0},{"id":3,"sLen":0,"time":90674.16,"type":0},{"id":0,"sLen":0,"time":91011.2344,"type":0},{"id":3,"sLen":337.078643999994,"time":91348.31,"type":0},{"id":2,"sLen":0,"time":97078.65,"type":0},{"id":0,"sLen":0,"time":97415.73,"type":0},{"id":2,"sLen":0,"time":97584.27,"type":0},{"id":0,"sLen":0,"time":97921.35,"type":0},{"id":2,"sLen":0,"time":98089.89,"type":0},{"id":0,"sLen":0,"time":98426.97,"type":0},{"id":2,"sLen":0,"time":98595.51,"type":0},{"id":0,"sLen":0,"time":98932.5859,"type":0},{"id":1,"sLen":0,"time":99101.125,"type":0},{"id":1,"sLen":0,"time":99438.2,"type":0},{"id":2,"sLen":0,"time":99775.28,"type":0},{"id":0,"sLen":0,"time":100112.359,"type":0},{"id":2,"sLen":0,"time":100280.9,"type":0},{"id":0,"sLen":0,"time":100617.977,"type":0},{"id":2,"sLen":0,"time":100786.516,"type":0},{"id":0,"sLen":0,"time":101123.594,"type":0},{"id":2,"sLen":0,"time":101292.133,"type":0},{"id":0,"sLen":0,"time":101629.211,"type":0},{"id":1,"sLen":0,"time":101797.75,"type":0},{"id":1,"sLen":0,"time":102134.828,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":0,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":0,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":0,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":0,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104831.461,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":0,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":0,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":0,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":0,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107528.086,"type":0},{"id":1,"sLen":0,"time":107865.172,"type":0},{"id":1,"sLen":0,"time":108202.25,"type":0},{"id":0,"sLen":0,"time":108370.789,"type":0},{"id":1,"sLen":0,"time":108539.328,"type":0},{"id":1,"sLen":0,"time":108876.406,"type":0},{"id":0,"sLen":0,"time":109044.945,"type":0},{"id":1,"sLen":0,"time":109213.484,"type":0},{"id":1,"sLen":0,"time":109550.563,"type":0},{"id":0,"sLen":0,"time":109719.1,"type":0},{"id":1,"sLen":0,"time":109887.641,"type":0},{"id":1,"sLen":0,"time":110224.719,"type":0},{"id":0,"sLen":0,"time":110393.258,"type":0},{"id":2,"sLen":0,"time":113258.43,"type":0},{"id":3,"sLen":0,"time":113595.508,"type":0},{"id":0,"sLen":0,"time":113932.586,"type":0},{"id":1,"sLen":0,"time":114269.664,"type":0},{"id":1,"sLen":0,"time":114606.742,"type":0},{"id":3,"sLen":0,"time":114943.82,"type":0},{"id":1,"sLen":0,"time":115280.9,"type":0},{"id":1,"sLen":0,"time":115617.977,"type":0},{"id":0,"sLen":0,"time":115786.516,"type":0},{"id":2,"sLen":589.887633999984,"time":115955.055,"type":0}],"position":"dad","characters":["tankman"],"visible":true,"type":0},{"notes":[{"id":2,"sLen":0,"time":5393.2583,"type":0},{"id":0,"sLen":0,"time":5730.337,"type":0},{"id":0,"sLen":0,"time":6067.41553,"type":0},{"id":2,"sLen":0,"time":6404.494,"type":0},{"id":1,"sLen":0,"time":6741.573,"type":0},{"id":3,"sLen":0,"time":7078.652,"type":0},{"id":1,"sLen":0,"time":7415.73047,"type":0},{"id":1,"sLen":0,"time":7752.809,"type":0},{"id":0,"sLen":0,"time":7921.348,"type":0},{"id":2,"sLen":0,"time":8089.8877,"type":0},{"id":0,"sLen":0,"time":8426.966,"type":0},{"id":0,"sLen":0,"time":8764.045,"type":0},{"id":2,"sLen":0,"time":9101.124,"type":0},{"id":1,"sLen":0,"time":9438.202,"type":0},{"id":3,"sLen":0,"time":9775.281,"type":0},{"id":1,"sLen":0,"time":10112.3594,"type":0},{"id":1,"sLen":0,"time":10449.4385,"type":0},{"id":0,"sLen":0,"time":10617.9775,"type":0},{"id":0,"sLen":252.808990000001,"time":14831.4609,"type":0},{"id":1,"sLen":252.80899,"time":15168.5391,"type":0},{"id":3,"sLen":252.80899,"time":15505.6182,"type":0},{"id":1,"sLen":252.808990000001,"time":15842.6963,"type":0},{"id":3,"sLen":0,"time":16179.7754,"type":0},{"id":0,"sLen":0,"time":16516.8535,"type":0},{"id":1,"sLen":0,"time":16685.3926,"type":0},{"id":3,"sLen":252.808990000001,"time":16853.9316,"type":0},{"id":1,"sLen":252.808990000001,"time":17191.0117,"type":0},{"id":0,"sLen":0,"time":17528.09,"type":0},{"id":0,"sLen":0,"time":17865.168,"type":0},{"id":0,"sLen":0,"time":18202.248,"type":0},{"id":3,"sLen":0,"time":18539.3262,"type":0},{"id":1,"sLen":0,"time":18876.4043,"type":0},{"id":3,"sLen":0,"time":19213.4824,"type":0},{"id":1,"sLen":0,"time":19382.0215,"type":0},{"id":3,"sLen":0,"time":19719.1016,"type":0},{"id":1,"sLen":0,"time":19887.64,"type":0},{"id":1,"sLen":0,"time":20224.7188,"type":0},{"id":3,"sLen":0,"time":20561.7969,"type":0},{"id":1,"sLen":0,"time":20898.877,"type":0},{"id":1,"sLen":0,"time":21235.9551,"type":0},{"id":0,"sLen":0,"time":21404.4941,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":3,"sLen":0,"time":21910.1133,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":3,"sLen":0,"time":22584.27,"type":0},{"id":1,"sLen":0,"time":22921.3477,"type":0},{"id":1,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":252.808990000001,"time":23595.5059,"type":0},{"id":0,"sLen":252.808990000012,"time":23932.584,"type":0},{"id":3,"sLen":0,"time":24269.6621,"type":0},{"id":0,"sLen":0,"time":24606.7422,"type":0},{"id":3,"sLen":0,"time":24943.82,"type":0},{"id":0,"sLen":0,"time":25280.8984,"type":0},{"id":3,"sLen":0,"time":25617.9766,"type":0},{"id":2,"sLen":0,"time":25955.0566,"type":0},{"id":3,"sLen":0,"time":26292.1348,"type":0},{"id":1,"sLen":0,"time":26629.2129,"type":0},{"id":2,"sLen":589.88763400001,"time":26966.293,"type":0},{"id":3,"sLen":589.887633999992,"time":27640.45,"type":0},{"id":1,"sLen":589.887633999999,"time":28314.6074,"type":0},{"id":0,"sLen":337.078644000008,"time":28988.7637,"type":0},{"id":3,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":3,"sLen":0,"time":31011.2363,"type":0},{"id":2,"sLen":0,"time":31348.3145,"type":0},{"id":3,"sLen":0,"time":31685.3926,"type":0},{"id":1,"sLen":0,"time":32022.4727,"type":0},{"id":1,"sLen":674.157300000003,"time":32359.55,"type":0},{"id":0,"sLen":0,"time":37752.81,"type":0},{"id":2,"sLen":0,"time":38089.8867,"type":0},{"id":0,"sLen":0,"time":38426.9648,"type":0},{"id":2,"sLen":0,"time":38764.043,"type":0},{"id":3,"sLen":0,"time":38932.5859,"type":0},{"id":1,"sLen":0,"time":39101.125,"type":0},{"id":0,"sLen":0,"time":39438.2031,"type":0},{"id":1,"sLen":0,"time":39775.28,"type":0},{"id":1,"sLen":0,"time":40112.36,"type":0},{"id":0,"sLen":0,"time":40280.9,"type":0},{"id":3,"sLen":0,"time":40449.4375,"type":0},{"id":2,"sLen":0,"time":40786.5156,"type":0},{"id":0,"sLen":0,"time":41123.5938,"type":0},{"id":2,"sLen":0,"time":41460.6758,"type":0},{"id":3,"sLen":0,"time":41797.7539,"type":0},{"id":0,"sLen":0,"time":42134.832,"type":0},{"id":0,"sLen":0,"time":42471.91,"type":0},{"id":1,"sLen":0,"time":42640.45,"type":0},{"id":1,"sLen":0,"time":42808.99,"type":0},{"id":0,"sLen":0,"time":42977.5273,"type":0},{"id":3,"sLen":0,"time":48539.3242,"type":0},{"id":1,"sLen":0,"time":48707.8633,"type":0},{"id":0,"sLen":0,"time":48876.4063,"type":0},{"id":1,"sLen":0,"time":49213.4844,"type":0},{"id":0,"sLen":0,"time":49550.5625,"type":0},{"id":2,"sLen":0,"time":49887.64,"type":0},{"id":3,"sLen":0,"time":50056.18,"type":0},{"id":2,"sLen":0,"time":50224.72,"type":0},{"id":3,"sLen":0,"time":50561.7969,"type":0},{"id":2,"sLen":0,"time":50898.875,"type":0},{"id":3,"sLen":0,"time":51235.9531,"type":0},{"id":1,"sLen":0,"time":51573.0352,"type":0},{"id":0,"sLen":0,"time":51910.1133,"type":0},{"id":1,"sLen":0,"time":52247.19,"type":0},{"id":2,"sLen":0,"time":52584.27,"type":0},{"id":1,"sLen":0,"time":52921.3477,"type":0},{"id":1,"sLen":0,"time":53258.4258,"type":0},{"id":1,"sLen":0,"time":53595.5039,"type":0},{"id":1,"sLen":1011.23595999998,"time":53932.5859,"type":0},{"id":2,"sLen":505.617981000003,"time":55955.0547,"type":0},{"id":1,"sLen":674.157299999999,"time":56629.2148,"type":0},{"id":0,"sLen":252.808989999998,"time":58651.6836,"type":0},{"id":2,"sLen":0,"time":58988.7656,"type":0},{"id":2,"sLen":0,"time":59325.8438,"type":0},{"id":0,"sLen":0,"time":59662.92,"type":0},{"id":0,"sLen":0,"time":60000,"type":0},{"id":2,"sLen":0,"time":60337.08,"type":0},{"id":1,"sLen":0,"time":60674.1563,"type":0},{"id":3,"sLen":0,"time":61011.2344,"type":0},{"id":1,"sLen":0,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":61685.3945,"type":0},{"id":0,"sLen":0,"time":61853.9336,"type":0},{"id":2,"sLen":0,"time":62022.4727,"type":0},{"id":0,"sLen":0,"time":62359.55,"type":0},{"id":0,"sLen":0,"time":62696.63,"type":0},{"id":2,"sLen":0,"time":63033.707,"type":0},{"id":1,"sLen":0,"time":63370.7852,"type":0},{"id":3,"sLen":0,"time":63707.8633,"type":0},{"id":1,"sLen":0,"time":64044.9453,"type":0},{"id":1,"sLen":0,"time":64382.0234,"type":0},{"id":0,"sLen":0,"time":64550.5625,"type":0},{"id":1,"sLen":0,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":3,"sLen":0,"time":65393.2578,"type":0},{"id":1,"sLen":0,"time":65730.3359,"type":0},{"id":3,"sLen":505.617981000003,"time":66067.4141,"type":0},{"id":1,"sLen":505.617981000003,"time":66741.57,"type":0},{"id":0,"sLen":505.617981000003,"time":67415.73,"type":0},{"id":1,"sLen":0,"time":68089.89,"type":0},{"id":0,"sLen":0,"time":68426.97,"type":0},{"id":3,"sLen":589.887633999999,"time":68764.05,"type":0},{"id":0,"sLen":337.078643999994,"time":69438.2,"type":0},{"id":1,"sLen":0,"time":70112.36,"type":0},{"id":0,"sLen":0,"time":70449.44,"type":0},{"id":3,"sLen":0,"time":70786.5156,"type":0},{"id":1,"sLen":0,"time":71123.59,"type":0},{"id":0,"sLen":0,"time":71460.67,"type":0},{"id":0,"sLen":0,"time":71797.75,"type":0},{"id":0,"sLen":0,"time":72134.83,"type":0},{"id":0,"sLen":0,"time":72303.37,"type":0},{"id":0,"sLen":0,"time":72471.91,"type":0},{"id":1,"sLen":0,"time":72808.99,"type":0},{"id":3,"sLen":0,"time":73146.07,"type":0},{"id":1,"sLen":0,"time":73314.61,"type":0},{"id":3,"sLen":0,"time":73651.69,"type":0},{"id":1,"sLen":0,"time":73820.23,"type":0},{"id":1,"sLen":0,"time":74157.3047,"type":0},{"id":3,"sLen":0,"time":74494.38,"type":0},{"id":1,"sLen":0,"time":74831.46,"type":0},{"id":1,"sLen":0,"time":75168.54,"type":0},{"id":0,"sLen":0,"time":75337.08,"type":0},{"id":1,"sLen":1264.04492,"time":75505.62,"type":0},{"id":0,"sLen":1264.04492,"time":76853.93,"type":0},{"id":3,"sLen":1264.04492,"time":78202.25,"type":0},{"id":0,"sLen":1011.23596000001,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":80898.875,"type":0},{"id":0,"sLen":0,"time":81235.95,"type":0},{"id":3,"sLen":0,"time":81573.03,"type":0},{"id":0,"sLen":0,"time":81910.11,"type":0},{"id":3,"sLen":0,"time":82247.19,"type":0},{"id":3,"sLen":0,"time":82584.27,"type":0},{"id":0,"sLen":0,"time":82752.81,"type":0},{"id":1,"sLen":252.808990000005,"time":82921.35,"type":0},{"id":0,"sLen":0,"time":83258.43,"type":0},{"id":3,"sLen":0,"time":83595.51,"type":0},{"id":1,"sLen":0,"time":83932.5859,"type":0},{"id":0,"sLen":0,"time":84269.6641,"type":0},{"id":3,"sLen":0,"time":84606.74,"type":0},{"id":1,"sLen":0,"time":84943.82,"type":0},{"id":0,"sLen":0,"time":85196.63,"type":0},{"id":1,"sLen":0,"time":85449.44,"type":0},{"id":0,"sLen":0,"time":85617.98,"type":0},{"id":2,"sLen":0,"time":85955.0547,"type":0},{"id":0,"sLen":0,"time":91685.39,"type":0},{"id":1,"sLen":0,"time":92022.47,"type":0},{"id":3,"sLen":0,"time":92359.55,"type":0},{"id":0,"sLen":0,"time":92696.63,"type":0},{"id":0,"sLen":0,"time":93033.71,"type":0},{"id":1,"sLen":0,"time":93370.79,"type":0},{"id":3,"sLen":0,"time":93707.87,"type":0},{"id":0,"sLen":0,"time":94044.9453,"type":0},{"id":2,"sLen":0,"time":94382.02,"type":0},{"id":3,"sLen":0,"time":94719.1,"type":0},{"id":0,"sLen":0,"time":95056.18,"type":0},{"id":3,"sLen":0,"time":95393.26,"type":0},{"id":0,"sLen":0,"time":95730.3359,"type":0},{"id":3,"sLen":0,"time":96067.4141,"type":0},{"id":0,"sLen":0,"time":96404.49,"type":0},{"id":3,"sLen":337.078643999994,"time":96741.57,"type":0},{"id":0,"sLen":589.887633999955,"time":98426.97,"type":0},{"id":2,"sLen":589.887633999999,"time":99101.125,"type":0},{"id":0,"sLen":589.887633999999,"time":101123.594,"type":0},{"id":2,"sLen":337.078644000008,"time":101797.75,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":3,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":3,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":3,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":3,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104831.461,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":3,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":3,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":3,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":3,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107528.086,"type":0},{"id":3,"sLen":589.887634000028,"time":107865.172,"type":0},{"id":1,"sLen":589.887634000028,"time":108539.328,"type":0},{"id":0,"sLen":589.887633999999,"time":109213.484,"type":0},{"id":1,"sLen":589.887633999999,"time":109887.641,"type":0},{"id":1,"sLen":0,"time":110561.8,"type":0},{"id":1,"sLen":0,"time":110898.875,"type":0},{"id":0,"sLen":0,"time":111067.414,"type":0},{"id":1,"sLen":0,"time":111235.953,"type":0},{"id":1,"sLen":0,"time":111573.031,"type":0},{"id":0,"sLen":0,"time":111741.57,"type":0},{"id":1,"sLen":0,"time":111910.109,"type":0},{"id":1,"sLen":0,"time":112247.188,"type":0},{"id":0,"sLen":0,"time":112415.727,"type":0},{"id":1,"sLen":0,"time":112584.273,"type":0},{"id":1,"sLen":0,"time":112921.352,"type":0},{"id":0,"sLen":0,"time":113089.891,"type":0},{"id":3,"sLen":589.887633999999,"time":113258.43,"type":0},{"id":1,"sLen":589.887633999999,"time":113932.586,"type":0},{"id":0,"sLen":589.887633999984,"time":114606.742,"type":0},{"id":1,"sLen":337.078643999994,"time":115280.9,"type":0},{"id":2,"sLen":0,"time":115955.055,"type":0},{"id":3,"sLen":0,"time":116292.133,"type":0},{"id":0,"sLen":0,"time":116629.211,"type":0},{"id":1,"sLen":0,"time":116966.289,"type":0},{"id":1,"sLen":0,"time":117303.367,"type":0},{"id":3,"sLen":0,"time":117640.453,"type":0},{"id":1,"sLen":0,"time":117977.531,"type":0},{"id":1,"sLen":0,"time":118314.609,"type":0},{"id":0,"sLen":0,"time":118483.148,"type":0}],"position":"boyfriend","characters":["bf-holding-gf"],"visible":true,"type":1},{"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539325842697,"type":0},{"id":3,"sLen":0,"time":252.808988764045,"type":0},{"id":3,"sLen":0,"time":421.348314606742,"type":0},{"id":0,"sLen":0,"time":589.887640449438,"type":0},{"id":3,"sLen":0,"time":758.426966292135,"type":0},{"id":3,"sLen":0,"time":842.696629213483,"type":0},{"id":0,"sLen":0,"time":1011.23595505618,"type":0},{"id":0,"sLen":0,"time":1179.77528089888,"type":0},{"id":0,"sLen":0,"time":1264.04494382022,"type":0},{"id":3,"sLen":0,"time":1348.31460674157,"type":0},{"id":0,"sLen":0,"time":1516.85393258427,"type":0},{"id":0,"sLen":0,"time":1601.12359550562,"type":0},{"id":3,"sLen":0,"time":1853.93258426966,"type":0},{"id":0,"sLen":0,"time":2022.47191011236,"type":0},{"id":3,"sLen":0,"time":2106.74157303371,"type":0},{"id":3,"sLen":0,"time":2191.01123595506,"type":0},{"id":0,"sLen":0,"time":2359.55056179775,"type":0},{"id":0,"sLen":0,"time":2696.62921348315,"type":0},{"id":3,"sLen":0,"time":2865.16853932584,"type":0},{"id":3,"sLen":0,"time":2949.43820224719,"type":0},{"id":3,"sLen":0,"time":3117.97752808989,"type":0},{"id":0,"sLen":0,"time":3286.51685393258,"type":0},{"id":3,"sLen":0,"time":3455.05617977528,"type":0},{"id":3,"sLen":0,"time":3539.32584269663,"type":0},{"id":0,"sLen":0,"time":3707.86516853933,"type":0},{"id":0,"sLen":0,"time":3876.40449438202,"type":0},{"id":0,"sLen":0,"time":3960.67415730337,"type":0},{"id":3,"sLen":0,"time":4044.94382022472,"type":0},{"id":0,"sLen":0,"time":4213.48314606742,"type":0},{"id":0,"sLen":0,"time":4297.75280898876,"type":0},{"id":3,"sLen":0,"time":4550.56179775281,"type":0},{"id":0,"sLen":0,"time":4719.10112359551,"type":0},{"id":3,"sLen":0,"time":4803.37078651685,"type":0},{"id":3,"sLen":0,"time":4887.6404494382,"type":0},{"id":0,"sLen":0,"time":5056.1797752809,"type":0},{"id":0,"sLen":0,"time":5140.44943820225,"type":0},{"id":0,"sLen":0,"time":5224.7191011236,"type":0},{"id":0,"sLen":0,"time":5393.25842696629,"type":0},{"id":3,"sLen":0,"time":5561.79775280899,"type":0},{"id":3,"sLen":0,"time":5646.06741573034,"type":0},{"id":3,"sLen":0,"time":5814.60674157303,"type":0},{"id":0,"sLen":0,"time":5983.14606741573,"type":0},{"id":3,"sLen":0,"time":6151.68539325843,"type":0},{"id":3,"sLen":0,"time":6235.95505617978,"type":0},{"id":0,"sLen":0,"time":6404.49438202247,"type":0},{"id":0,"sLen":0,"time":6573.03370786517,"type":0},{"id":0,"sLen":0,"time":6657.30337078652,"type":0},{"id":3,"sLen":0,"time":6741.57303370786,"type":0},{"id":0,"sLen":0,"time":6910.11235955056,"type":0},{"id":0,"sLen":0,"time":6994.38202247191,"type":0},{"id":3,"sLen":0,"time":7247.19101123595,"type":0},{"id":0,"sLen":0,"time":7415.73033707865,"type":0},{"id":3,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7584.26966292135,"type":0},{"id":0,"sLen":0,"time":7752.80898876405,"type":0},{"id":0,"sLen":0,"time":8089.88764044944,"type":0},{"id":3,"sLen":0,"time":8258.42696629213,"type":0},{"id":3,"sLen":0,"time":8342.69662921348,"type":0},{"id":3,"sLen":0,"time":8511.23595505618,"type":0},{"id":0,"sLen":0,"time":8679.77528089888,"type":0},{"id":3,"sLen":0,"time":8848.31460674157,"type":0},{"id":3,"sLen":0,"time":8932.58426966292,"type":0},{"id":0,"sLen":0,"time":9101.12359550562,"type":0},{"id":0,"sLen":0,"time":9269.66292134831,"type":0},{"id":0,"sLen":0,"time":9353.93258426966,"type":0},{"id":3,"sLen":0,"time":9438.20224719101,"type":0},{"id":0,"sLen":0,"time":9606.74157303371,"type":0},{"id":0,"sLen":0,"time":9691.01123595505,"type":0},{"id":3,"sLen":0,"time":9943.8202247191,"type":0},{"id":0,"sLen":0,"time":10112.3595505618,"type":0},{"id":3,"sLen":0,"time":10196.6292134831,"type":0},{"id":3,"sLen":0,"time":10280.8988764045,"type":0},{"id":0,"sLen":0,"time":10449.4382022472,"type":0},{"id":0,"sLen":0,"time":21235.9550561798,"type":0},{"id":3,"sLen":0,"time":21320.2247191011,"type":0},{"id":0,"sLen":0,"time":21404.4943820225,"type":0},{"id":0,"sLen":0,"time":21573.0337078652,"type":0},{"id":3,"sLen":0,"time":21910.1123595506,"type":0},{"id":0,"sLen":0,"time":22247.191011236,"type":0},{"id":3,"sLen":0,"time":22584.2696629213,"type":0},{"id":0,"sLen":0,"time":22921.3483146067,"type":0},{"id":0,"sLen":0,"time":23258.4269662921,"type":0},{"id":3,"sLen":0,"time":23595.5056179775,"type":0},{"id":3,"sLen":0,"time":23932.5842696629,"type":0},{"id":0,"sLen":0,"time":24269.6629213483,"type":0},{"id":3,"sLen":0,"time":24606.7415730337,"type":0},{"id":0,"sLen":0,"time":24943.8202247191,"type":0},{"id":3,"sLen":0,"time":25280.8988764045,"type":0},{"id":0,"sLen":0,"time":25617.9775280899,"type":0},{"id":0,"sLen":0,"time":25955.0561797753,"type":0},{"id":3,"sLen":0,"time":26292.1348314607,"type":0},{"id":3,"sLen":0,"time":26629.2134831461,"type":0},{"id":3,"sLen":0,"time":26713.4831460674,"type":0},{"id":3,"sLen":0,"time":26797.7528089888,"type":0},{"id":3,"sLen":0,"time":26966.2921348315,"type":0},{"id":0,"sLen":0,"time":27303.3707865169,"type":0},{"id":0,"sLen":0,"time":27640.4494382022,"type":0},{"id":3,"sLen":0,"time":27977.5280898876,"type":0},{"id":3,"sLen":0,"time":28314.606741573,"type":0},{"id":0,"sLen":0,"time":28651.6853932584,"type":0},{"id":3,"sLen":0,"time":28988.7640449438,"type":0},{"id":0,"sLen":0,"time":29325.8426966292,"type":0},{"id":0,"sLen":0,"time":29662.9213483146,"type":0},{"id":0,"sLen":0,"time":29831.4606741573,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30168.5393258427,"type":0},{"id":3,"sLen":0,"time":30337.0786516854,"type":0},{"id":3,"sLen":0,"time":30505.6179775281,"type":0},{"id":3,"sLen":0,"time":30674.1573033708,"type":0},{"id":0,"sLen":0,"time":30842.6966292135,"type":0},{"id":0,"sLen":0,"time":31011.2359550562,"type":0},{"id":3,"sLen":0,"time":31348.3146067416,"type":0},{"id":3,"sLen":0,"time":31685.393258427,"type":0},{"id":0,"sLen":0,"time":31853.9325842697,"type":0},{"id":0,"sLen":0,"time":32359.5505617978,"type":0},{"id":3,"sLen":0,"time":32696.6292134831,"type":0},{"id":0,"sLen":0,"time":33033.7078651685,"type":0},{"id":0,"sLen":0,"time":33202.2471910112,"type":0},{"id":3,"sLen":0,"time":33370.7865168539,"type":0},{"id":0,"sLen":0,"time":33707.8651685393,"type":0},{"id":3,"sLen":0,"time":34044.9438202247,"type":0},{"id":3,"sLen":0,"time":34382.0224719101,"type":0},{"id":0,"sLen":0,"time":34550.5617977528,"type":0},{"id":3,"sLen":0,"time":34719.1011235955,"type":0},{"id":0,"sLen":0,"time":35056.1797752809,"type":0},{"id":3,"sLen":0,"time":35393.2584269663,"type":0},{"id":0,"sLen":0,"time":35730.3370786517,"type":0},{"id":0,"sLen":0,"time":35898.8764044944,"type":0},{"id":3,"sLen":0,"time":36067.4157303371,"type":0},{"id":0,"sLen":0,"time":36404.4943820225,"type":0},{"id":3,"sLen":0,"time":36741.5730337079,"type":0},{"id":3,"sLen":0,"time":37078.6516853933,"type":0},{"id":0,"sLen":0,"time":37247.191011236,"type":0},{"id":3,"sLen":0,"time":37415.7303370787,"type":0},{"id":0,"sLen":0,"time":37752.808988764,"type":0},{"id":3,"sLen":0,"time":38089.8876404494,"type":0},{"id":3,"sLen":0,"time":38426.9662921348,"type":0},{"id":0,"sLen":0,"time":38595.5056179775,"type":0},{"id":3,"sLen":0,"time":38764.0449438202,"type":0},{"id":0,"sLen":0,"time":39101.1235955056,"type":0},{"id":3,"sLen":0,"time":39438.202247191,"type":0},{"id":3,"sLen":0,"time":39775.2808988764,"type":0},{"id":0,"sLen":0,"time":39943.8202247191,"type":0},{"id":3,"sLen":0,"time":40112.3595505618,"type":0},{"id":0,"sLen":0,"time":40449.4382022472,"type":0},{"id":3,"sLen":0,"time":40786.5168539326,"type":0},{"id":3,"sLen":0,"time":41123.595505618,"type":0},{"id":0,"sLen":0,"time":41292.1348314607,"type":0},{"id":3,"sLen":0,"time":41460.6741573034,"type":0},{"id":0,"sLen":0,"time":41797.7528089888,"type":0},{"id":0,"sLen":0,"time":42134.8314606742,"type":0},{"id":0,"sLen":0,"time":42471.9101123596,"type":0},{"id":0,"sLen":0,"time":42640.4494382022,"type":0},{"id":3,"sLen":0,"time":42808.9887640449,"type":0},{"id":3,"sLen":0,"time":42893.2584269663,"type":0},{"id":3,"sLen":0,"time":42977.5280898876,"type":0},{"id":0,"sLen":0,"time":43146.0674157303,"type":0},{"id":3,"sLen":0,"time":43483.1460674157,"type":0},{"id":3,"sLen":0,"time":43820.2247191011,"type":0},{"id":0,"sLen":0,"time":43988.7640449438,"type":0},{"id":3,"sLen":0,"time":44157.3033707865,"type":0},{"id":0,"sLen":0,"time":44494.3820224719,"type":0},{"id":3,"sLen":0,"time":44831.4606741573,"type":0},{"id":0,"sLen":0,"time":45168.5393258427,"type":0},{"id":0,"sLen":0,"time":45337.0786516854,"type":0},{"id":3,"sLen":0,"time":45505.6179775281,"type":0},{"id":0,"sLen":0,"time":45842.6966292135,"type":0},{"id":3,"sLen":0,"time":46179.7752808989,"type":0},{"id":3,"sLen":0,"time":46516.8539325843,"type":0},{"id":0,"sLen":0,"time":46685.393258427,"type":0},{"id":3,"sLen":0,"time":46853.9325842697,"type":0},{"id":0,"sLen":0,"time":47191.0112359551,"type":0},{"id":3,"sLen":0,"time":47528.0898876404,"type":0},{"id":3,"sLen":0,"time":47865.1685393258,"type":0},{"id":0,"sLen":0,"time":48033.7078651685,"type":0},{"id":3,"sLen":0,"time":48202.2471910112,"type":0},{"id":0,"sLen":0,"time":48539.3258426966,"type":0},{"id":3,"sLen":0,"time":48876.404494382,"type":0},{"id":3,"sLen":0,"time":49213.4831460674,"type":0},{"id":0,"sLen":0,"time":49382.0224719101,"type":0},{"id":3,"sLen":0,"time":49550.5617977528,"type":0},{"id":0,"sLen":0,"time":49887.6404494382,"type":0},{"id":3,"sLen":0,"time":50224.7191011236,"type":0},{"id":0,"sLen":0,"time":50561.797752809,"type":0},{"id":0,"sLen":0,"time":50730.3370786517,"type":0},{"id":3,"sLen":0,"time":50898.8764044944,"type":0},{"id":0,"sLen":0,"time":51235.9550561798,"type":0},{"id":3,"sLen":0,"time":51573.0337078652,"type":0},{"id":3,"sLen":0,"time":51910.1123595506,"type":0},{"id":0,"sLen":0,"time":52078.6516853933,"type":0},{"id":3,"sLen":0,"time":52247.191011236,"type":0},{"id":0,"sLen":0,"time":52584.2696629213,"type":0},{"id":0,"sLen":0,"time":52921.3483146067,"type":0},{"id":0,"sLen":0,"time":53258.4269662921,"type":0},{"id":0,"sLen":0,"time":53426.9662921348,"type":0},{"id":3,"sLen":0,"time":53595.5056179775,"type":0},{"id":3,"sLen":0,"time":53679.7752808989,"type":0},{"id":3,"sLen":0,"time":53764.0449438202,"type":0},{"id":0,"sLen":0,"time":53932.5842696629,"type":0},{"id":3,"sLen":0,"time":54101.1235955056,"type":0},{"id":3,"sLen":0,"time":54185.393258427,"type":0},{"id":3,"sLen":0,"time":54353.9325842697,"type":0},{"id":0,"sLen":0,"time":54522.4719101124,"type":0},{"id":3,"sLen":0,"time":54691.0112359551,"type":0},{"id":3,"sLen":0,"time":54775.2808988764,"type":0},{"id":0,"sLen":0,"time":54943.8202247191,"type":0},{"id":0,"sLen":0,"time":55112.3595505618,"type":0},{"id":0,"sLen":0,"time":55196.6292134831,"type":0},{"id":3,"sLen":0,"time":55280.8988764045,"type":0},{"id":0,"sLen":0,"time":55449.4382022472,"type":0},{"id":0,"sLen":0,"time":55533.7078651685,"type":0},{"id":3,"sLen":0,"time":55786.5168539326,"type":0},{"id":0,"sLen":0,"time":55955.0561797753,"type":0},{"id":3,"sLen":0,"time":56039.3258426966,"type":0},{"id":3,"sLen":0,"time":56123.595505618,"type":0},{"id":0,"sLen":0,"time":56292.1348314607,"type":0},{"id":0,"sLen":0,"time":56629.2134831461,"type":0},{"id":3,"sLen":0,"time":56797.7528089888,"type":0},{"id":3,"sLen":0,"time":56882.0224719101,"type":0},{"id":3,"sLen":0,"time":57050.5617977528,"type":0},{"id":0,"sLen":0,"time":57219.1011235955,"type":0},{"id":3,"sLen":0,"time":57387.6404494382,"type":0},{"id":3,"sLen":0,"time":57471.9101123595,"type":0},{"id":0,"sLen":0,"time":57640.4494382022,"type":0},{"id":0,"sLen":0,"time":57808.9887640449,"type":0},{"id":0,"sLen":0,"time":57893.2584269663,"type":0},{"id":3,"sLen":0,"time":57977.5280898876,"type":0},{"id":0,"sLen":0,"time":58146.0674157303,"type":0},{"id":0,"sLen":0,"time":58230.3370786517,"type":0},{"id":3,"sLen":0,"time":58483.1460674157,"type":0},{"id":0,"sLen":0,"time":58651.6853932584,"type":0},{"id":3,"sLen":0,"time":58735.9550561798,"type":0},{"id":3,"sLen":0,"time":58820.2247191011,"type":0},{"id":0,"sLen":0,"time":58988.7640449438,"type":0},{"id":0,"sLen":0,"time":59073.0337078652,"type":0},{"id":0,"sLen":0,"time":59157.3033707865,"type":0},{"id":0,"sLen":0,"time":59325.8426966292,"type":0},{"id":3,"sLen":0,"time":59494.3820224719,"type":0},{"id":3,"sLen":0,"time":59578.6516853933,"type":0},{"id":3,"sLen":0,"time":59747.191011236,"type":0},{"id":0,"sLen":0,"time":59915.7303370786,"type":0},{"id":3,"sLen":0,"time":60084.2696629213,"type":0},{"id":3,"sLen":0,"time":60168.5393258427,"type":0},{"id":0,"sLen":0,"time":60337.0786516854,"type":0},{"id":0,"sLen":0,"time":60505.6179775281,"type":0},{"id":0,"sLen":0,"time":60589.8876404494,"type":0},{"id":3,"sLen":0,"time":60674.1573033708,"type":0},{"id":0,"sLen":0,"time":60842.6966292135,"type":0},{"id":0,"sLen":0,"time":60926.9662921348,"type":0},{"id":3,"sLen":0,"time":61179.7752808989,"type":0},{"id":0,"sLen":0,"time":61348.3146067416,"type":0},{"id":3,"sLen":0,"time":61432.5842696629,"type":0},{"id":3,"sLen":0,"time":61516.8539325843,"type":0},{"id":0,"sLen":0,"time":61685.393258427,"type":0},{"id":0,"sLen":0,"time":62022.4719101124,"type":0},{"id":3,"sLen":0,"time":62191.0112359551,"type":0},{"id":3,"sLen":0,"time":62275.2808988764,"type":0},{"id":3,"sLen":0,"time":62443.8202247191,"type":0},{"id":0,"sLen":0,"time":62612.3595505618,"type":0},{"id":3,"sLen":0,"time":62780.8988764045,"type":0},{"id":3,"sLen":0,"time":62865.1685393258,"type":0},{"id":0,"sLen":0,"time":63033.7078651685,"type":0},{"id":0,"sLen":0,"time":63202.2471910112,"type":0},{"id":0,"sLen":0,"time":63286.5168539326,"type":0},{"id":3,"sLen":0,"time":63370.7865168539,"type":0},{"id":0,"sLen":0,"time":63539.3258426966,"type":0},{"id":0,"sLen":0,"time":63623.595505618,"type":0},{"id":3,"sLen":0,"time":63876.404494382,"type":0},{"id":0,"sLen":0,"time":64044.9438202247,"type":0},{"id":3,"sLen":0,"time":64129.2134831461,"type":0},{"id":3,"sLen":0,"time":64213.4831460674,"type":0},{"id":0,"sLen":0,"time":64382.0224719101,"type":0},{"id":3,"sLen":0,"time":64719.1011235955,"type":0},{"id":0,"sLen":0,"time":65056.1797752809,"type":0},{"id":0,"sLen":0,"time":65393.2584269663,"type":0},{"id":0,"sLen":0,"time":65730.3370786517,"type":0},{"id":0,"sLen":0,"time":66067.4157303371,"type":0},{"id":3,"sLen":0,"time":66404.4943820225,"type":0},{"id":3,"sLen":0,"time":66741.5730337079,"type":0},{"id":3,"sLen":0,"time":67078.6516853933,"type":0},{"id":3,"sLen":0,"time":67415.7303370787,"type":0},{"id":0,"sLen":0,"time":67752.808988764,"type":0},{"id":0,"sLen":0,"time":68089.8876404494,"type":0},{"id":0,"sLen":0,"time":68426.9662921348,"type":0},{"id":0,"sLen":0,"time":68764.0449438202,"type":0},{"id":3,"sLen":0,"time":69101.1235955056,"type":0},{"id":3,"sLen":0,"time":69438.202247191,"type":0},{"id":3,"sLen":0,"time":69775.2808988764,"type":0},{"id":3,"sLen":0,"time":69859.5505617978,"type":0},{"id":3,"sLen":0,"time":69943.8202247191,"type":0},{"id":3,"sLen":0,"time":70112.3595505618,"type":0},{"id":0,"sLen":0,"time":70449.4382022472,"type":0},{"id":0,"sLen":0,"time":70786.5168539326,"type":0},{"id":0,"sLen":0,"time":71123.595505618,"type":0},{"id":0,"sLen":0,"time":71460.6741573034,"type":0},{"id":3,"sLen":0,"time":71797.7528089888,"type":0},{"id":3,"sLen":0,"time":72134.8314606741,"type":0},{"id":3,"sLen":0,"time":72471.9101123595,"type":0},{"id":3,"sLen":0,"time":72556.1797752809,"type":0},{"id":3,"sLen":0,"time":72640.4494382023,"type":0},{"id":3,"sLen":0,"time":72808.9887640449,"type":0},{"id":3,"sLen":0,"time":72893.2584269663,"type":0},{"id":3,"sLen":0,"time":72977.5280898877,"type":0},{"id":0,"sLen":0,"time":73146.0674157303,"type":0},{"id":0,"sLen":0,"time":73230.3370786517,"type":0},{"id":0,"sLen":0,"time":73314.6067415731,"type":0},{"id":0,"sLen":0,"time":73483.1460674157,"type":0},{"id":0,"sLen":0,"time":73567.4157303371,"type":0},{"id":0,"sLen":0,"time":73651.6853932585,"type":0},{"id":0,"sLen":0,"time":73820.2247191011,"type":0},{"id":0,"sLen":0,"time":73904.4943820225,"type":0},{"id":0,"sLen":0,"time":73988.7640449438,"type":0},{"id":0,"sLen":0,"time":74157.3033707866,"type":0},{"id":0,"sLen":0,"time":74241.5730337079,"type":0},{"id":0,"sLen":0,"time":74325.8426966292,"type":0},{"id":0,"sLen":0,"time":74410.1123595506,"type":0},{"id":3,"sLen":0,"time":74494.382022472,"type":0},{"id":3,"sLen":0,"time":74578.6516853933,"type":0},{"id":3,"sLen":0,"time":74662.9213483146,"type":0},{"id":3,"sLen":0,"time":74747.191011236,"type":0},{"id":0,"sLen":0,"time":74831.4606741573,"type":0},{"id":0,"sLen":0,"time":74915.7303370787,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75084.2696629214,"type":0},{"id":3,"sLen":0,"time":75168.5393258427,"type":0},{"id":0,"sLen":0,"time":75505.6179775281,"type":0},{"id":3,"sLen":0,"time":75842.6966292135,"type":0},{"id":0,"sLen":0,"time":76179.7752808989,"type":0},{"id":0,"sLen":0,"time":76516.8539325843,"type":0},{"id":3,"sLen":0,"time":76853.9325842697,"type":0},{"id":3,"sLen":0,"time":77191.0112359551,"type":0},{"id":0,"sLen":0,"time":77528.0898876405,"type":0},{"id":3,"sLen":0,"time":77865.1685393259,"type":0},{"id":3,"sLen":0,"time":78033.7078651686,"type":0},{"id":0,"sLen":0,"time":78202.2471910113,"type":0},{"id":0,"sLen":0,"time":78539.3258426967,"type":0},{"id":3,"sLen":0,"time":78876.4044943821,"type":0},{"id":0,"sLen":0,"time":79213.4831460675,"type":0},{"id":0,"sLen":0,"time":79550.5617977529,"type":0},{"id":3,"sLen":0,"time":79887.6404494383,"type":0},{"id":0,"sLen":0,"time":80140.4494382023,"type":0},{"id":0,"sLen":0,"time":80224.7191011237,"type":0},{"id":0,"sLen":0,"time":80308.988764045,"type":0},{"id":0,"sLen":0,"time":80477.5280898877,"type":0},{"id":3,"sLen":84.269662921346,"time":80730.3370786517,"type":0},{"id":0,"sLen":0,"time":80898.8764044945,"type":0},{"id":3,"sLen":0,"time":81067.4157303371,"type":0},{"id":0,"sLen":0,"time":81235.9550561799,"type":0},{"id":3,"sLen":0,"time":81404.4943820225,"type":0},{"id":0,"sLen":0,"time":81573.0337078652,"type":0},{"id":3,"sLen":0,"time":81741.5730337079,"type":0},{"id":0,"sLen":0,"time":81910.1123595506,"type":0},{"id":3,"sLen":0,"time":82078.6516853933,"type":0},{"id":3,"sLen":0,"time":82247.191011236,"type":0},{"id":0,"sLen":0,"time":82415.7303370787,"type":0},{"id":3,"sLen":0,"time":82584.2696629214,"type":0},{"id":0,"sLen":0,"time":82752.8089887641,"type":0},{"id":3,"sLen":0,"time":82921.3483146068,"type":0},{"id":0,"sLen":0,"time":83089.8876404495,"type":0},{"id":3,"sLen":0,"time":83258.4269662922,"type":0},{"id":3,"sLen":0,"time":83426.9662921349,"type":0},{"id":0,"sLen":0,"time":83595.5056179776,"type":0},{"id":0,"sLen":0,"time":83764.0449438203,"type":0},{"id":0,"sLen":0,"time":83932.584269663,"type":0},{"id":3,"sLen":0,"time":84269.6629213484,"type":0},{"id":3,"sLen":0,"time":84606.7415730338,"type":0},{"id":3,"sLen":0,"time":84775.2808988765,"type":0},{"id":3,"sLen":0,"time":84943.8202247192,"type":0},{"id":3,"sLen":0,"time":85280.8988764046,"type":0},{"id":0,"sLen":0,"time":85617.97752809,"type":0},{"id":0,"sLen":0,"time":85702.2471910113,"type":0},{"id":3,"sLen":0,"time":85870.786516854,"type":0},{"id":0,"sLen":0,"time":86039.3258426967,"type":0},{"id":0,"sLen":0,"time":86292.1348314607,"type":0},{"id":3,"sLen":0,"time":86629.2134831461,"type":0},{"id":0,"sLen":0,"time":87134.8314606741,"type":0},{"id":3,"sLen":0,"time":87303.3707865169,"type":0},{"id":0,"sLen":0,"time":87640.4494382023,"type":0},{"id":3,"sLen":0,"time":87977.5280898877,"type":0},{"id":0,"sLen":0,"time":88483.1460674157,"type":0},{"id":3,"sLen":0,"time":88651.6853932584,"type":0},{"id":0,"sLen":0,"time":88988.7640449438,"type":0},{"id":3,"sLen":0,"time":89325.8426966292,"type":0},{"id":0,"sLen":0,"time":89831.4606741573,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.0786516854,"type":0},{"id":3,"sLen":0,"time":90674.1573033708,"type":0},{"id":0,"sLen":0,"time":91179.7752808989,"type":0},{"id":3,"sLen":0,"time":91348.3146067416,"type":0},{"id":0,"sLen":0,"time":91685.393258427,"type":0},{"id":3,"sLen":0,"time":92022.4719101124,"type":0},{"id":0,"sLen":0,"time":92528.0898876405,"type":0},{"id":3,"sLen":0,"time":92696.6292134832,"type":0},{"id":0,"sLen":0,"time":93033.7078651686,"type":0},{"id":3,"sLen":0,"time":93370.786516854,"type":0},{"id":0,"sLen":0,"time":93876.404494382,"type":0},{"id":3,"sLen":0,"time":94044.9438202248,"type":0},{"id":0,"sLen":0,"time":94382.0224719102,"type":0},{"id":3,"sLen":0,"time":94719.1011235956,"type":0},{"id":0,"sLen":0,"time":95224.7191011236,"type":0},{"id":3,"sLen":0,"time":95393.2584269663,"type":0},{"id":0,"sLen":0,"time":95730.3370786517,"type":0},{"id":0,"sLen":0,"time":96067.4157303371,"type":0},{"id":0,"sLen":0,"time":96404.4943820225,"type":0},{"id":0,"sLen":0,"time":96573.0337078652,"type":0},{"id":3,"sLen":0,"time":96741.5730337079,"type":0},{"id":3,"sLen":0,"time":96825.8426966292,"type":0},{"id":3,"sLen":0,"time":96910.1123595505,"type":0},{"id":0,"sLen":0,"time":97078.6516853933,"type":0},{"id":3,"sLen":0,"time":97415.7303370787,"type":0},{"id":3,"sLen":0,"time":97752.808988764,"type":0},{"id":0,"sLen":0,"time":97921.3483146067,"type":0},{"id":3,"sLen":0,"time":98089.8876404494,"type":0},{"id":0,"sLen":0,"time":98426.9662921348,"type":0},{"id":3,"sLen":0,"time":98764.0449438202,"type":0},{"id":0,"sLen":0,"time":99101.1235955056,"type":0},{"id":0,"sLen":0,"time":99269.6629213483,"type":0},{"id":3,"sLen":0,"time":99438.202247191,"type":0},{"id":0,"sLen":0,"time":99775.2808988764,"type":0},{"id":3,"sLen":0,"time":100112.359550562,"type":0},{"id":3,"sLen":0,"time":100449.438202247,"type":0},{"id":0,"sLen":0,"time":100617.97752809,"type":0},{"id":3,"sLen":0,"time":100786.516853933,"type":0},{"id":0,"sLen":0,"time":101123.595505618,"type":0},{"id":3,"sLen":0,"time":101460.674157303,"type":0},{"id":3,"sLen":0,"time":101797.752808989,"type":0},{"id":0,"sLen":0,"time":101966.292134831,"type":0},{"id":3,"sLen":0,"time":102134.831460674,"type":0},{"id":0,"sLen":0,"time":102471.91011236,"type":0},{"id":3,"sLen":0,"time":102808.988764045,"type":0},{"id":3,"sLen":0,"time":103146.06741573,"type":0},{"id":0,"sLen":0,"time":103314.606741573,"type":0},{"id":3,"sLen":0,"time":103483.146067416,"type":0},{"id":0,"sLen":0,"time":103820.224719101,"type":0},{"id":3,"sLen":0,"time":104157.303370787,"type":0},{"id":0,"sLen":0,"time":104494.382022472,"type":0},{"id":0,"sLen":0,"time":104662.921348315,"type":0},{"id":3,"sLen":0,"time":104831.460674157,"type":0},{"id":0,"sLen":0,"time":105168.539325843,"type":0},{"id":3,"sLen":0,"time":105505.617977528,"type":0},{"id":3,"sLen":0,"time":105842.696629213,"type":0},{"id":0,"sLen":0,"time":106011.235955056,"type":0},{"id":3,"sLen":0,"time":106179.775280899,"type":0},{"id":0,"sLen":0,"time":106516.853932584,"type":0},{"id":0,"sLen":0,"time":106853.93258427,"type":0},{"id":0,"sLen":0,"time":107191.011235955,"type":0},{"id":3,"sLen":0,"time":107528.08988764,"type":0},{"id":0,"sLen":0,"time":107865.168539326,"type":0},{"id":0,"sLen":0,"time":108202.247191011,"type":0},{"id":0,"sLen":0,"time":108539.325842697,"type":0},{"id":0,"sLen":0,"time":108876.404494382,"type":0},{"id":3,"sLen":0,"time":109213.483146068,"type":0},{"id":3,"sLen":0,"time":109550.561797753,"type":0},{"id":3,"sLen":0,"time":109887.640449438,"type":0},{"id":3,"sLen":0,"time":110224.719101124,"type":0},{"id":0,"sLen":0,"time":110561.797752809,"type":0},{"id":0,"sLen":0,"time":110730.337078652,"type":0},{"id":0,"sLen":0,"time":110898.876404495,"type":0},{"id":3,"sLen":0,"time":111235.95505618,"type":0},{"id":3,"sLen":0,"time":111573.033707865,"type":0},{"id":3,"sLen":0,"time":111910.112359551,"type":0},{"id":3,"sLen":0,"time":112247.191011236,"type":0},{"id":0,"sLen":0,"time":112584.269662922,"type":0},{"id":3,"sLen":0,"time":112921.348314607,"type":0},{"id":0,"sLen":0,"time":113258.426966292,"type":0},{"id":3,"sLen":0,"time":113932.584269663,"type":0},{"id":0,"sLen":0,"time":114269.662921349,"type":0},{"id":3,"sLen":0,"time":114438.202247191,"type":0},{"id":0,"sLen":0,"time":114606.741573034,"type":0},{"id":3,"sLen":0,"time":114943.820224719,"type":0},{"id":0,"sLen":0,"time":115280.898876405,"type":0},{"id":3,"sLen":0,"time":115617.97752809,"type":0},{"id":3,"sLen":0,"time":115786.516853933,"type":0},{"id":0,"sLen":0,"time":115955.056179776,"type":0},{"id":3,"sLen":0,"time":116123.595505618,"type":0},{"id":0,"sLen":0,"time":116292.134831461,"type":0},{"id":0,"sLen":0,"time":116629.213483146,"type":0},{"id":3,"sLen":0,"time":116966.292134832,"type":0},{"id":3,"sLen":0,"time":117303.370786517,"type":0},{"id":0,"sLen":0,"time":117640.449438203,"type":0},{"id":3,"sLen":0,"time":117977.528089888,"type":0},{"id":3,"sLen":0,"time":118061.797752809,"type":0},{"id":0,"sLen":0,"time":118314.606741573,"type":0},{"id":3,"sLen":0,"time":118398.876404495,"type":0},{"id":0,"sLen":0,"time":118651.685393258,"type":0},{"id":3,"sLen":0,"time":118820.224719101,"type":0},{"id":3,"sLen":0,"time":118904.494382022,"type":0},{"id":3,"sLen":0,"time":119073.033707865,"type":0},{"id":0,"sLen":0,"time":119241.573033708,"type":0},{"id":3,"sLen":0,"time":119410.112359551,"type":0},{"id":3,"sLen":0,"time":119494.382022472,"type":0},{"id":0,"sLen":0,"time":119662.921348315,"type":0},{"id":0,"sLen":0,"time":119831.460674157,"type":0},{"id":0,"sLen":0,"time":119915.730337079,"type":0},{"id":3,"sLen":0,"time":120000,"type":0},{"id":0,"sLen":0,"time":120168.539325843,"type":0},{"id":0,"sLen":0,"time":120252.808988764,"type":0},{"id":3,"sLen":0,"time":120505.617977528,"type":0},{"id":0,"sLen":0,"time":120674.157303371,"type":0},{"id":3,"sLen":0,"time":120758.426966292,"type":0},{"id":3,"sLen":0,"time":120842.696629213,"type":0},{"id":0,"sLen":0,"time":121011.235955056,"type":0},{"id":0,"sLen":0,"time":121095.505617978,"type":0},{"id":0,"sLen":0,"time":121179.775280899,"type":0}],"position":"girlfriend","type":2,"characters":["pico-speakers"],"visible":false}],"codenameChart":true,"stage":"tank","noteTypes":["No Anim Note"]} \ No newline at end of file diff --git a/assets/songs/stress/charts/hard.json b/assets/songs/stress/charts/hard.json index ad29ed8c..12b34d42 100644 --- a/assets/songs/stress/charts/hard.json +++ b/assets/songs/stress/charts/hard.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":5393.25842696629,"name":"Camera Movement"},{"params":[0],"time":10786.5168539326,"name":"Camera Movement"},{"params":[1],"time":14831.4606741573,"name":"Camera Movement"},{"params":[0],"time":21573.0337078652,"name":"Camera Movement"},{"params":[1],"time":24269.6629213483,"name":"Camera Movement"},{"params":[0],"time":26966.2921348315,"name":"Camera Movement"},{"params":[1],"time":28314.606741573,"name":"Camera Movement"},{"params":[0],"time":31011.2359550562,"name":"Camera Movement"},{"params":[1],"time":37752.808988764,"name":"Camera Movement"},{"params":[0],"time":43146.0674157303,"name":"Camera Movement"},{"params":[1],"time":48539.3258426966,"name":"Camera Movement"},{"params":[0],"time":53932.5842696629,"name":"Camera Movement"},{"params":[1],"time":59325.8426966292,"name":"Camera Movement"},{"params":[0],"time":62022.4719101124,"name":"Camera Movement"},{"params":[0,"good!"],"time":62022.4719101124,"name":"Play Animation"},{"params":[1],"time":67415.7303370787,"name":"Camera Movement"},{"params":[0],"time":75505.6179775281,"name":"Camera Movement"},{"params":[1],"time":80898.8764044945,"name":"Camera Movement"},{"params":[0],"time":86292.1348314608,"name":"Camera Movement"},{"params":[1],"time":91685.3932584271,"name":"Camera Movement"},{"params":[0],"time":97078.6516853934,"name":"Camera Movement"},{"params":[1],"time":102471.91011236,"name":"Camera Movement"},{"params":[0],"time":107865.168539326,"name":"Camera Movement"},{"params":[1],"time":113258.426966292,"name":"Camera Movement"}],"scrollSpeed":2.6,"strumLines":[{"notes":[{"id":1,"sLen":0,"time":0,"type":0},{"id":2,"sLen":0,"time":168.539322,"type":0},{"id":0,"sLen":0,"time":337.078644,"type":0},{"id":0,"sLen":0,"time":505.617981,"type":0},{"id":2,"sLen":0,"time":674.1573,"type":0},{"id":3,"sLen":0,"time":842.696655,"type":0},{"id":1,"sLen":0,"time":1011.23596,"type":0},{"id":3,"sLen":0,"time":1179.77527,"type":0},{"id":0,"sLen":0,"time":1348.31458,"type":0},{"id":3,"sLen":0,"time":1516.85388,"type":0},{"id":2,"sLen":0,"time":1685.39331,"type":0},{"id":3,"sLen":0,"time":1853.93262,"type":0},{"id":1,"sLen":0,"time":2022.47192,"type":0},{"id":3,"sLen":0,"time":2191.01123,"type":0},{"id":0,"sLen":0,"time":2359.55054,"type":0},{"id":3,"sLen":0,"time":2528.08984,"type":0},{"id":1,"sLen":0,"time":2696.62915,"type":0},{"id":2,"sLen":0,"time":2865.16846,"type":0},{"id":0,"sLen":0,"time":3033.70776,"type":0},{"id":0,"sLen":0,"time":3202.247,"type":0},{"id":2,"sLen":0,"time":3370.78662,"type":0},{"id":3,"sLen":0,"time":3539.326,"type":0},{"id":1,"sLen":0,"time":3707.86523,"type":0},{"id":3,"sLen":0,"time":3876.40454,"type":0},{"id":0,"sLen":0,"time":4044.94385,"type":0},{"id":3,"sLen":0,"time":4213.483,"type":0},{"id":2,"sLen":0,"time":4382.02246,"type":0},{"id":3,"sLen":0,"time":4550.562,"type":0},{"id":1,"sLen":0,"time":4719.101,"type":0},{"id":3,"sLen":0,"time":4887.64063,"type":0},{"id":0,"sLen":0,"time":5056.17969,"type":0},{"id":3,"sLen":0,"time":5224.719,"type":0},{"id":0,"sLen":0,"time":11123.5957,"type":0},{"id":1,"sLen":0,"time":11292.1348,"type":0},{"id":3,"sLen":252.808989999996,"time":11460.6738,"type":0},{"id":1,"sLen":0,"time":11797.7529,"type":0},{"id":0,"sLen":0,"time":12134.8311,"type":0},{"id":0,"sLen":0,"time":12471.91,"type":0},{"id":0,"sLen":0,"time":12808.9883,"type":0},{"id":0,"sLen":0,"time":12977.5283,"type":0},{"id":2,"sLen":0,"time":13146.0674,"type":0},{"id":3,"sLen":0,"time":13230.3369,"type":0},{"id":2,"sLen":0,"time":13314.6064,"type":0},{"id":1,"sLen":0,"time":13483.1465,"type":0},{"id":3,"sLen":0,"time":13651.6855,"type":0},{"id":2,"sLen":0,"time":13820.2246,"type":0},{"id":1,"sLen":0,"time":13988.7637,"type":0},{"id":3,"sLen":0,"time":14157.3037,"type":0},{"id":2,"sLen":0,"time":14325.8428,"type":0},{"id":1,"sLen":0,"time":14494.3818,"type":0},{"id":3,"sLen":0,"time":14578.6514,"type":0},{"id":2,"sLen":0,"time":14662.9209,"type":0},{"id":0,"sLen":0,"time":14831.4609,"type":0},{"id":3,"sLen":0,"time":15000,"type":0},{"id":2,"sLen":0,"time":15168.5391,"type":0},{"id":3,"sLen":0,"time":15337.0791,"type":0},{"id":1,"sLen":0,"time":15505.6182,"type":0},{"id":3,"sLen":0,"time":15674.1572,"type":0},{"id":0,"sLen":0,"time":15842.6963,"type":0},{"id":3,"sLen":0,"time":16011.2363,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":2,"sLen":0,"time":21741.5723,"type":0},{"id":0,"sLen":0,"time":21910.1133,"type":0},{"id":2,"sLen":0,"time":22078.6523,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":2,"sLen":0,"time":22415.73,"type":0},{"id":0,"sLen":0,"time":22584.27,"type":0},{"id":2,"sLen":0,"time":22752.8086,"type":0},{"id":3,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23005.6172,"type":0},{"id":3,"sLen":0,"time":23089.8867,"type":0},{"id":3,"sLen":0,"time":23258.4277,"type":0},{"id":2,"sLen":0,"time":23426.9668,"type":0},{"id":3,"sLen":0,"time":23595.5059,"type":0},{"id":0,"sLen":0,"time":23764.0449,"type":0},{"id":1,"sLen":0,"time":23932.584,"type":0},{"id":0,"sLen":0,"time":24101.123,"type":0},{"id":2,"sLen":0,"time":24269.6621,"type":0},{"id":3,"sLen":0,"time":26966.293,"type":0},{"id":2,"sLen":0,"time":27134.832,"type":0},{"id":3,"sLen":0,"time":27303.3711,"type":0},{"id":0,"sLen":0,"time":27471.91,"type":0},{"id":3,"sLen":0,"time":27640.45,"type":0},{"id":2,"sLen":0,"time":27808.9883,"type":0},{"id":3,"sLen":0,"time":27977.5273,"type":0},{"id":0,"sLen":0,"time":28146.0684,"type":0},{"id":3,"sLen":0,"time":28314.6074,"type":0},{"id":2,"sLen":0,"time":28398.877,"type":0},{"id":3,"sLen":0,"time":28483.1465,"type":0},{"id":0,"sLen":0,"time":28651.6855,"type":0},{"id":3,"sLen":0,"time":28820.2246,"type":0},{"id":2,"sLen":0,"time":28988.7637,"type":0},{"id":0,"sLen":0,"time":29157.3027,"type":0},{"id":3,"sLen":0,"time":29325.8418,"type":0},{"id":1,"sLen":0,"time":29494.3828,"type":0},{"id":0,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":31011.2363,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":0,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":0,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":2,"sLen":0,"time":32359.55,"type":0},{"id":3,"sLen":0,"time":32528.09,"type":0},{"id":1,"sLen":0,"time":32696.6289,"type":0},{"id":3,"sLen":0,"time":32865.168,"type":0},{"id":0,"sLen":0,"time":33033.707,"type":0},{"id":3,"sLen":0,"time":33202.2461,"type":0},{"id":1,"sLen":0,"time":33370.7852,"type":0},{"id":3,"sLen":0,"time":33539.3242,"type":0},{"id":3,"sLen":0,"time":33623.5938,"type":0},{"id":1,"sLen":0,"time":33707.8633,"type":0},{"id":3,"sLen":0,"time":33876.4063,"type":0},{"id":1,"sLen":0,"time":34044.9453,"type":0},{"id":2,"sLen":0,"time":34213.4844,"type":0},{"id":0,"sLen":0,"time":34382.0234,"type":0},{"id":2,"sLen":0,"time":34550.5625,"type":0},{"id":0,"sLen":0,"time":34719.1,"type":0},{"id":3,"sLen":0,"time":34887.64,"type":0},{"id":1,"sLen":0,"time":34971.91,"type":0},{"id":2,"sLen":0,"time":35056.18,"type":0},{"id":3,"sLen":0,"time":35224.72,"type":0},{"id":2,"sLen":0,"time":35393.2578,"type":0},{"id":3,"sLen":0,"time":35561.7969,"type":0},{"id":0,"sLen":0,"time":35730.3359,"type":0},{"id":1,"sLen":0,"time":35898.875,"type":0},{"id":0,"sLen":0,"time":36067.4141,"type":0},{"id":2,"sLen":0,"time":36151.6836,"type":0},{"id":0,"sLen":0,"time":36235.9531,"type":0},{"id":3,"sLen":0,"time":36404.4961,"type":0},{"id":1,"sLen":0,"time":36573.0352,"type":0},{"id":0,"sLen":0,"time":36741.5742,"type":0},{"id":1,"sLen":0,"time":36910.1133,"type":0},{"id":0,"sLen":0,"time":37078.6523,"type":0},{"id":1,"sLen":0,"time":37247.19,"type":0},{"id":1,"sLen":0,"time":37415.73,"type":0},{"id":3,"sLen":0,"time":37584.27,"type":0},{"id":3,"sLen":0,"time":37668.54,"type":0},{"id":3,"sLen":0,"time":43146.0664,"type":0},{"id":1,"sLen":0,"time":43230.3359,"type":0},{"id":3,"sLen":0,"time":43314.6055,"type":0},{"id":0,"sLen":0,"time":43483.1445,"type":0},{"id":3,"sLen":0,"time":43651.6836,"type":0},{"id":1,"sLen":0,"time":43735.9531,"type":0},{"id":2,"sLen":0,"time":43820.2266,"type":0},{"id":0,"sLen":0,"time":43988.7656,"type":0},{"id":1,"sLen":0,"time":44157.3047,"type":0},{"id":3,"sLen":0,"time":44325.8438,"type":0},{"id":3,"sLen":0,"time":44410.1133,"type":0},{"id":2,"sLen":0,"time":44494.3828,"type":0},{"id":3,"sLen":0,"time":44662.92,"type":0},{"id":0,"sLen":0,"time":44747.19,"type":0},{"id":1,"sLen":0,"time":44831.46,"type":0},{"id":0,"sLen":0,"time":45000,"type":0},{"id":2,"sLen":0,"time":45168.54,"type":0},{"id":3,"sLen":0,"time":45337.08,"type":0},{"id":1,"sLen":0,"time":45505.6172,"type":0},{"id":3,"sLen":0,"time":45674.1563,"type":0},{"id":2,"sLen":0,"time":45758.4258,"type":0},{"id":0,"sLen":0,"time":45842.6953,"type":0},{"id":3,"sLen":0,"time":46011.2344,"type":0},{"id":1,"sLen":0,"time":46179.7734,"type":0},{"id":3,"sLen":0,"time":46348.3164,"type":0},{"id":0,"sLen":0,"time":46516.8555,"type":0},{"id":1,"sLen":0,"time":46685.3945,"type":0},{"id":3,"sLen":0,"time":46853.9336,"type":0},{"id":2,"sLen":0,"time":46938.2031,"type":0},{"id":3,"sLen":0,"time":47022.4727,"type":0},{"id":1,"sLen":0,"time":47191.01,"type":0},{"id":3,"sLen":0,"time":47359.55,"type":0},{"id":0,"sLen":0,"time":47528.09,"type":0},{"id":3,"sLen":0,"time":47696.63,"type":0},{"id":1,"sLen":0,"time":47865.168,"type":0},{"id":3,"sLen":0,"time":48033.707,"type":0},{"id":2,"sLen":0,"time":48202.2461,"type":0},{"id":2,"sLen":0,"time":48370.7852,"type":0},{"id":1,"sLen":0,"time":53932.5859,"type":0},{"id":2,"sLen":0,"time":54101.125,"type":0},{"id":0,"sLen":0,"time":54269.6641,"type":0},{"id":0,"sLen":0,"time":54438.2031,"type":0},{"id":2,"sLen":0,"time":54606.7422,"type":0},{"id":3,"sLen":0,"time":54775.28,"type":0},{"id":1,"sLen":0,"time":54943.82,"type":0},{"id":3,"sLen":0,"time":55112.36,"type":0},{"id":0,"sLen":0,"time":55280.9,"type":0},{"id":1,"sLen":0,"time":55449.4375,"type":0},{"id":2,"sLen":0,"time":55617.9766,"type":0},{"id":3,"sLen":0,"time":55786.5156,"type":0},{"id":1,"sLen":0,"time":55955.0547,"type":0},{"id":3,"sLen":0,"time":56123.5938,"type":0},{"id":0,"sLen":0,"time":56292.1367,"type":0},{"id":3,"sLen":0,"time":56460.6758,"type":0},{"id":1,"sLen":0,"time":56629.2148,"type":0},{"id":2,"sLen":0,"time":56797.7539,"type":0},{"id":0,"sLen":0,"time":56966.293,"type":0},{"id":0,"sLen":0,"time":57134.832,"type":0},{"id":2,"sLen":0,"time":57303.37,"type":0},{"id":3,"sLen":0,"time":57471.91,"type":0},{"id":1,"sLen":0,"time":57640.45,"type":0},{"id":3,"sLen":0,"time":57808.99,"type":0},{"id":0,"sLen":0,"time":57977.5273,"type":0},{"id":2,"sLen":0,"time":58146.0664,"type":0},{"id":3,"sLen":0,"time":58230.3359,"type":0},{"id":1,"sLen":0,"time":58314.6055,"type":0},{"id":3,"sLen":0,"time":58483.1445,"type":0},{"id":1,"sLen":0,"time":58651.6836,"type":0},{"id":3,"sLen":0,"time":58820.2266,"type":0},{"id":0,"sLen":0,"time":58988.7656,"type":0},{"id":3,"sLen":0,"time":59157.3047,"type":0},{"id":0,"sLen":168.539321999997,"time":59325.8438,"type":0},{"id":0,"sLen":168.539321999997,"time":60000,"type":0},{"id":0,"sLen":168.539322000004,"time":60674.1563,"type":0},{"id":0,"sLen":168.539321999997,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":62022.4719101124,"type":1},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":1,"sLen":0,"time":65224.72,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":0,"time":65730.3359,"type":0},{"id":0,"sLen":0,"time":66067.4141,"type":0},{"id":0,"sLen":0,"time":66404.49,"type":0},{"id":0,"sLen":0,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":66910.11,"type":0},{"id":2,"sLen":0,"time":67078.65,"type":0},{"id":3,"sLen":0,"time":67162.92,"type":0},{"id":2,"sLen":0,"time":67247.19,"type":0},{"id":1,"sLen":0,"time":67415.73,"type":0},{"id":3,"sLen":0,"time":67584.27,"type":0},{"id":2,"sLen":0,"time":67752.81,"type":0},{"id":1,"sLen":0,"time":67921.35,"type":0},{"id":3,"sLen":0,"time":68089.89,"type":0},{"id":2,"sLen":0,"time":68258.43,"type":0},{"id":1,"sLen":0,"time":68426.97,"type":0},{"id":3,"sLen":0,"time":68511.2344,"type":0},{"id":2,"sLen":0,"time":68595.51,"type":0},{"id":0,"sLen":0,"time":68764.05,"type":0},{"id":3,"sLen":0,"time":68932.5859,"type":0},{"id":2,"sLen":0,"time":69101.125,"type":0},{"id":3,"sLen":0,"time":69269.6641,"type":0},{"id":1,"sLen":0,"time":69438.2,"type":0},{"id":3,"sLen":0,"time":69606.74,"type":0},{"id":0,"sLen":0,"time":69775.28,"type":0},{"id":3,"sLen":0,"time":69943.82,"type":0},{"id":0,"sLen":0,"time":75505.62,"type":0},{"id":0,"sLen":0,"time":75674.16,"type":0},{"id":1,"sLen":0,"time":75842.6953,"type":0},{"id":0,"sLen":0,"time":76011.2344,"type":0},{"id":2,"sLen":0,"time":76095.51,"type":0},{"id":0,"sLen":0,"time":76179.77,"type":0},{"id":1,"sLen":0,"time":76348.31,"type":0},{"id":3,"sLen":0,"time":76516.85,"type":0},{"id":0,"sLen":0,"time":76685.39,"type":0},{"id":1,"sLen":0,"time":76853.93,"type":0},{"id":3,"sLen":0,"time":76938.2,"type":0},{"id":1,"sLen":0,"time":77022.47,"type":0},{"id":0,"sLen":0,"time":77191.01,"type":0},{"id":1,"sLen":0,"time":77359.55,"type":0},{"id":2,"sLen":252.808990000005,"time":77528.0859,"type":0},{"id":0,"sLen":0,"time":77865.17,"type":0},{"id":0,"sLen":0,"time":78033.71,"type":0},{"id":1,"sLen":0,"time":78202.25,"type":0},{"id":0,"sLen":0,"time":78370.79,"type":0},{"id":3,"sLen":0,"time":78455.0547,"type":0},{"id":3,"sLen":0,"time":78539.33,"type":0},{"id":3,"sLen":0,"time":78707.87,"type":0},{"id":0,"sLen":0,"time":78792.13,"type":0},{"id":1,"sLen":0,"time":78876.41,"type":0},{"id":1,"sLen":0,"time":79044.9453,"type":0},{"id":3,"sLen":0,"time":79129.21,"type":0},{"id":2,"sLen":0,"time":79213.4844,"type":0},{"id":0,"sLen":0,"time":79382.02,"type":0},{"id":1,"sLen":0,"time":79466.29,"type":0},{"id":2,"sLen":0,"time":79550.56,"type":0},{"id":3,"sLen":0,"time":79634.83,"type":0},{"id":2,"sLen":0,"time":79719.1,"type":0},{"id":0,"sLen":84.2696600000054,"time":79803.37,"type":0},{"id":3,"sLen":0,"time":79971.91,"type":0},{"id":2,"sLen":0,"time":80056.18,"type":0},{"id":0,"sLen":0,"time":80224.72,"type":0},{"id":3,"sLen":0,"time":80393.26,"type":0},{"id":0,"sLen":0,"time":80561.8,"type":0},{"id":2,"sLen":0,"time":80730.3359,"type":0},{"id":1,"sLen":0,"time":86292.13,"type":0},{"id":0,"sLen":0,"time":86376.41,"type":0},{"id":3,"sLen":0,"time":86460.67,"type":0},{"id":2,"sLen":0,"time":86629.21,"type":0},{"id":1,"sLen":0,"time":86713.4844,"type":0},{"id":3,"sLen":0,"time":86797.75,"type":0},{"id":2,"sLen":0,"time":86966.29,"type":0},{"id":3,"sLen":0,"time":87134.83,"type":0},{"id":2,"sLen":0,"time":87303.37,"type":0},{"id":0,"sLen":0,"time":87471.91,"type":0},{"id":1,"sLen":0,"time":87640.45,"type":0},{"id":0,"sLen":0,"time":87724.72,"type":0},{"id":3,"sLen":0,"time":87808.99,"type":0},{"id":2,"sLen":0,"time":87977.53,"type":0},{"id":1,"sLen":0,"time":88061.8,"type":0},{"id":3,"sLen":0,"time":88146.07,"type":0},{"id":2,"sLen":0,"time":88314.61,"type":0},{"id":3,"sLen":0,"time":88483.15,"type":0},{"id":1,"sLen":0,"time":88651.69,"type":0},{"id":3,"sLen":0,"time":88820.23,"type":0},{"id":2,"sLen":0,"time":88988.7656,"type":0},{"id":3,"sLen":0,"time":89073.03,"type":0},{"id":2,"sLen":0,"time":89157.3047,"type":0},{"id":1,"sLen":0,"time":89325.84,"type":0},{"id":0,"sLen":0,"time":89410.11,"type":0},{"id":1,"sLen":0,"time":89494.38,"type":0},{"id":3,"sLen":0,"time":89662.92,"type":0},{"id":3,"sLen":0,"time":89831.46,"type":0},{"id":1,"sLen":0,"time":89915.73,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":1,"sLen":0,"time":90168.54,"type":0},{"id":0,"sLen":0,"time":90337.08,"type":0},{"id":3,"sLen":0,"time":90421.35,"type":0},{"id":1,"sLen":0,"time":90505.62,"type":0},{"id":0,"sLen":0,"time":90589.89,"type":0},{"id":1,"sLen":0,"time":90674.16,"type":0},{"id":3,"sLen":0,"time":90842.6953,"type":0},{"id":0,"sLen":0,"time":91011.2344,"type":0},{"id":3,"sLen":0,"time":91095.51,"type":0},{"id":1,"sLen":0,"time":91179.77,"type":0},{"id":0,"sLen":0,"time":91264.05,"type":0},{"id":1,"sLen":168.539321999997,"time":91348.31,"type":0},{"id":0,"sLen":0,"time":97078.65,"type":0},{"id":3,"sLen":0,"time":97162.92,"type":0},{"id":2,"sLen":0,"time":97247.19,"type":0},{"id":3,"sLen":0,"time":97415.73,"type":0},{"id":0,"sLen":0,"time":97584.27,"type":0},{"id":3,"sLen":0,"time":97668.54,"type":0},{"id":2,"sLen":0,"time":97752.81,"type":0},{"id":3,"sLen":0,"time":97921.35,"type":0},{"id":0,"sLen":0,"time":98089.89,"type":0},{"id":3,"sLen":0,"time":98174.16,"type":0},{"id":2,"sLen":0,"time":98258.43,"type":0},{"id":3,"sLen":0,"time":98426.97,"type":0},{"id":0,"sLen":0,"time":98595.51,"type":0},{"id":3,"sLen":0,"time":98679.77,"type":0},{"id":2,"sLen":0,"time":98764.05,"type":0},{"id":3,"sLen":0,"time":98932.5859,"type":0},{"id":2,"sLen":0,"time":99101.125,"type":0},{"id":1,"sLen":0,"time":99269.6641,"type":0},{"id":0,"sLen":0,"time":99438.2,"type":0},{"id":3,"sLen":0,"time":99606.74,"type":0},{"id":0,"sLen":0,"time":99775.28,"type":0},{"id":3,"sLen":0,"time":99859.55,"type":0},{"id":2,"sLen":0,"time":99943.82,"type":0},{"id":3,"sLen":0,"time":100112.359,"type":0},{"id":0,"sLen":0,"time":100280.9,"type":0},{"id":3,"sLen":0,"time":100365.172,"type":0},{"id":2,"sLen":0,"time":100449.438,"type":0},{"id":3,"sLen":0,"time":100617.977,"type":0},{"id":0,"sLen":0,"time":100786.516,"type":0},{"id":3,"sLen":0,"time":100870.789,"type":0},{"id":2,"sLen":0,"time":100955.055,"type":0},{"id":3,"sLen":0,"time":101123.594,"type":0},{"id":0,"sLen":0,"time":101292.133,"type":0},{"id":3,"sLen":0,"time":101376.406,"type":0},{"id":2,"sLen":0,"time":101460.672,"type":0},{"id":3,"sLen":0,"time":101629.211,"type":0},{"id":2,"sLen":0,"time":101797.75,"type":0},{"id":1,"sLen":0,"time":101966.289,"type":0},{"id":0,"sLen":0,"time":102134.828,"type":0},{"id":3,"sLen":0,"time":102303.367,"type":0},{"id":0,"sLen":0,"time":102471.906,"type":0},{"id":3,"sLen":0,"time":102556.18,"type":0},{"id":2,"sLen":0,"time":102640.453,"type":0},{"id":3,"sLen":0,"time":102808.992,"type":0},{"id":0,"sLen":0,"time":102977.531,"type":0},{"id":3,"sLen":0,"time":103061.8,"type":0},{"id":2,"sLen":0,"time":103146.07,"type":0},{"id":3,"sLen":0,"time":103314.609,"type":0},{"id":0,"sLen":0,"time":103483.148,"type":0},{"id":3,"sLen":0,"time":103567.414,"type":0},{"id":2,"sLen":0,"time":103651.688,"type":0},{"id":3,"sLen":0,"time":103820.227,"type":0},{"id":0,"sLen":0,"time":103988.766,"type":0},{"id":3,"sLen":0,"time":104073.031,"type":0},{"id":2,"sLen":0,"time":104157.305,"type":0},{"id":3,"sLen":0,"time":104325.844,"type":0},{"id":2,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104662.922,"type":0},{"id":0,"sLen":0,"time":104831.461,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":0,"sLen":0,"time":105168.539,"type":0},{"id":3,"sLen":0,"time":105252.813,"type":0},{"id":2,"sLen":0,"time":105337.078,"type":0},{"id":3,"sLen":0,"time":105505.617,"type":0},{"id":0,"sLen":0,"time":105674.156,"type":0},{"id":3,"sLen":0,"time":105758.43,"type":0},{"id":2,"sLen":0,"time":105842.695,"type":0},{"id":3,"sLen":0,"time":106011.234,"type":0},{"id":0,"sLen":0,"time":106179.773,"type":0},{"id":3,"sLen":0,"time":106264.047,"type":0},{"id":2,"sLen":0,"time":106348.313,"type":0},{"id":3,"sLen":0,"time":106516.852,"type":0},{"id":0,"sLen":0,"time":106685.391,"type":0},{"id":3,"sLen":0,"time":106769.664,"type":0},{"id":2,"sLen":0,"time":106853.93,"type":0},{"id":3,"sLen":0,"time":107022.469,"type":0},{"id":2,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107359.547,"type":0},{"id":0,"sLen":0,"time":107528.086,"type":0},{"id":3,"sLen":0,"time":107696.633,"type":0},{"id":3,"sLen":0,"time":107865.172,"type":0},{"id":2,"sLen":0,"time":107949.438,"type":0},{"id":3,"sLen":0,"time":108033.711,"type":0},{"id":2,"sLen":0,"time":108117.977,"type":0},{"id":0,"sLen":0,"time":108202.25,"type":0},{"id":1,"sLen":0,"time":108370.789,"type":0},{"id":3,"sLen":0,"time":108539.328,"type":0},{"id":2,"sLen":0,"time":108623.594,"type":0},{"id":3,"sLen":0,"time":108707.867,"type":0},{"id":2,"sLen":0,"time":108792.133,"type":0},{"id":0,"sLen":0,"time":108876.406,"type":0},{"id":1,"sLen":0,"time":109044.945,"type":0},{"id":3,"sLen":0,"time":109213.484,"type":0},{"id":0,"sLen":0,"time":109297.75,"type":0},{"id":2,"sLen":0,"time":109382.023,"type":0},{"id":3,"sLen":0,"time":109466.289,"type":0},{"id":0,"sLen":0,"time":109550.563,"type":0},{"id":1,"sLen":0,"time":109719.1,"type":0},{"id":3,"sLen":0,"time":109887.641,"type":0},{"id":2,"sLen":0,"time":109971.906,"type":0},{"id":3,"sLen":0,"time":110056.18,"type":0},{"id":2,"sLen":0,"time":110140.453,"type":0},{"id":0,"sLen":0,"time":110224.719,"type":0},{"id":1,"sLen":0,"time":110393.258,"type":0},{"id":2,"sLen":0,"time":113258.43,"type":0},{"id":3,"sLen":0,"time":113426.969,"type":0},{"id":1,"sLen":0,"time":113595.508,"type":0},{"id":3,"sLen":0,"time":113764.047,"type":0},{"id":0,"sLen":0,"time":113932.586,"type":0},{"id":1,"sLen":0,"time":114101.125,"type":0},{"id":0,"sLen":0,"time":114269.664,"type":0},{"id":3,"sLen":0,"time":114353.93,"type":0},{"id":1,"sLen":0,"time":114438.2,"type":0},{"id":0,"sLen":0,"time":114522.469,"type":0},{"id":2,"sLen":0,"time":114606.742,"type":0},{"id":3,"sLen":0,"time":114775.281,"type":0},{"id":1,"sLen":0,"time":114943.82,"type":0},{"id":3,"sLen":0,"time":115112.359,"type":0},{"id":0,"sLen":0,"time":115280.9,"type":0},{"id":3,"sLen":0,"time":115365.172,"type":0},{"id":1,"sLen":0,"time":115449.438,"type":0},{"id":0,"sLen":0,"time":115533.711,"type":0},{"id":3,"sLen":0,"time":115617.977,"type":0},{"id":1,"sLen":0,"time":115786.516,"type":0},{"id":2,"sLen":589.887633999984,"time":115955.055,"type":0}],"position":"dad","characters":["tankman"],"visible":true,"type":0},{"notes":[{"id":1,"sLen":0,"time":5393.2583,"type":0},{"id":2,"sLen":0,"time":5561.798,"type":0},{"id":0,"sLen":0,"time":5730.337,"type":0},{"id":0,"sLen":0,"time":5898.87646,"type":0},{"id":2,"sLen":0,"time":6067.41553,"type":0},{"id":3,"sLen":0,"time":6235.955,"type":0},{"id":1,"sLen":0,"time":6404.494,"type":0},{"id":3,"sLen":0,"time":6573.03369,"type":0},{"id":0,"sLen":0,"time":6741.573,"type":0},{"id":3,"sLen":0,"time":6910.1123,"type":0},{"id":2,"sLen":0,"time":7078.652,"type":0},{"id":3,"sLen":0,"time":7247.191,"type":0},{"id":1,"sLen":0,"time":7415.73047,"type":0},{"id":3,"sLen":0,"time":7584.26953,"type":0},{"id":0,"sLen":0,"time":7752.809,"type":0},{"id":3,"sLen":0,"time":7921.348,"type":0},{"id":1,"sLen":0,"time":8089.8877,"type":0},{"id":2,"sLen":0,"time":8258.427,"type":0},{"id":0,"sLen":0,"time":8426.966,"type":0},{"id":0,"sLen":0,"time":8595.506,"type":0},{"id":2,"sLen":0,"time":8764.045,"type":0},{"id":3,"sLen":0,"time":8932.584,"type":0},{"id":1,"sLen":0,"time":9101.124,"type":0},{"id":3,"sLen":0,"time":9269.663,"type":0},{"id":0,"sLen":0,"time":9438.202,"type":0},{"id":3,"sLen":0,"time":9606.741,"type":0},{"id":2,"sLen":0,"time":9775.281,"type":0},{"id":3,"sLen":0,"time":9943.82,"type":0},{"id":1,"sLen":0,"time":10112.3594,"type":0},{"id":3,"sLen":0,"time":10280.8984,"type":0},{"id":0,"sLen":0,"time":10449.4385,"type":0},{"id":3,"sLen":0,"time":10617.9775,"type":0},{"id":1,"sLen":252.808990000001,"time":14831.4609,"type":0},{"id":2,"sLen":252.80899,"time":15168.5391,"type":0},{"id":3,"sLen":252.80899,"time":15505.6182,"type":0},{"id":0,"sLen":252.808990000001,"time":15842.6963,"type":0},{"id":1,"sLen":252.80899,"time":16179.7754,"type":0},{"id":0,"sLen":0,"time":16516.8535,"type":0},{"id":1,"sLen":0,"time":16685.3926,"type":0},{"id":3,"sLen":252.808990000001,"time":16853.9316,"type":0},{"id":1,"sLen":0,"time":17191.0117,"type":0},{"id":0,"sLen":0,"time":17528.09,"type":0},{"id":0,"sLen":0,"time":17865.168,"type":0},{"id":0,"sLen":0,"time":18202.248,"type":0},{"id":0,"sLen":0,"time":18370.7871,"type":0},{"id":2,"sLen":0,"time":18539.3262,"type":0},{"id":3,"sLen":0,"time":18623.5957,"type":0},{"id":2,"sLen":0,"time":18707.8652,"type":0},{"id":1,"sLen":0,"time":18876.4043,"type":0},{"id":3,"sLen":0,"time":19044.9434,"type":0},{"id":2,"sLen":0,"time":19213.4824,"type":0},{"id":1,"sLen":0,"time":19382.0215,"type":0},{"id":3,"sLen":0,"time":19550.5625,"type":0},{"id":2,"sLen":0,"time":19719.1016,"type":0},{"id":1,"sLen":0,"time":19887.64,"type":0},{"id":3,"sLen":0,"time":19971.91,"type":0},{"id":2,"sLen":0,"time":20056.18,"type":0},{"id":0,"sLen":0,"time":20224.7188,"type":0},{"id":3,"sLen":0,"time":20393.2578,"type":0},{"id":2,"sLen":0,"time":20561.7969,"type":0},{"id":3,"sLen":0,"time":20730.3379,"type":0},{"id":1,"sLen":0,"time":20898.877,"type":0},{"id":3,"sLen":0,"time":21067.416,"type":0},{"id":0,"sLen":0,"time":21235.9551,"type":0},{"id":3,"sLen":0,"time":21404.4941,"type":0},{"id":1,"sLen":0,"time":21573.0332,"type":0},{"id":1,"sLen":0,"time":21910.1133,"type":0},{"id":1,"sLen":0,"time":22247.1914,"type":0},{"id":1,"sLen":0,"time":22584.27,"type":0},{"id":2,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":252.808990000001,"time":23595.5059,"type":0},{"id":0,"sLen":252.808990000012,"time":23932.584,"type":0},{"id":3,"sLen":0,"time":24269.6621,"type":0},{"id":2,"sLen":0,"time":24438.2031,"type":0},{"id":0,"sLen":0,"time":24606.7422,"type":0},{"id":2,"sLen":0,"time":24775.2813,"type":0},{"id":3,"sLen":0,"time":24943.82,"type":0},{"id":2,"sLen":0,"time":25112.36,"type":0},{"id":0,"sLen":0,"time":25280.8984,"type":0},{"id":2,"sLen":0,"time":25449.4375,"type":0},{"id":3,"sLen":0,"time":25617.9766,"type":0},{"id":2,"sLen":0,"time":25702.248,"type":0},{"id":3,"sLen":0,"time":25786.5176,"type":0},{"id":3,"sLen":0,"time":25955.0566,"type":0},{"id":2,"sLen":0,"time":26123.5957,"type":0},{"id":3,"sLen":0,"time":26292.1348,"type":0},{"id":0,"sLen":0,"time":26460.6738,"type":0},{"id":1,"sLen":0,"time":26629.2129,"type":0},{"id":0,"sLen":0,"time":26797.752,"type":0},{"id":2,"sLen":505.617980999999,"time":26966.293,"type":0},{"id":3,"sLen":505.617980999989,"time":27640.45,"type":0},{"id":1,"sLen":505.617980999999,"time":28314.6074,"type":0},{"id":2,"sLen":505.617980999999,"time":28988.7637,"type":0},{"id":3,"sLen":0,"time":29662.9219,"type":0},{"id":2,"sLen":0,"time":29831.46,"type":0},{"id":3,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30168.54,"type":0},{"id":3,"sLen":0,"time":30337.0781,"type":0},{"id":2,"sLen":0,"time":30505.6172,"type":0},{"id":3,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":30842.6973,"type":0},{"id":3,"sLen":0,"time":31011.2363,"type":0},{"id":2,"sLen":0,"time":31095.5059,"type":0},{"id":3,"sLen":0,"time":31179.7754,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":3,"sLen":0,"time":31516.8535,"type":0},{"id":2,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":3,"sLen":0,"time":32022.4727,"type":0},{"id":1,"sLen":0,"time":32191.0117,"type":0},{"id":1,"sLen":674.157300000003,"time":32359.55,"type":0},{"id":2,"sLen":0,"time":37752.81,"type":0},{"id":3,"sLen":0,"time":37921.3477,"type":0},{"id":1,"sLen":0,"time":38089.8867,"type":0},{"id":3,"sLen":0,"time":38258.4258,"type":0},{"id":0,"sLen":0,"time":38426.9648,"type":0},{"id":3,"sLen":0,"time":38595.5039,"type":0},{"id":1,"sLen":0,"time":38764.043,"type":0},{"id":3,"sLen":0,"time":38932.5859,"type":0},{"id":3,"sLen":0,"time":39016.8555,"type":0},{"id":1,"sLen":0,"time":39101.125,"type":0},{"id":3,"sLen":0,"time":39269.6641,"type":0},{"id":1,"sLen":0,"time":39438.2031,"type":0},{"id":2,"sLen":0,"time":39606.7422,"type":0},{"id":0,"sLen":0,"time":39775.28,"type":0},{"id":2,"sLen":0,"time":39943.82,"type":0},{"id":0,"sLen":0,"time":40112.36,"type":0},{"id":3,"sLen":0,"time":40280.9,"type":0},{"id":1,"sLen":0,"time":40365.168,"type":0},{"id":2,"sLen":0,"time":40449.4375,"type":0},{"id":3,"sLen":0,"time":40617.9766,"type":0},{"id":2,"sLen":0,"time":40786.5156,"type":0},{"id":3,"sLen":0,"time":40955.0547,"type":0},{"id":0,"sLen":0,"time":41123.5938,"type":0},{"id":1,"sLen":0,"time":41292.1367,"type":0},{"id":0,"sLen":0,"time":41460.6758,"type":0},{"id":2,"sLen":0,"time":41544.9453,"type":0},{"id":0,"sLen":0,"time":41629.2148,"type":0},{"id":3,"sLen":0,"time":41797.7539,"type":0},{"id":1,"sLen":0,"time":41966.293,"type":0},{"id":0,"sLen":0,"time":42134.832,"type":0},{"id":1,"sLen":0,"time":42303.37,"type":0},{"id":0,"sLen":0,"time":42471.91,"type":0},{"id":1,"sLen":0,"time":42640.45,"type":0},{"id":1,"sLen":0,"time":42808.99,"type":0},{"id":3,"sLen":0,"time":42977.5273,"type":0},{"id":3,"sLen":0,"time":43061.7969,"type":0},{"id":3,"sLen":0,"time":48539.3242,"type":0},{"id":1,"sLen":0,"time":48623.5938,"type":0},{"id":3,"sLen":0,"time":48707.8633,"type":0},{"id":0,"sLen":0,"time":48876.4063,"type":0},{"id":3,"sLen":0,"time":49044.9453,"type":0},{"id":1,"sLen":0,"time":49129.2148,"type":0},{"id":2,"sLen":0,"time":49213.4844,"type":0},{"id":0,"sLen":0,"time":49382.0234,"type":0},{"id":1,"sLen":0,"time":49550.5625,"type":0},{"id":3,"sLen":0,"time":49719.1,"type":0},{"id":3,"sLen":0,"time":49803.37,"type":0},{"id":2,"sLen":0,"time":49887.64,"type":0},{"id":3,"sLen":0,"time":50056.18,"type":0},{"id":0,"sLen":0,"time":50140.45,"type":0},{"id":1,"sLen":0,"time":50224.72,"type":0},{"id":0,"sLen":0,"time":50393.2578,"type":0},{"id":2,"sLen":0,"time":50561.7969,"type":0},{"id":3,"sLen":0,"time":50730.3359,"type":0},{"id":1,"sLen":0,"time":50898.875,"type":0},{"id":3,"sLen":0,"time":51067.4141,"type":0},{"id":2,"sLen":0,"time":51151.6836,"type":0},{"id":0,"sLen":0,"time":51235.9531,"type":0},{"id":3,"sLen":0,"time":51404.4961,"type":0},{"id":1,"sLen":0,"time":51573.0352,"type":0},{"id":3,"sLen":0,"time":51741.5742,"type":0},{"id":0,"sLen":0,"time":51910.1133,"type":0},{"id":1,"sLen":0,"time":52078.6523,"type":0},{"id":3,"sLen":0,"time":52247.19,"type":0},{"id":2,"sLen":0,"time":52331.46,"type":0},{"id":3,"sLen":0,"time":52415.73,"type":0},{"id":1,"sLen":0,"time":52584.27,"type":0},{"id":3,"sLen":0,"time":52752.81,"type":0},{"id":0,"sLen":0,"time":52921.3477,"type":0},{"id":3,"sLen":0,"time":53089.8867,"type":0},{"id":1,"sLen":0,"time":53258.4258,"type":0},{"id":3,"sLen":0,"time":53426.9648,"type":0},{"id":2,"sLen":0,"time":53595.5039,"type":0},{"id":2,"sLen":0,"time":53764.043,"type":0},{"id":1,"sLen":842.696654999985,"time":53932.5859,"type":0},{"id":2,"sLen":505.617981000003,"time":55955.0547,"type":0},{"id":1,"sLen":674.157299999999,"time":56629.2148,"type":0},{"id":0,"sLen":252.808989999998,"time":58651.6836,"type":0},{"id":2,"sLen":84.2696599999836,"time":58988.7656,"type":0},{"id":1,"sLen":0,"time":59157.3047,"type":0},{"id":1,"sLen":0,"time":59325.8438,"type":0},{"id":2,"sLen":0,"time":59494.3828,"type":0},{"id":0,"sLen":0,"time":59662.92,"type":0},{"id":0,"sLen":0,"time":59831.46,"type":0},{"id":2,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60168.54,"type":0},{"id":1,"sLen":0,"time":60337.08,"type":0},{"id":3,"sLen":0,"time":60505.6172,"type":0},{"id":0,"sLen":0,"time":60674.1563,"type":0},{"id":1,"sLen":0,"time":60842.6953,"type":0},{"id":2,"sLen":0,"time":61011.2344,"type":0},{"id":3,"sLen":0,"time":61179.7734,"type":0},{"id":1,"sLen":0,"time":61348.3164,"type":0},{"id":3,"sLen":0,"time":61516.8555,"type":0},{"id":0,"sLen":0,"time":61685.3945,"type":0},{"id":3,"sLen":0,"time":61853.9336,"type":0},{"id":1,"sLen":0,"time":62022.4727,"type":0},{"id":2,"sLen":0,"time":62191.01,"type":0},{"id":0,"sLen":0,"time":62359.55,"type":0},{"id":0,"sLen":0,"time":62528.09,"type":0},{"id":2,"sLen":0,"time":62696.63,"type":0},{"id":3,"sLen":0,"time":62865.168,"type":0},{"id":1,"sLen":0,"time":63033.707,"type":0},{"id":3,"sLen":0,"time":63202.2461,"type":0},{"id":0,"sLen":0,"time":63370.7852,"type":0},{"id":2,"sLen":0,"time":63539.3242,"type":0},{"id":3,"sLen":0,"time":63623.5938,"type":0},{"id":1,"sLen":0,"time":63707.8633,"type":0},{"id":3,"sLen":0,"time":63876.4063,"type":0},{"id":1,"sLen":0,"time":64044.9453,"type":0},{"id":3,"sLen":0,"time":64213.4844,"type":0},{"id":0,"sLen":0,"time":64382.0234,"type":0},{"id":3,"sLen":0,"time":64550.5625,"type":0},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":1,"sLen":0,"time":65224.72,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":168.539321999997,"time":65730.3359,"type":0},{"id":3,"sLen":421.348327999993,"time":66067.4141,"type":0},{"id":0,"sLen":84.2696600000054,"time":66573.03,"type":0},{"id":1,"sLen":421.348327999993,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":67247.19,"type":0},{"id":2,"sLen":589.887633999999,"time":67415.73,"type":0},{"id":1,"sLen":252.808990000005,"time":68089.89,"type":0},{"id":2,"sLen":252.808990000005,"time":68426.97,"type":0},{"id":3,"sLen":589.887633999999,"time":68764.05,"type":0},{"id":0,"sLen":589.887633999999,"time":69438.2,"type":0},{"id":1,"sLen":252.808990000005,"time":70112.36,"type":0},{"id":0,"sLen":0,"time":70449.44,"type":0},{"id":1,"sLen":0,"time":70617.98,"type":0},{"id":3,"sLen":252.808990000005,"time":70786.5156,"type":0},{"id":1,"sLen":0,"time":71123.59,"type":0},{"id":0,"sLen":0,"time":71460.67,"type":0},{"id":0,"sLen":0,"time":71797.75,"type":0},{"id":0,"sLen":0,"time":72134.83,"type":0},{"id":0,"sLen":0,"time":72303.37,"type":0},{"id":2,"sLen":0,"time":72471.91,"type":0},{"id":3,"sLen":0,"time":72556.18,"type":0},{"id":2,"sLen":0,"time":72640.45,"type":0},{"id":1,"sLen":0,"time":72808.99,"type":0},{"id":3,"sLen":0,"time":72977.53,"type":0},{"id":2,"sLen":0,"time":73146.07,"type":0},{"id":1,"sLen":0,"time":73314.61,"type":0},{"id":3,"sLen":0,"time":73483.15,"type":0},{"id":2,"sLen":0,"time":73651.69,"type":0},{"id":1,"sLen":0,"time":73820.23,"type":0},{"id":3,"sLen":0,"time":73904.49,"type":0},{"id":2,"sLen":0,"time":73988.7656,"type":0},{"id":0,"sLen":0,"time":74157.3047,"type":0},{"id":3,"sLen":0,"time":74325.84,"type":0},{"id":2,"sLen":0,"time":74494.38,"type":0},{"id":3,"sLen":0,"time":74662.92,"type":0},{"id":1,"sLen":0,"time":74831.46,"type":0},{"id":3,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75168.54,"type":0},{"id":3,"sLen":0,"time":75337.08,"type":0},{"id":1,"sLen":1264.04492,"time":75505.62,"type":0},{"id":0,"sLen":1264.04492,"time":76853.93,"type":0},{"id":3,"sLen":1264.04492,"time":78202.25,"type":0},{"id":0,"sLen":1264.04492,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":80898.875,"type":0},{"id":0,"sLen":0,"time":81067.4141,"type":0},{"id":1,"sLen":0,"time":81235.95,"type":0},{"id":0,"sLen":0,"time":81404.49,"type":0},{"id":2,"sLen":0,"time":81488.7656,"type":0},{"id":0,"sLen":0,"time":81573.03,"type":0},{"id":1,"sLen":0,"time":81741.57,"type":0},{"id":3,"sLen":0,"time":81910.11,"type":0},{"id":0,"sLen":0,"time":82078.65,"type":0},{"id":1,"sLen":0,"time":82247.19,"type":0},{"id":3,"sLen":0,"time":82331.46,"type":0},{"id":1,"sLen":0,"time":82415.73,"type":0},{"id":0,"sLen":0,"time":82584.27,"type":0},{"id":1,"sLen":0,"time":82752.81,"type":0},{"id":2,"sLen":252.808990000005,"time":82921.35,"type":0},{"id":0,"sLen":0,"time":83258.43,"type":0},{"id":0,"sLen":0,"time":83426.97,"type":0},{"id":1,"sLen":0,"time":83595.51,"type":0},{"id":0,"sLen":0,"time":83764.05,"type":0},{"id":3,"sLen":0,"time":83848.31,"type":0},{"id":3,"sLen":0,"time":83932.5859,"type":0},{"id":3,"sLen":0,"time":84101.125,"type":0},{"id":0,"sLen":0,"time":84185.39,"type":0},{"id":1,"sLen":0,"time":84269.6641,"type":0},{"id":1,"sLen":0,"time":84438.2,"type":0},{"id":3,"sLen":0,"time":84522.47,"type":0},{"id":2,"sLen":0,"time":84606.74,"type":0},{"id":0,"sLen":0,"time":84775.28,"type":0},{"id":1,"sLen":0,"time":84859.55,"type":0},{"id":2,"sLen":0,"time":84943.82,"type":0},{"id":3,"sLen":0,"time":85028.0859,"type":0},{"id":2,"sLen":0,"time":85112.36,"type":0},{"id":0,"sLen":84.2696600000054,"time":85196.63,"type":0},{"id":3,"sLen":0,"time":85365.17,"type":0},{"id":2,"sLen":0,"time":85449.44,"type":0},{"id":0,"sLen":0,"time":85617.98,"type":0},{"id":3,"sLen":0,"time":85786.5156,"type":0},{"id":0,"sLen":0,"time":85955.0547,"type":0},{"id":2,"sLen":0,"time":86123.59,"type":0},{"id":1,"sLen":0,"time":91685.39,"type":0},{"id":0,"sLen":0,"time":91769.6641,"type":0},{"id":3,"sLen":0,"time":91853.93,"type":0},{"id":2,"sLen":0,"time":92022.47,"type":0},{"id":1,"sLen":0,"time":92106.74,"type":0},{"id":3,"sLen":0,"time":92191.01,"type":0},{"id":2,"sLen":0,"time":92359.55,"type":0},{"id":3,"sLen":0,"time":92528.0859,"type":0},{"id":2,"sLen":0,"time":92696.63,"type":0},{"id":0,"sLen":0,"time":92865.17,"type":0},{"id":1,"sLen":0,"time":93033.71,"type":0},{"id":0,"sLen":0,"time":93117.98,"type":0},{"id":3,"sLen":0,"time":93202.25,"type":0},{"id":2,"sLen":0,"time":93370.79,"type":0},{"id":1,"sLen":0,"time":93455.0547,"type":0},{"id":3,"sLen":0,"time":93539.33,"type":0},{"id":2,"sLen":0,"time":93707.87,"type":0},{"id":3,"sLen":0,"time":93876.41,"type":0},{"id":1,"sLen":0,"time":94044.9453,"type":0},{"id":3,"sLen":0,"time":94213.4844,"type":0},{"id":2,"sLen":0,"time":94382.02,"type":0},{"id":3,"sLen":0,"time":94466.29,"type":0},{"id":2,"sLen":0,"time":94550.56,"type":0},{"id":1,"sLen":0,"time":94719.1,"type":0},{"id":0,"sLen":0,"time":94803.37,"type":0},{"id":1,"sLen":0,"time":94887.64,"type":0},{"id":3,"sLen":0,"time":95056.18,"type":0},{"id":3,"sLen":0,"time":95224.72,"type":0},{"id":1,"sLen":0,"time":95308.99,"type":0},{"id":3,"sLen":0,"time":95393.26,"type":0},{"id":1,"sLen":0,"time":95561.8,"type":0},{"id":0,"sLen":0,"time":95730.3359,"type":0},{"id":3,"sLen":0,"time":95814.61,"type":0},{"id":1,"sLen":0,"time":95898.875,"type":0},{"id":0,"sLen":0,"time":95983.15,"type":0},{"id":1,"sLen":0,"time":96067.4141,"type":0},{"id":3,"sLen":0,"time":96235.95,"type":0},{"id":0,"sLen":0,"time":96404.49,"type":0},{"id":3,"sLen":0,"time":96488.7656,"type":0},{"id":1,"sLen":0,"time":96573.03,"type":0},{"id":0,"sLen":0,"time":96657.3047,"type":0},{"id":1,"sLen":337.078643999994,"time":96741.57,"type":0},{"id":0,"sLen":589.887633999955,"time":98426.97,"type":0},{"id":2,"sLen":589.887633999999,"time":99101.125,"type":0},{"id":0,"sLen":589.887633999999,"time":101123.594,"type":0},{"id":2,"sLen":589.887633999984,"time":101797.75,"type":0},{"id":3,"sLen":0,"time":102471.906,"type":0},{"id":0,"sLen":0,"time":102556.18,"type":0},{"id":2,"sLen":0,"time":102640.453,"type":0},{"id":0,"sLen":0,"time":102808.992,"type":0},{"id":3,"sLen":0,"time":102977.531,"type":0},{"id":0,"sLen":0,"time":103061.8,"type":0},{"id":2,"sLen":0,"time":103146.07,"type":0},{"id":0,"sLen":0,"time":103314.609,"type":0},{"id":3,"sLen":0,"time":103483.148,"type":0},{"id":0,"sLen":0,"time":103567.414,"type":0},{"id":2,"sLen":0,"time":103651.688,"type":0},{"id":0,"sLen":0,"time":103820.227,"type":0},{"id":3,"sLen":0,"time":103988.766,"type":0},{"id":0,"sLen":0,"time":104073.031,"type":0},{"id":2,"sLen":0,"time":104157.305,"type":0},{"id":0,"sLen":0,"time":104325.844,"type":0},{"id":2,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104662.922,"type":0},{"id":3,"sLen":0,"time":104831.461,"type":0},{"id":0,"sLen":0,"time":105000,"type":0},{"id":3,"sLen":0,"time":105168.539,"type":0},{"id":0,"sLen":0,"time":105252.813,"type":0},{"id":2,"sLen":0,"time":105337.078,"type":0},{"id":0,"sLen":0,"time":105505.617,"type":0},{"id":3,"sLen":0,"time":105674.156,"type":0},{"id":0,"sLen":0,"time":105758.43,"type":0},{"id":2,"sLen":0,"time":105842.695,"type":0},{"id":0,"sLen":0,"time":106011.234,"type":0},{"id":3,"sLen":0,"time":106179.773,"type":0},{"id":0,"sLen":0,"time":106264.047,"type":0},{"id":2,"sLen":0,"time":106348.313,"type":0},{"id":0,"sLen":0,"time":106516.852,"type":0},{"id":3,"sLen":0,"time":106685.391,"type":0},{"id":0,"sLen":0,"time":106769.664,"type":0},{"id":2,"sLen":0,"time":106853.93,"type":0},{"id":0,"sLen":0,"time":107022.469,"type":0},{"id":2,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107359.547,"type":0},{"id":3,"sLen":0,"time":107528.086,"type":0},{"id":0,"sLen":0,"time":107696.633,"type":0},{"id":3,"sLen":589.887634000028,"time":107865.172,"type":0},{"id":1,"sLen":589.887634000028,"time":108539.328,"type":0},{"id":2,"sLen":589.887633999999,"time":109213.484,"type":0},{"id":1,"sLen":589.887633999999,"time":109887.641,"type":0},{"id":3,"sLen":0,"time":110561.8,"type":0},{"id":2,"sLen":0,"time":110646.07,"type":0},{"id":3,"sLen":0,"time":110730.336,"type":0},{"id":2,"sLen":0,"time":110814.609,"type":0},{"id":0,"sLen":0,"time":110898.875,"type":0},{"id":1,"sLen":0,"time":111067.414,"type":0},{"id":3,"sLen":0,"time":111235.953,"type":0},{"id":2,"sLen":0,"time":111320.227,"type":0},{"id":3,"sLen":0,"time":111404.492,"type":0},{"id":2,"sLen":0,"time":111488.766,"type":0},{"id":0,"sLen":0,"time":111573.031,"type":0},{"id":1,"sLen":0,"time":111741.57,"type":0},{"id":3,"sLen":0,"time":111910.109,"type":0},{"id":0,"sLen":0,"time":111994.383,"type":0},{"id":2,"sLen":0,"time":112078.648,"type":0},{"id":3,"sLen":0,"time":112162.922,"type":0},{"id":0,"sLen":0,"time":112247.188,"type":0},{"id":1,"sLen":0,"time":112415.727,"type":0},{"id":3,"sLen":0,"time":112584.273,"type":0},{"id":2,"sLen":0,"time":112668.539,"type":0},{"id":3,"sLen":0,"time":112752.813,"type":0},{"id":2,"sLen":0,"time":112837.078,"type":0},{"id":0,"sLen":0,"time":112921.352,"type":0},{"id":1,"sLen":0,"time":113089.891,"type":0},{"id":3,"sLen":589.887633999999,"time":113258.43,"type":0},{"id":1,"sLen":589.887633999999,"time":113932.586,"type":0},{"id":0,"sLen":589.887633999984,"time":114606.742,"type":0},{"id":1,"sLen":589.887633999999,"time":115280.9,"type":0},{"id":2,"sLen":0,"time":115955.055,"type":0},{"id":3,"sLen":0,"time":116123.594,"type":0},{"id":1,"sLen":0,"time":116292.133,"type":0},{"id":3,"sLen":0,"time":116460.672,"type":0},{"id":0,"sLen":0,"time":116629.211,"type":0},{"id":1,"sLen":0,"time":116797.75,"type":0},{"id":0,"sLen":0,"time":116966.289,"type":0},{"id":3,"sLen":0,"time":117050.563,"type":0},{"id":1,"sLen":0,"time":117134.828,"type":0},{"id":0,"sLen":0,"time":117219.1,"type":0},{"id":2,"sLen":0,"time":117303.367,"type":0},{"id":3,"sLen":0,"time":117471.906,"type":0},{"id":1,"sLen":0,"time":117640.453,"type":0},{"id":3,"sLen":0,"time":117808.992,"type":0},{"id":0,"sLen":0,"time":117977.531,"type":0},{"id":3,"sLen":0,"time":118061.8,"type":0},{"id":1,"sLen":0,"time":118146.07,"type":0},{"id":0,"sLen":0,"time":118230.336,"type":0},{"id":3,"sLen":0,"time":118314.609,"type":0},{"id":1,"sLen":0,"time":118483.148,"type":0}],"position":"boyfriend","characters":["bf-holding-gf"],"visible":true,"type":1},{"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539325842697,"type":0},{"id":3,"sLen":0,"time":252.808988764045,"type":0},{"id":3,"sLen":0,"time":421.348314606742,"type":0},{"id":0,"sLen":0,"time":589.887640449438,"type":0},{"id":3,"sLen":0,"time":758.426966292135,"type":0},{"id":3,"sLen":0,"time":842.696629213483,"type":0},{"id":0,"sLen":0,"time":1011.23595505618,"type":0},{"id":0,"sLen":0,"time":1179.77528089888,"type":0},{"id":0,"sLen":0,"time":1264.04494382022,"type":0},{"id":3,"sLen":0,"time":1348.31460674157,"type":0},{"id":0,"sLen":0,"time":1516.85393258427,"type":0},{"id":0,"sLen":0,"time":1601.12359550562,"type":0},{"id":3,"sLen":0,"time":1853.93258426966,"type":0},{"id":0,"sLen":0,"time":2022.47191011236,"type":0},{"id":3,"sLen":0,"time":2106.74157303371,"type":0},{"id":3,"sLen":0,"time":2191.01123595506,"type":0},{"id":0,"sLen":0,"time":2359.55056179775,"type":0},{"id":0,"sLen":0,"time":2696.62921348315,"type":0},{"id":3,"sLen":0,"time":2865.16853932584,"type":0},{"id":3,"sLen":0,"time":2949.43820224719,"type":0},{"id":3,"sLen":0,"time":3117.97752808989,"type":0},{"id":0,"sLen":0,"time":3286.51685393258,"type":0},{"id":3,"sLen":0,"time":3455.05617977528,"type":0},{"id":3,"sLen":0,"time":3539.32584269663,"type":0},{"id":0,"sLen":0,"time":3707.86516853933,"type":0},{"id":0,"sLen":0,"time":3876.40449438202,"type":0},{"id":0,"sLen":0,"time":3960.67415730337,"type":0},{"id":3,"sLen":0,"time":4044.94382022472,"type":0},{"id":0,"sLen":0,"time":4213.48314606742,"type":0},{"id":0,"sLen":0,"time":4297.75280898876,"type":0},{"id":3,"sLen":0,"time":4550.56179775281,"type":0},{"id":0,"sLen":0,"time":4719.10112359551,"type":0},{"id":3,"sLen":0,"time":4803.37078651685,"type":0},{"id":3,"sLen":0,"time":4887.6404494382,"type":0},{"id":0,"sLen":0,"time":5056.1797752809,"type":0},{"id":0,"sLen":0,"time":5140.44943820225,"type":0},{"id":0,"sLen":0,"time":5224.7191011236,"type":0},{"id":0,"sLen":0,"time":5393.25842696629,"type":0},{"id":3,"sLen":0,"time":5561.79775280899,"type":0},{"id":3,"sLen":0,"time":5646.06741573034,"type":0},{"id":3,"sLen":0,"time":5814.60674157303,"type":0},{"id":0,"sLen":0,"time":5983.14606741573,"type":0},{"id":3,"sLen":0,"time":6151.68539325843,"type":0},{"id":3,"sLen":0,"time":6235.95505617978,"type":0},{"id":0,"sLen":0,"time":6404.49438202247,"type":0},{"id":0,"sLen":0,"time":6573.03370786517,"type":0},{"id":0,"sLen":0,"time":6657.30337078652,"type":0},{"id":3,"sLen":0,"time":6741.57303370786,"type":0},{"id":0,"sLen":0,"time":6910.11235955056,"type":0},{"id":0,"sLen":0,"time":6994.38202247191,"type":0},{"id":3,"sLen":0,"time":7247.19101123595,"type":0},{"id":0,"sLen":0,"time":7415.73033707865,"type":0},{"id":3,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7584.26966292135,"type":0},{"id":0,"sLen":0,"time":7752.80898876405,"type":0},{"id":0,"sLen":0,"time":8089.88764044944,"type":0},{"id":3,"sLen":0,"time":8258.42696629213,"type":0},{"id":3,"sLen":0,"time":8342.69662921348,"type":0},{"id":3,"sLen":0,"time":8511.23595505618,"type":0},{"id":0,"sLen":0,"time":8679.77528089888,"type":0},{"id":3,"sLen":0,"time":8848.31460674157,"type":0},{"id":3,"sLen":0,"time":8932.58426966292,"type":0},{"id":0,"sLen":0,"time":9101.12359550562,"type":0},{"id":0,"sLen":0,"time":9269.66292134831,"type":0},{"id":0,"sLen":0,"time":9353.93258426966,"type":0},{"id":3,"sLen":0,"time":9438.20224719101,"type":0},{"id":0,"sLen":0,"time":9606.74157303371,"type":0},{"id":0,"sLen":0,"time":9691.01123595505,"type":0},{"id":3,"sLen":0,"time":9943.8202247191,"type":0},{"id":0,"sLen":0,"time":10112.3595505618,"type":0},{"id":3,"sLen":0,"time":10196.6292134831,"type":0},{"id":3,"sLen":0,"time":10280.8988764045,"type":0},{"id":0,"sLen":0,"time":10449.4382022472,"type":0},{"id":0,"sLen":0,"time":21235.9550561798,"type":0},{"id":3,"sLen":0,"time":21320.2247191011,"type":0},{"id":0,"sLen":0,"time":21404.4943820225,"type":0},{"id":0,"sLen":0,"time":21573.0337078652,"type":0},{"id":3,"sLen":0,"time":21910.1123595506,"type":0},{"id":0,"sLen":0,"time":22247.191011236,"type":0},{"id":3,"sLen":0,"time":22584.2696629213,"type":0},{"id":0,"sLen":0,"time":22921.3483146067,"type":0},{"id":0,"sLen":0,"time":23258.4269662921,"type":0},{"id":3,"sLen":0,"time":23595.5056179775,"type":0},{"id":3,"sLen":0,"time":23932.5842696629,"type":0},{"id":0,"sLen":0,"time":24269.6629213483,"type":0},{"id":3,"sLen":0,"time":24606.7415730337,"type":0},{"id":0,"sLen":0,"time":24943.8202247191,"type":0},{"id":3,"sLen":0,"time":25280.8988764045,"type":0},{"id":0,"sLen":0,"time":25617.9775280899,"type":0},{"id":0,"sLen":0,"time":25955.0561797753,"type":0},{"id":3,"sLen":0,"time":26292.1348314607,"type":0},{"id":3,"sLen":0,"time":26629.2134831461,"type":0},{"id":3,"sLen":0,"time":26713.4831460674,"type":0},{"id":3,"sLen":0,"time":26797.7528089888,"type":0},{"id":3,"sLen":0,"time":26966.2921348315,"type":0},{"id":0,"sLen":0,"time":27303.3707865169,"type":0},{"id":0,"sLen":0,"time":27640.4494382022,"type":0},{"id":3,"sLen":0,"time":27977.5280898876,"type":0},{"id":3,"sLen":0,"time":28314.606741573,"type":0},{"id":0,"sLen":0,"time":28651.6853932584,"type":0},{"id":3,"sLen":0,"time":28988.7640449438,"type":0},{"id":0,"sLen":0,"time":29325.8426966292,"type":0},{"id":0,"sLen":0,"time":29662.9213483146,"type":0},{"id":0,"sLen":0,"time":29831.4606741573,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30168.5393258427,"type":0},{"id":3,"sLen":0,"time":30337.0786516854,"type":0},{"id":3,"sLen":0,"time":30505.6179775281,"type":0},{"id":3,"sLen":0,"time":30674.1573033708,"type":0},{"id":0,"sLen":0,"time":30842.6966292135,"type":0},{"id":0,"sLen":0,"time":31011.2359550562,"type":0},{"id":3,"sLen":0,"time":31348.3146067416,"type":0},{"id":3,"sLen":0,"time":31685.393258427,"type":0},{"id":0,"sLen":0,"time":31853.9325842697,"type":0},{"id":0,"sLen":0,"time":32359.5505617978,"type":0},{"id":3,"sLen":0,"time":32696.6292134831,"type":0},{"id":0,"sLen":0,"time":33033.7078651685,"type":0},{"id":0,"sLen":0,"time":33202.2471910112,"type":0},{"id":3,"sLen":0,"time":33370.7865168539,"type":0},{"id":0,"sLen":0,"time":33707.8651685393,"type":0},{"id":3,"sLen":0,"time":34044.9438202247,"type":0},{"id":3,"sLen":0,"time":34382.0224719101,"type":0},{"id":0,"sLen":0,"time":34550.5617977528,"type":0},{"id":3,"sLen":0,"time":34719.1011235955,"type":0},{"id":0,"sLen":0,"time":35056.1797752809,"type":0},{"id":3,"sLen":0,"time":35393.2584269663,"type":0},{"id":0,"sLen":0,"time":35730.3370786517,"type":0},{"id":0,"sLen":0,"time":35898.8764044944,"type":0},{"id":3,"sLen":0,"time":36067.4157303371,"type":0},{"id":0,"sLen":0,"time":36404.4943820225,"type":0},{"id":3,"sLen":0,"time":36741.5730337079,"type":0},{"id":3,"sLen":0,"time":37078.6516853933,"type":0},{"id":0,"sLen":0,"time":37247.191011236,"type":0},{"id":3,"sLen":0,"time":37415.7303370787,"type":0},{"id":0,"sLen":0,"time":37752.808988764,"type":0},{"id":3,"sLen":0,"time":38089.8876404494,"type":0},{"id":3,"sLen":0,"time":38426.9662921348,"type":0},{"id":0,"sLen":0,"time":38595.5056179775,"type":0},{"id":3,"sLen":0,"time":38764.0449438202,"type":0},{"id":0,"sLen":0,"time":39101.1235955056,"type":0},{"id":3,"sLen":0,"time":39438.202247191,"type":0},{"id":3,"sLen":0,"time":39775.2808988764,"type":0},{"id":0,"sLen":0,"time":39943.8202247191,"type":0},{"id":3,"sLen":0,"time":40112.3595505618,"type":0},{"id":0,"sLen":0,"time":40449.4382022472,"type":0},{"id":3,"sLen":0,"time":40786.5168539326,"type":0},{"id":3,"sLen":0,"time":41123.595505618,"type":0},{"id":0,"sLen":0,"time":41292.1348314607,"type":0},{"id":3,"sLen":0,"time":41460.6741573034,"type":0},{"id":0,"sLen":0,"time":41797.7528089888,"type":0},{"id":0,"sLen":0,"time":42134.8314606742,"type":0},{"id":0,"sLen":0,"time":42471.9101123596,"type":0},{"id":0,"sLen":0,"time":42640.4494382022,"type":0},{"id":3,"sLen":0,"time":42808.9887640449,"type":0},{"id":3,"sLen":0,"time":42893.2584269663,"type":0},{"id":3,"sLen":0,"time":42977.5280898876,"type":0},{"id":0,"sLen":0,"time":43146.0674157303,"type":0},{"id":3,"sLen":0,"time":43483.1460674157,"type":0},{"id":3,"sLen":0,"time":43820.2247191011,"type":0},{"id":0,"sLen":0,"time":43988.7640449438,"type":0},{"id":3,"sLen":0,"time":44157.3033707865,"type":0},{"id":0,"sLen":0,"time":44494.3820224719,"type":0},{"id":3,"sLen":0,"time":44831.4606741573,"type":0},{"id":0,"sLen":0,"time":45168.5393258427,"type":0},{"id":0,"sLen":0,"time":45337.0786516854,"type":0},{"id":3,"sLen":0,"time":45505.6179775281,"type":0},{"id":0,"sLen":0,"time":45842.6966292135,"type":0},{"id":3,"sLen":0,"time":46179.7752808989,"type":0},{"id":3,"sLen":0,"time":46516.8539325843,"type":0},{"id":0,"sLen":0,"time":46685.393258427,"type":0},{"id":3,"sLen":0,"time":46853.9325842697,"type":0},{"id":0,"sLen":0,"time":47191.0112359551,"type":0},{"id":3,"sLen":0,"time":47528.0898876404,"type":0},{"id":3,"sLen":0,"time":47865.1685393258,"type":0},{"id":0,"sLen":0,"time":48033.7078651685,"type":0},{"id":3,"sLen":0,"time":48202.2471910112,"type":0},{"id":0,"sLen":0,"time":48539.3258426966,"type":0},{"id":3,"sLen":0,"time":48876.404494382,"type":0},{"id":3,"sLen":0,"time":49213.4831460674,"type":0},{"id":0,"sLen":0,"time":49382.0224719101,"type":0},{"id":3,"sLen":0,"time":49550.5617977528,"type":0},{"id":0,"sLen":0,"time":49887.6404494382,"type":0},{"id":3,"sLen":0,"time":50224.7191011236,"type":0},{"id":0,"sLen":0,"time":50561.797752809,"type":0},{"id":0,"sLen":0,"time":50730.3370786517,"type":0},{"id":3,"sLen":0,"time":50898.8764044944,"type":0},{"id":0,"sLen":0,"time":51235.9550561798,"type":0},{"id":3,"sLen":0,"time":51573.0337078652,"type":0},{"id":3,"sLen":0,"time":51910.1123595506,"type":0},{"id":0,"sLen":0,"time":52078.6516853933,"type":0},{"id":3,"sLen":0,"time":52247.191011236,"type":0},{"id":0,"sLen":0,"time":52584.2696629213,"type":0},{"id":0,"sLen":0,"time":52921.3483146067,"type":0},{"id":0,"sLen":0,"time":53258.4269662921,"type":0},{"id":0,"sLen":0,"time":53426.9662921348,"type":0},{"id":3,"sLen":0,"time":53595.5056179775,"type":0},{"id":3,"sLen":0,"time":53679.7752808989,"type":0},{"id":3,"sLen":0,"time":53764.0449438202,"type":0},{"id":0,"sLen":0,"time":53932.5842696629,"type":0},{"id":3,"sLen":0,"time":54101.1235955056,"type":0},{"id":3,"sLen":0,"time":54185.393258427,"type":0},{"id":3,"sLen":0,"time":54353.9325842697,"type":0},{"id":0,"sLen":0,"time":54522.4719101124,"type":0},{"id":3,"sLen":0,"time":54691.0112359551,"type":0},{"id":3,"sLen":0,"time":54775.2808988764,"type":0},{"id":0,"sLen":0,"time":54943.8202247191,"type":0},{"id":0,"sLen":0,"time":55112.3595505618,"type":0},{"id":0,"sLen":0,"time":55196.6292134831,"type":0},{"id":3,"sLen":0,"time":55280.8988764045,"type":0},{"id":0,"sLen":0,"time":55449.4382022472,"type":0},{"id":0,"sLen":0,"time":55533.7078651685,"type":0},{"id":3,"sLen":0,"time":55786.5168539326,"type":0},{"id":0,"sLen":0,"time":55955.0561797753,"type":0},{"id":3,"sLen":0,"time":56039.3258426966,"type":0},{"id":3,"sLen":0,"time":56123.595505618,"type":0},{"id":0,"sLen":0,"time":56292.1348314607,"type":0},{"id":0,"sLen":0,"time":56629.2134831461,"type":0},{"id":3,"sLen":0,"time":56797.7528089888,"type":0},{"id":3,"sLen":0,"time":56882.0224719101,"type":0},{"id":3,"sLen":0,"time":57050.5617977528,"type":0},{"id":0,"sLen":0,"time":57219.1011235955,"type":0},{"id":3,"sLen":0,"time":57387.6404494382,"type":0},{"id":3,"sLen":0,"time":57471.9101123595,"type":0},{"id":0,"sLen":0,"time":57640.4494382022,"type":0},{"id":0,"sLen":0,"time":57808.9887640449,"type":0},{"id":0,"sLen":0,"time":57893.2584269663,"type":0},{"id":3,"sLen":0,"time":57977.5280898876,"type":0},{"id":0,"sLen":0,"time":58146.0674157303,"type":0},{"id":0,"sLen":0,"time":58230.3370786517,"type":0},{"id":3,"sLen":0,"time":58483.1460674157,"type":0},{"id":0,"sLen":0,"time":58651.6853932584,"type":0},{"id":3,"sLen":0,"time":58735.9550561798,"type":0},{"id":3,"sLen":0,"time":58820.2247191011,"type":0},{"id":0,"sLen":0,"time":58988.7640449438,"type":0},{"id":0,"sLen":0,"time":59073.0337078652,"type":0},{"id":0,"sLen":0,"time":59157.3033707865,"type":0},{"id":0,"sLen":0,"time":59325.8426966292,"type":0},{"id":3,"sLen":0,"time":59494.3820224719,"type":0},{"id":3,"sLen":0,"time":59578.6516853933,"type":0},{"id":3,"sLen":0,"time":59747.191011236,"type":0},{"id":0,"sLen":0,"time":59915.7303370786,"type":0},{"id":3,"sLen":0,"time":60084.2696629213,"type":0},{"id":3,"sLen":0,"time":60168.5393258427,"type":0},{"id":0,"sLen":0,"time":60337.0786516854,"type":0},{"id":0,"sLen":0,"time":60505.6179775281,"type":0},{"id":0,"sLen":0,"time":60589.8876404494,"type":0},{"id":3,"sLen":0,"time":60674.1573033708,"type":0},{"id":0,"sLen":0,"time":60842.6966292135,"type":0},{"id":0,"sLen":0,"time":60926.9662921348,"type":0},{"id":3,"sLen":0,"time":61179.7752808989,"type":0},{"id":0,"sLen":0,"time":61348.3146067416,"type":0},{"id":3,"sLen":0,"time":61432.5842696629,"type":0},{"id":3,"sLen":0,"time":61516.8539325843,"type":0},{"id":0,"sLen":0,"time":61685.393258427,"type":0},{"id":0,"sLen":0,"time":62022.4719101124,"type":0},{"id":3,"sLen":0,"time":62191.0112359551,"type":0},{"id":3,"sLen":0,"time":62275.2808988764,"type":0},{"id":3,"sLen":0,"time":62443.8202247191,"type":0},{"id":0,"sLen":0,"time":62612.3595505618,"type":0},{"id":3,"sLen":0,"time":62780.8988764045,"type":0},{"id":3,"sLen":0,"time":62865.1685393258,"type":0},{"id":0,"sLen":0,"time":63033.7078651685,"type":0},{"id":0,"sLen":0,"time":63202.2471910112,"type":0},{"id":0,"sLen":0,"time":63286.5168539326,"type":0},{"id":3,"sLen":0,"time":63370.7865168539,"type":0},{"id":0,"sLen":0,"time":63539.3258426966,"type":0},{"id":0,"sLen":0,"time":63623.595505618,"type":0},{"id":3,"sLen":0,"time":63876.404494382,"type":0},{"id":0,"sLen":0,"time":64044.9438202247,"type":0},{"id":3,"sLen":0,"time":64129.2134831461,"type":0},{"id":3,"sLen":0,"time":64213.4831460674,"type":0},{"id":0,"sLen":0,"time":64382.0224719101,"type":0},{"id":3,"sLen":0,"time":64719.1011235955,"type":0},{"id":0,"sLen":0,"time":65056.1797752809,"type":0},{"id":0,"sLen":0,"time":65393.2584269663,"type":0},{"id":0,"sLen":0,"time":65730.3370786517,"type":0},{"id":0,"sLen":0,"time":66067.4157303371,"type":0},{"id":3,"sLen":0,"time":66404.4943820225,"type":0},{"id":3,"sLen":0,"time":66741.5730337079,"type":0},{"id":3,"sLen":0,"time":67078.6516853933,"type":0},{"id":3,"sLen":0,"time":67415.7303370787,"type":0},{"id":0,"sLen":0,"time":67752.808988764,"type":0},{"id":0,"sLen":0,"time":68089.8876404494,"type":0},{"id":0,"sLen":0,"time":68426.9662921348,"type":0},{"id":0,"sLen":0,"time":68764.0449438202,"type":0},{"id":3,"sLen":0,"time":69101.1235955056,"type":0},{"id":3,"sLen":0,"time":69438.202247191,"type":0},{"id":3,"sLen":0,"time":69775.2808988764,"type":0},{"id":3,"sLen":0,"time":69859.5505617978,"type":0},{"id":3,"sLen":0,"time":69943.8202247191,"type":0},{"id":3,"sLen":0,"time":70112.3595505618,"type":0},{"id":0,"sLen":0,"time":70449.4382022472,"type":0},{"id":0,"sLen":0,"time":70786.5168539326,"type":0},{"id":0,"sLen":0,"time":71123.595505618,"type":0},{"id":0,"sLen":0,"time":71460.6741573034,"type":0},{"id":3,"sLen":0,"time":71797.7528089888,"type":0},{"id":3,"sLen":0,"time":72134.8314606741,"type":0},{"id":3,"sLen":0,"time":72471.9101123595,"type":0},{"id":3,"sLen":0,"time":72556.1797752809,"type":0},{"id":3,"sLen":0,"time":72640.4494382023,"type":0},{"id":3,"sLen":0,"time":72808.9887640449,"type":0},{"id":3,"sLen":0,"time":72893.2584269663,"type":0},{"id":3,"sLen":0,"time":72977.5280898877,"type":0},{"id":0,"sLen":0,"time":73146.0674157303,"type":0},{"id":0,"sLen":0,"time":73230.3370786517,"type":0},{"id":0,"sLen":0,"time":73314.6067415731,"type":0},{"id":0,"sLen":0,"time":73483.1460674157,"type":0},{"id":0,"sLen":0,"time":73567.4157303371,"type":0},{"id":0,"sLen":0,"time":73651.6853932585,"type":0},{"id":0,"sLen":0,"time":73820.2247191011,"type":0},{"id":0,"sLen":0,"time":73904.4943820225,"type":0},{"id":0,"sLen":0,"time":73988.7640449438,"type":0},{"id":0,"sLen":0,"time":74157.3033707866,"type":0},{"id":0,"sLen":0,"time":74241.5730337079,"type":0},{"id":0,"sLen":0,"time":74325.8426966292,"type":0},{"id":0,"sLen":0,"time":74410.1123595506,"type":0},{"id":3,"sLen":0,"time":74494.382022472,"type":0},{"id":3,"sLen":0,"time":74578.6516853933,"type":0},{"id":3,"sLen":0,"time":74662.9213483146,"type":0},{"id":3,"sLen":0,"time":74747.191011236,"type":0},{"id":0,"sLen":0,"time":74831.4606741573,"type":0},{"id":0,"sLen":0,"time":74915.7303370787,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75084.2696629214,"type":0},{"id":3,"sLen":0,"time":75168.5393258427,"type":0},{"id":0,"sLen":0,"time":75505.6179775281,"type":0},{"id":3,"sLen":0,"time":75842.6966292135,"type":0},{"id":0,"sLen":0,"time":76179.7752808989,"type":0},{"id":0,"sLen":0,"time":76516.8539325843,"type":0},{"id":3,"sLen":0,"time":76853.9325842697,"type":0},{"id":3,"sLen":0,"time":77191.0112359551,"type":0},{"id":0,"sLen":0,"time":77528.0898876405,"type":0},{"id":3,"sLen":0,"time":77865.1685393259,"type":0},{"id":3,"sLen":0,"time":78033.7078651686,"type":0},{"id":0,"sLen":0,"time":78202.2471910113,"type":0},{"id":0,"sLen":0,"time":78539.3258426967,"type":0},{"id":3,"sLen":0,"time":78876.4044943821,"type":0},{"id":0,"sLen":0,"time":79213.4831460675,"type":0},{"id":0,"sLen":0,"time":79550.5617977529,"type":0},{"id":3,"sLen":0,"time":79887.6404494383,"type":0},{"id":0,"sLen":0,"time":80140.4494382023,"type":0},{"id":0,"sLen":0,"time":80224.7191011237,"type":0},{"id":0,"sLen":0,"time":80308.988764045,"type":0},{"id":0,"sLen":0,"time":80477.5280898877,"type":0},{"id":3,"sLen":84.269662921346,"time":80730.3370786517,"type":0},{"id":0,"sLen":0,"time":80898.8764044945,"type":0},{"id":3,"sLen":0,"time":81067.4157303371,"type":0},{"id":0,"sLen":0,"time":81235.9550561799,"type":0},{"id":3,"sLen":0,"time":81404.4943820225,"type":0},{"id":0,"sLen":0,"time":81573.0337078652,"type":0},{"id":3,"sLen":0,"time":81741.5730337079,"type":0},{"id":0,"sLen":0,"time":81910.1123595506,"type":0},{"id":3,"sLen":0,"time":82078.6516853933,"type":0},{"id":3,"sLen":0,"time":82247.191011236,"type":0},{"id":0,"sLen":0,"time":82415.7303370787,"type":0},{"id":3,"sLen":0,"time":82584.2696629214,"type":0},{"id":0,"sLen":0,"time":82752.8089887641,"type":0},{"id":3,"sLen":0,"time":82921.3483146068,"type":0},{"id":0,"sLen":0,"time":83089.8876404495,"type":0},{"id":3,"sLen":0,"time":83258.4269662922,"type":0},{"id":3,"sLen":0,"time":83426.9662921349,"type":0},{"id":0,"sLen":0,"time":83595.5056179776,"type":0},{"id":0,"sLen":0,"time":83764.0449438203,"type":0},{"id":0,"sLen":0,"time":83932.584269663,"type":0},{"id":3,"sLen":0,"time":84269.6629213484,"type":0},{"id":3,"sLen":0,"time":84606.7415730338,"type":0},{"id":3,"sLen":0,"time":84775.2808988765,"type":0},{"id":3,"sLen":0,"time":84943.8202247192,"type":0},{"id":3,"sLen":0,"time":85280.8988764046,"type":0},{"id":0,"sLen":0,"time":85617.97752809,"type":0},{"id":0,"sLen":0,"time":85702.2471910113,"type":0},{"id":3,"sLen":0,"time":85870.786516854,"type":0},{"id":0,"sLen":0,"time":86039.3258426967,"type":0},{"id":0,"sLen":0,"time":86292.1348314607,"type":0},{"id":3,"sLen":0,"time":86629.2134831461,"type":0},{"id":0,"sLen":0,"time":87134.8314606741,"type":0},{"id":3,"sLen":0,"time":87303.3707865169,"type":0},{"id":0,"sLen":0,"time":87640.4494382023,"type":0},{"id":3,"sLen":0,"time":87977.5280898877,"type":0},{"id":0,"sLen":0,"time":88483.1460674157,"type":0},{"id":3,"sLen":0,"time":88651.6853932584,"type":0},{"id":0,"sLen":0,"time":88988.7640449438,"type":0},{"id":3,"sLen":0,"time":89325.8426966292,"type":0},{"id":0,"sLen":0,"time":89831.4606741573,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.0786516854,"type":0},{"id":3,"sLen":0,"time":90674.1573033708,"type":0},{"id":0,"sLen":0,"time":91179.7752808989,"type":0},{"id":3,"sLen":0,"time":91348.3146067416,"type":0},{"id":0,"sLen":0,"time":91685.393258427,"type":0},{"id":3,"sLen":0,"time":92022.4719101124,"type":0},{"id":0,"sLen":0,"time":92528.0898876405,"type":0},{"id":3,"sLen":0,"time":92696.6292134832,"type":0},{"id":0,"sLen":0,"time":93033.7078651686,"type":0},{"id":3,"sLen":0,"time":93370.786516854,"type":0},{"id":0,"sLen":0,"time":93876.404494382,"type":0},{"id":3,"sLen":0,"time":94044.9438202248,"type":0},{"id":0,"sLen":0,"time":94382.0224719102,"type":0},{"id":3,"sLen":0,"time":94719.1011235956,"type":0},{"id":0,"sLen":0,"time":95224.7191011236,"type":0},{"id":3,"sLen":0,"time":95393.2584269663,"type":0},{"id":0,"sLen":0,"time":95730.3370786517,"type":0},{"id":0,"sLen":0,"time":96067.4157303371,"type":0},{"id":0,"sLen":0,"time":96404.4943820225,"type":0},{"id":0,"sLen":0,"time":96573.0337078652,"type":0},{"id":3,"sLen":0,"time":96741.5730337079,"type":0},{"id":3,"sLen":0,"time":96825.8426966292,"type":0},{"id":3,"sLen":0,"time":96910.1123595505,"type":0},{"id":0,"sLen":0,"time":97078.6516853933,"type":0},{"id":3,"sLen":0,"time":97415.7303370787,"type":0},{"id":3,"sLen":0,"time":97752.808988764,"type":0},{"id":0,"sLen":0,"time":97921.3483146067,"type":0},{"id":3,"sLen":0,"time":98089.8876404494,"type":0},{"id":0,"sLen":0,"time":98426.9662921348,"type":0},{"id":3,"sLen":0,"time":98764.0449438202,"type":0},{"id":0,"sLen":0,"time":99101.1235955056,"type":0},{"id":0,"sLen":0,"time":99269.6629213483,"type":0},{"id":3,"sLen":0,"time":99438.202247191,"type":0},{"id":0,"sLen":0,"time":99775.2808988764,"type":0},{"id":3,"sLen":0,"time":100112.359550562,"type":0},{"id":3,"sLen":0,"time":100449.438202247,"type":0},{"id":0,"sLen":0,"time":100617.97752809,"type":0},{"id":3,"sLen":0,"time":100786.516853933,"type":0},{"id":0,"sLen":0,"time":101123.595505618,"type":0},{"id":3,"sLen":0,"time":101460.674157303,"type":0},{"id":3,"sLen":0,"time":101797.752808989,"type":0},{"id":0,"sLen":0,"time":101966.292134831,"type":0},{"id":3,"sLen":0,"time":102134.831460674,"type":0},{"id":0,"sLen":0,"time":102471.91011236,"type":0},{"id":3,"sLen":0,"time":102808.988764045,"type":0},{"id":3,"sLen":0,"time":103146.06741573,"type":0},{"id":0,"sLen":0,"time":103314.606741573,"type":0},{"id":3,"sLen":0,"time":103483.146067416,"type":0},{"id":0,"sLen":0,"time":103820.224719101,"type":0},{"id":3,"sLen":0,"time":104157.303370787,"type":0},{"id":0,"sLen":0,"time":104494.382022472,"type":0},{"id":0,"sLen":0,"time":104662.921348315,"type":0},{"id":3,"sLen":0,"time":104831.460674157,"type":0},{"id":0,"sLen":0,"time":105168.539325843,"type":0},{"id":3,"sLen":0,"time":105505.617977528,"type":0},{"id":3,"sLen":0,"time":105842.696629213,"type":0},{"id":0,"sLen":0,"time":106011.235955056,"type":0},{"id":3,"sLen":0,"time":106179.775280899,"type":0},{"id":0,"sLen":0,"time":106516.853932584,"type":0},{"id":0,"sLen":0,"time":106853.93258427,"type":0},{"id":0,"sLen":0,"time":107191.011235955,"type":0},{"id":3,"sLen":0,"time":107528.08988764,"type":0},{"id":0,"sLen":0,"time":107865.168539326,"type":0},{"id":0,"sLen":0,"time":108202.247191011,"type":0},{"id":0,"sLen":0,"time":108539.325842697,"type":0},{"id":0,"sLen":0,"time":108876.404494382,"type":0},{"id":3,"sLen":0,"time":109213.483146068,"type":0},{"id":3,"sLen":0,"time":109550.561797753,"type":0},{"id":3,"sLen":0,"time":109887.640449438,"type":0},{"id":3,"sLen":0,"time":110224.719101124,"type":0},{"id":0,"sLen":0,"time":110561.797752809,"type":0},{"id":0,"sLen":0,"time":110730.337078652,"type":0},{"id":0,"sLen":0,"time":110898.876404495,"type":0},{"id":3,"sLen":0,"time":111235.95505618,"type":0},{"id":3,"sLen":0,"time":111573.033707865,"type":0},{"id":3,"sLen":0,"time":111910.112359551,"type":0},{"id":3,"sLen":0,"time":112247.191011236,"type":0},{"id":0,"sLen":0,"time":112584.269662922,"type":0},{"id":3,"sLen":0,"time":112921.348314607,"type":0},{"id":0,"sLen":0,"time":113258.426966292,"type":0},{"id":3,"sLen":0,"time":113932.584269663,"type":0},{"id":0,"sLen":0,"time":114269.662921349,"type":0},{"id":3,"sLen":0,"time":114438.202247191,"type":0},{"id":0,"sLen":0,"time":114606.741573034,"type":0},{"id":3,"sLen":0,"time":114943.820224719,"type":0},{"id":0,"sLen":0,"time":115280.898876405,"type":0},{"id":3,"sLen":0,"time":115617.97752809,"type":0},{"id":3,"sLen":0,"time":115786.516853933,"type":0},{"id":0,"sLen":0,"time":115955.056179776,"type":0},{"id":3,"sLen":0,"time":116123.595505618,"type":0},{"id":0,"sLen":0,"time":116292.134831461,"type":0},{"id":0,"sLen":0,"time":116629.213483146,"type":0},{"id":3,"sLen":0,"time":116966.292134832,"type":0},{"id":3,"sLen":0,"time":117303.370786517,"type":0},{"id":0,"sLen":0,"time":117640.449438203,"type":0},{"id":3,"sLen":0,"time":117977.528089888,"type":0},{"id":3,"sLen":0,"time":118061.797752809,"type":0},{"id":0,"sLen":0,"time":118314.606741573,"type":0},{"id":3,"sLen":0,"time":118398.876404495,"type":0},{"id":0,"sLen":0,"time":118651.685393258,"type":0},{"id":3,"sLen":0,"time":118820.224719101,"type":0},{"id":3,"sLen":0,"time":118904.494382022,"type":0},{"id":3,"sLen":0,"time":119073.033707865,"type":0},{"id":0,"sLen":0,"time":119241.573033708,"type":0},{"id":3,"sLen":0,"time":119410.112359551,"type":0},{"id":3,"sLen":0,"time":119494.382022472,"type":0},{"id":0,"sLen":0,"time":119662.921348315,"type":0},{"id":0,"sLen":0,"time":119831.460674157,"type":0},{"id":0,"sLen":0,"time":119915.730337079,"type":0},{"id":3,"sLen":0,"time":120000,"type":0},{"id":0,"sLen":0,"time":120168.539325843,"type":0},{"id":0,"sLen":0,"time":120252.808988764,"type":0},{"id":3,"sLen":0,"time":120505.617977528,"type":0},{"id":0,"sLen":0,"time":120674.157303371,"type":0},{"id":3,"sLen":0,"time":120758.426966292,"type":0},{"id":3,"sLen":0,"time":120842.696629213,"type":0},{"id":0,"sLen":0,"time":121011.235955056,"type":0},{"id":0,"sLen":0,"time":121095.505617978,"type":0},{"id":0,"sLen":0,"time":121179.775280899,"type":0}],"position":"girlfriend","type":2,"characters":["pico-speakers"],"visible":false}],"codenameChart":true,"stage":"tank","noteTypes":["No Anim Note"]} \ No newline at end of file +{"events":[{"params":[1],"time":5393.25842696629,"name":"Camera Movement"},{"params":[0],"time":10786.5168539326,"name":"Camera Movement"},{"params":[1],"time":14831.4606741573,"name":"Camera Movement"},{"params":[0],"time":21573.0337078652,"name":"Camera Movement"},{"params":[1],"time":24269.6629213483,"name":"Camera Movement"},{"params":[0],"time":26966.2921348315,"name":"Camera Movement"},{"params":[1],"time":28314.606741573,"name":"Camera Movement"},{"params":[0],"time":31011.2359550562,"name":"Camera Movement"},{"params":[1],"time":37752.808988764,"name":"Camera Movement"},{"params":[0],"time":43146.0674157303,"name":"Camera Movement"},{"params":[1],"time":48539.3258426966,"name":"Camera Movement"},{"params":[0],"time":53932.5842696629,"name":"Camera Movement"},{"params":[1],"time":59325.8426966292,"name":"Camera Movement"},{"params":[0],"time":62022.4719101124,"name":"Camera Movement"},{"params":[1],"time":67415.7303370787,"name":"Camera Movement"},{"params":[0],"time":75505.6179775281,"name":"Camera Movement"},{"params":[1],"time":80898.8764044945,"name":"Camera Movement"},{"params":[0],"time":86292.1348314608,"name":"Camera Movement"},{"params":[1],"time":91685.3932584271,"name":"Camera Movement"},{"params":[0],"time":97078.6516853934,"name":"Camera Movement"},{"params":[1],"time":102471.91011236,"name":"Camera Movement"},{"params":[0],"time":107865.168539326,"name":"Camera Movement"},{"params":[1],"time":113258.426966292,"name":"Camera Movement"}],"codenameChart":true,"strumLines":[{"keyCount":4,"position":"dad","notes":[{"id":1,"sLen":0,"time":0,"type":0},{"id":2,"sLen":0,"time":168.539322,"type":0},{"id":0,"sLen":0,"time":337.078644,"type":0},{"id":0,"sLen":0,"time":505.617981,"type":0},{"id":2,"sLen":0,"time":674.1573,"type":0},{"id":3,"sLen":0,"time":842.696655,"type":0},{"id":1,"sLen":0,"time":1011.23596,"type":0},{"id":3,"sLen":0,"time":1179.77527,"type":0},{"id":0,"sLen":0,"time":1348.31458,"type":0},{"id":3,"sLen":0,"time":1516.85388,"type":0},{"id":2,"sLen":0,"time":1685.39331,"type":0},{"id":3,"sLen":0,"time":1853.93262,"type":0},{"id":1,"sLen":0,"time":2022.47192,"type":0},{"id":3,"sLen":0,"time":2191.01123,"type":0},{"id":0,"sLen":0,"time":2359.55054,"type":0},{"id":3,"sLen":0,"time":2528.08984,"type":0},{"id":1,"sLen":0,"time":2696.62915,"type":0},{"id":2,"sLen":0,"time":2865.16846,"type":0},{"id":0,"sLen":0,"time":3033.70776,"type":0},{"id":0,"sLen":0,"time":3202.247,"type":0},{"id":2,"sLen":0,"time":3370.78662,"type":0},{"id":3,"sLen":0,"time":3539.326,"type":0},{"id":1,"sLen":0,"time":3707.86523,"type":0},{"id":3,"sLen":0,"time":3876.40454,"type":0},{"id":0,"sLen":0,"time":4044.94385,"type":0},{"id":3,"sLen":0,"time":4213.483,"type":0},{"id":2,"sLen":0,"time":4382.02246,"type":0},{"id":3,"sLen":0,"time":4550.562,"type":0},{"id":1,"sLen":0,"time":4719.101,"type":0},{"id":3,"sLen":0,"time":4887.64063,"type":0},{"id":0,"sLen":0,"time":5056.17969,"type":0},{"id":3,"sLen":0,"time":5224.719,"type":0},{"id":0,"sLen":0,"time":11123.5957,"type":0},{"id":1,"sLen":0,"time":11292.1348,"type":0},{"id":3,"sLen":252.808989999994,"time":11460.6738,"type":0},{"id":1,"sLen":0,"time":11797.7529,"type":0},{"id":0,"sLen":0,"time":12134.8311,"type":0},{"id":0,"sLen":0,"time":12471.91,"type":0},{"id":0,"sLen":0,"time":12808.9883,"type":0},{"id":0,"sLen":0,"time":12977.5283,"type":0},{"id":2,"sLen":0,"time":13146.0674,"type":0},{"id":3,"sLen":0,"time":13230.3369,"type":0},{"id":2,"sLen":0,"time":13314.6064,"type":0},{"id":1,"sLen":0,"time":13483.1465,"type":0},{"id":3,"sLen":0,"time":13651.6855,"type":0},{"id":2,"sLen":0,"time":13820.2246,"type":0},{"id":1,"sLen":0,"time":13988.7637,"type":0},{"id":3,"sLen":0,"time":14157.3037,"type":0},{"id":2,"sLen":0,"time":14325.8428,"type":0},{"id":1,"sLen":0,"time":14494.3818,"type":0},{"id":3,"sLen":0,"time":14578.6514,"type":0},{"id":2,"sLen":0,"time":14662.9209,"type":0},{"id":0,"sLen":0,"time":14831.4609,"type":0},{"id":3,"sLen":0,"time":15000,"type":0},{"id":2,"sLen":0,"time":15168.5391,"type":0},{"id":3,"sLen":0,"time":15337.0791,"type":0},{"id":1,"sLen":0,"time":15505.6182,"type":0},{"id":3,"sLen":0,"time":15674.1572,"type":0},{"id":0,"sLen":0,"time":15842.6963,"type":0},{"id":3,"sLen":0,"time":16011.2363,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":2,"sLen":0,"time":21741.5723,"type":0},{"id":0,"sLen":0,"time":21910.1133,"type":0},{"id":2,"sLen":0,"time":22078.6523,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":2,"sLen":0,"time":22415.73,"type":0},{"id":0,"sLen":0,"time":22584.27,"type":0},{"id":2,"sLen":0,"time":22752.8086,"type":0},{"id":3,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23005.6172,"type":0},{"id":3,"sLen":0,"time":23089.8867,"type":0},{"id":3,"sLen":0,"time":23258.4277,"type":0},{"id":2,"sLen":0,"time":23426.9668,"type":0},{"id":3,"sLen":0,"time":23595.5059,"type":0},{"id":0,"sLen":0,"time":23764.0449,"type":0},{"id":1,"sLen":0,"time":23932.584,"type":0},{"id":0,"sLen":0,"time":24101.123,"type":0},{"id":2,"sLen":0,"time":24269.6621,"type":0},{"id":3,"sLen":0,"time":26966.293,"type":0},{"id":2,"sLen":0,"time":27134.832,"type":0},{"id":3,"sLen":0,"time":27303.3711,"type":0},{"id":0,"sLen":0,"time":27471.91,"type":0},{"id":3,"sLen":0,"time":27640.45,"type":0},{"id":2,"sLen":0,"time":27808.9883,"type":0},{"id":3,"sLen":0,"time":27977.5273,"type":0},{"id":0,"sLen":0,"time":28146.0684,"type":0},{"id":3,"sLen":0,"time":28314.6074,"type":0},{"id":2,"sLen":0,"time":28398.877,"type":0},{"id":3,"sLen":0,"time":28483.1465,"type":0},{"id":0,"sLen":0,"time":28651.6855,"type":0},{"id":3,"sLen":0,"time":28820.2246,"type":0},{"id":2,"sLen":0,"time":28988.7637,"type":0},{"id":0,"sLen":0,"time":29157.3027,"type":0},{"id":3,"sLen":0,"time":29325.8418,"type":0},{"id":1,"sLen":0,"time":29494.3828,"type":0},{"id":0,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":31011.2363,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":0,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":0,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":2,"sLen":0,"time":32359.55,"type":0},{"id":3,"sLen":0,"time":32528.09,"type":0},{"id":1,"sLen":0,"time":32696.6289,"type":0},{"id":3,"sLen":0,"time":32865.168,"type":0},{"id":0,"sLen":0,"time":33033.707,"type":0},{"id":3,"sLen":0,"time":33202.2461,"type":0},{"id":1,"sLen":0,"time":33370.7852,"type":0},{"id":3,"sLen":0,"time":33539.3242,"type":0},{"id":3,"sLen":0,"time":33623.5938,"type":0},{"id":1,"sLen":0,"time":33707.8633,"type":0},{"id":3,"sLen":0,"time":33876.4063,"type":0},{"id":1,"sLen":0,"time":34044.9453,"type":0},{"id":2,"sLen":0,"time":34213.4844,"type":0},{"id":0,"sLen":0,"time":34382.0234,"type":0},{"id":2,"sLen":0,"time":34550.5625,"type":0},{"id":0,"sLen":0,"time":34719.1,"type":0},{"id":3,"sLen":0,"time":34887.64,"type":0},{"id":1,"sLen":0,"time":34971.91,"type":0},{"id":2,"sLen":0,"time":35056.18,"type":0},{"id":3,"sLen":0,"time":35224.72,"type":0},{"id":2,"sLen":0,"time":35393.2578,"type":0},{"id":3,"sLen":0,"time":35561.7969,"type":0},{"id":0,"sLen":0,"time":35730.3359,"type":0},{"id":1,"sLen":0,"time":35898.875,"type":0},{"id":0,"sLen":0,"time":36067.4141,"type":0},{"id":2,"sLen":0,"time":36151.6836,"type":0},{"id":0,"sLen":0,"time":36235.9531,"type":0},{"id":3,"sLen":0,"time":36404.4961,"type":0},{"id":1,"sLen":0,"time":36573.0352,"type":0},{"id":0,"sLen":0,"time":36741.5742,"type":0},{"id":1,"sLen":0,"time":36910.1133,"type":0},{"id":0,"sLen":0,"time":37078.6523,"type":0},{"id":1,"sLen":0,"time":37247.19,"type":0},{"id":1,"sLen":0,"time":37415.73,"type":0},{"id":3,"sLen":0,"time":37584.27,"type":0},{"id":3,"sLen":0,"time":37668.54,"type":0},{"id":3,"sLen":0,"time":43146.0664,"type":0},{"id":1,"sLen":0,"time":43230.3359,"type":0},{"id":3,"sLen":0,"time":43314.6055,"type":0},{"id":0,"sLen":0,"time":43483.1445,"type":0},{"id":3,"sLen":0,"time":43651.6836,"type":0},{"id":1,"sLen":0,"time":43735.9531,"type":0},{"id":2,"sLen":0,"time":43820.2266,"type":0},{"id":0,"sLen":0,"time":43988.7656,"type":0},{"id":1,"sLen":0,"time":44157.3047,"type":0},{"id":3,"sLen":0,"time":44325.8438,"type":0},{"id":3,"sLen":0,"time":44410.1133,"type":0},{"id":2,"sLen":0,"time":44494.3828,"type":0},{"id":3,"sLen":0,"time":44662.92,"type":0},{"id":0,"sLen":0,"time":44747.19,"type":0},{"id":1,"sLen":0,"time":44831.46,"type":0},{"id":0,"sLen":0,"time":45000,"type":0},{"id":2,"sLen":0,"time":45168.54,"type":0},{"id":3,"sLen":0,"time":45337.08,"type":0},{"id":1,"sLen":0,"time":45505.6172,"type":0},{"id":3,"sLen":0,"time":45674.1563,"type":0},{"id":2,"sLen":0,"time":45758.4258,"type":0},{"id":0,"sLen":0,"time":45842.6953,"type":0},{"id":3,"sLen":0,"time":46011.2344,"type":0},{"id":1,"sLen":0,"time":46179.7734,"type":0},{"id":3,"sLen":0,"time":46348.3164,"type":0},{"id":0,"sLen":0,"time":46516.8555,"type":0},{"id":1,"sLen":0,"time":46685.3945,"type":0},{"id":3,"sLen":0,"time":46853.9336,"type":0},{"id":2,"sLen":0,"time":46938.2031,"type":0},{"id":3,"sLen":0,"time":47022.4727,"type":0},{"id":1,"sLen":0,"time":47191.01,"type":0},{"id":3,"sLen":0,"time":47359.55,"type":0},{"id":0,"sLen":0,"time":47528.09,"type":0},{"id":3,"sLen":0,"time":47696.63,"type":0},{"id":1,"sLen":0,"time":47865.168,"type":0},{"id":3,"sLen":0,"time":48033.707,"type":0},{"id":2,"sLen":0,"time":48202.2461,"type":0},{"id":2,"sLen":0,"time":48370.7852,"type":0},{"id":1,"sLen":0,"time":53932.5859,"type":0},{"id":2,"sLen":0,"time":54101.125,"type":0},{"id":0,"sLen":0,"time":54269.6641,"type":0},{"id":0,"sLen":0,"time":54438.2031,"type":0},{"id":2,"sLen":0,"time":54606.7422,"type":0},{"id":3,"sLen":0,"time":54775.28,"type":0},{"id":1,"sLen":0,"time":54943.82,"type":0},{"id":3,"sLen":0,"time":55112.36,"type":0},{"id":0,"sLen":0,"time":55280.9,"type":0},{"id":1,"sLen":0,"time":55449.4375,"type":0},{"id":2,"sLen":0,"time":55617.9766,"type":0},{"id":3,"sLen":0,"time":55786.5156,"type":0},{"id":1,"sLen":0,"time":55955.0547,"type":0},{"id":3,"sLen":0,"time":56123.5938,"type":0},{"id":0,"sLen":0,"time":56292.1367,"type":0},{"id":3,"sLen":0,"time":56460.6758,"type":0},{"id":1,"sLen":0,"time":56629.2148,"type":0},{"id":2,"sLen":0,"time":56797.7539,"type":0},{"id":0,"sLen":0,"time":56966.293,"type":0},{"id":0,"sLen":0,"time":57134.832,"type":0},{"id":2,"sLen":0,"time":57303.37,"type":0},{"id":3,"sLen":0,"time":57471.91,"type":0},{"id":1,"sLen":0,"time":57640.45,"type":0},{"id":3,"sLen":0,"time":57808.99,"type":0},{"id":0,"sLen":0,"time":57977.5273,"type":0},{"id":2,"sLen":0,"time":58146.0664,"type":0},{"id":3,"sLen":0,"time":58230.3359,"type":0},{"id":1,"sLen":0,"time":58314.6055,"type":0},{"id":3,"sLen":0,"time":58483.1445,"type":0},{"id":1,"sLen":0,"time":58651.6836,"type":0},{"id":3,"sLen":0,"time":58820.2266,"type":0},{"id":0,"sLen":0,"time":58988.7656,"type":0},{"id":3,"sLen":0,"time":59157.3047,"type":0},{"id":0,"sLen":168.539321999997,"time":59325.8438,"type":0},{"id":0,"sLen":168.539321999997,"time":60000,"type":0},{"id":0,"sLen":168.539322000004,"time":60674.1563,"type":0},{"id":0,"sLen":168.539321999997,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":62022.4719101124,"type":1},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":1,"sLen":0,"time":65224.72,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":0,"time":65730.3359,"type":0},{"id":0,"sLen":0,"time":66067.4141,"type":0},{"id":0,"sLen":0,"time":66404.49,"type":0},{"id":0,"sLen":0,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":66910.11,"type":0},{"id":2,"sLen":0,"time":67078.65,"type":0},{"id":3,"sLen":0,"time":67162.92,"type":0},{"id":2,"sLen":0,"time":67247.19,"type":0},{"id":1,"sLen":0,"time":67415.73,"type":0},{"id":3,"sLen":0,"time":67584.27,"type":0},{"id":2,"sLen":0,"time":67752.81,"type":0},{"id":1,"sLen":0,"time":67921.35,"type":0},{"id":3,"sLen":0,"time":68089.89,"type":0},{"id":2,"sLen":0,"time":68258.43,"type":0},{"id":1,"sLen":0,"time":68426.97,"type":0},{"id":3,"sLen":0,"time":68511.2344,"type":0},{"id":2,"sLen":0,"time":68595.51,"type":0},{"id":0,"sLen":0,"time":68764.05,"type":0},{"id":3,"sLen":0,"time":68932.5859,"type":0},{"id":2,"sLen":0,"time":69101.125,"type":0},{"id":3,"sLen":0,"time":69269.6641,"type":0},{"id":1,"sLen":0,"time":69438.2,"type":0},{"id":3,"sLen":0,"time":69606.74,"type":0},{"id":0,"sLen":0,"time":69775.28,"type":0},{"id":3,"sLen":0,"time":69943.82,"type":0},{"id":0,"sLen":0,"time":75505.62,"type":0},{"id":0,"sLen":0,"time":75674.16,"type":0},{"id":1,"sLen":0,"time":75842.6953,"type":0},{"id":0,"sLen":0,"time":76011.2344,"type":0},{"id":2,"sLen":0,"time":76095.51,"type":0},{"id":0,"sLen":0,"time":76179.77,"type":0},{"id":1,"sLen":0,"time":76348.31,"type":0},{"id":3,"sLen":0,"time":76516.85,"type":0},{"id":0,"sLen":0,"time":76685.39,"type":0},{"id":1,"sLen":0,"time":76853.93,"type":0},{"id":3,"sLen":0,"time":76938.2,"type":0},{"id":1,"sLen":0,"time":77022.47,"type":0},{"id":0,"sLen":0,"time":77191.01,"type":0},{"id":1,"sLen":0,"time":77359.55,"type":0},{"id":2,"sLen":252.808990000005,"time":77528.0859,"type":0},{"id":0,"sLen":0,"time":77865.17,"type":0},{"id":0,"sLen":0,"time":78033.71,"type":0},{"id":1,"sLen":0,"time":78202.25,"type":0},{"id":0,"sLen":0,"time":78370.79,"type":0},{"id":3,"sLen":0,"time":78455.0547,"type":0},{"id":3,"sLen":0,"time":78539.33,"type":0},{"id":3,"sLen":0,"time":78707.87,"type":0},{"id":0,"sLen":0,"time":78792.13,"type":0},{"id":1,"sLen":0,"time":78876.41,"type":0},{"id":1,"sLen":0,"time":79044.9453,"type":0},{"id":3,"sLen":0,"time":79129.21,"type":0},{"id":2,"sLen":0,"time":79213.4844,"type":0},{"id":0,"sLen":0,"time":79382.02,"type":0},{"id":1,"sLen":0,"time":79466.29,"type":0},{"id":2,"sLen":0,"time":79550.56,"type":0},{"id":3,"sLen":0,"time":79634.83,"type":0},{"id":2,"sLen":0,"time":79719.1,"type":0},{"id":0,"sLen":84.2696600000054,"time":79803.37,"type":0},{"id":3,"sLen":0,"time":79971.91,"type":0},{"id":2,"sLen":0,"time":80056.18,"type":0},{"id":0,"sLen":0,"time":80224.72,"type":0},{"id":3,"sLen":0,"time":80393.26,"type":0},{"id":0,"sLen":0,"time":80561.8,"type":0},{"id":2,"sLen":0,"time":80730.3359,"type":0},{"id":1,"sLen":0,"time":86292.13,"type":0},{"id":0,"sLen":0,"time":86376.41,"type":0},{"id":3,"sLen":0,"time":86460.67,"type":0},{"id":2,"sLen":0,"time":86629.21,"type":0},{"id":1,"sLen":0,"time":86713.4844,"type":0},{"id":3,"sLen":0,"time":86797.75,"type":0},{"id":2,"sLen":0,"time":86966.29,"type":0},{"id":3,"sLen":0,"time":87134.83,"type":0},{"id":2,"sLen":0,"time":87303.37,"type":0},{"id":0,"sLen":0,"time":87471.91,"type":0},{"id":1,"sLen":0,"time":87640.45,"type":0},{"id":0,"sLen":0,"time":87724.72,"type":0},{"id":3,"sLen":0,"time":87808.99,"type":0},{"id":2,"sLen":0,"time":87977.53,"type":0},{"id":1,"sLen":0,"time":88061.8,"type":0},{"id":3,"sLen":0,"time":88146.07,"type":0},{"id":2,"sLen":0,"time":88314.61,"type":0},{"id":3,"sLen":0,"time":88483.15,"type":0},{"id":1,"sLen":0,"time":88651.69,"type":0},{"id":3,"sLen":0,"time":88820.23,"type":0},{"id":2,"sLen":0,"time":88988.7656,"type":0},{"id":3,"sLen":0,"time":89073.03,"type":0},{"id":2,"sLen":0,"time":89157.3047,"type":0},{"id":1,"sLen":0,"time":89325.84,"type":0},{"id":0,"sLen":0,"time":89410.11,"type":0},{"id":1,"sLen":0,"time":89494.38,"type":0},{"id":3,"sLen":0,"time":89662.92,"type":0},{"id":3,"sLen":0,"time":89831.46,"type":0},{"id":1,"sLen":0,"time":89915.73,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":1,"sLen":0,"time":90168.54,"type":0},{"id":0,"sLen":0,"time":90337.08,"type":0},{"id":3,"sLen":0,"time":90421.35,"type":0},{"id":1,"sLen":0,"time":90505.62,"type":0},{"id":0,"sLen":0,"time":90589.89,"type":0},{"id":1,"sLen":0,"time":90674.16,"type":0},{"id":3,"sLen":0,"time":90842.6953,"type":0},{"id":0,"sLen":0,"time":91011.2344,"type":0},{"id":3,"sLen":0,"time":91095.51,"type":0},{"id":1,"sLen":0,"time":91179.77,"type":0},{"id":0,"sLen":0,"time":91264.05,"type":0},{"id":1,"sLen":168.539321999997,"time":91348.31,"type":0},{"id":0,"sLen":0,"time":97078.65,"type":0},{"id":3,"sLen":0,"time":97162.92,"type":0},{"id":2,"sLen":0,"time":97247.19,"type":0},{"id":3,"sLen":0,"time":97415.73,"type":0},{"id":0,"sLen":0,"time":97584.27,"type":0},{"id":3,"sLen":0,"time":97668.54,"type":0},{"id":2,"sLen":0,"time":97752.81,"type":0},{"id":3,"sLen":0,"time":97921.35,"type":0},{"id":0,"sLen":0,"time":98089.89,"type":0},{"id":3,"sLen":0,"time":98174.16,"type":0},{"id":2,"sLen":0,"time":98258.43,"type":0},{"id":3,"sLen":0,"time":98426.97,"type":0},{"id":0,"sLen":0,"time":98595.51,"type":0},{"id":3,"sLen":0,"time":98679.77,"type":0},{"id":2,"sLen":0,"time":98764.05,"type":0},{"id":3,"sLen":0,"time":98932.5859,"type":0},{"id":2,"sLen":0,"time":99101.125,"type":0},{"id":1,"sLen":0,"time":99269.6641,"type":0},{"id":0,"sLen":0,"time":99438.2,"type":0},{"id":3,"sLen":0,"time":99606.74,"type":0},{"id":0,"sLen":0,"time":99775.28,"type":0},{"id":3,"sLen":0,"time":99859.55,"type":0},{"id":2,"sLen":0,"time":99943.82,"type":0},{"id":3,"sLen":0,"time":100112.359,"type":0},{"id":0,"sLen":0,"time":100280.9,"type":0},{"id":3,"sLen":0,"time":100365.172,"type":0},{"id":2,"sLen":0,"time":100449.438,"type":0},{"id":3,"sLen":0,"time":100617.977,"type":0},{"id":0,"sLen":0,"time":100786.516,"type":0},{"id":3,"sLen":0,"time":100870.789,"type":0},{"id":2,"sLen":0,"time":100955.055,"type":0},{"id":3,"sLen":0,"time":101123.594,"type":0},{"id":0,"sLen":0,"time":101292.133,"type":0},{"id":3,"sLen":0,"time":101376.406,"type":0},{"id":2,"sLen":0,"time":101460.672,"type":0},{"id":3,"sLen":0,"time":101629.211,"type":0},{"id":2,"sLen":0,"time":101797.75,"type":0},{"id":1,"sLen":0,"time":101966.289,"type":0},{"id":0,"sLen":0,"time":102134.828,"type":0},{"id":3,"sLen":0,"time":102303.367,"type":0},{"id":0,"sLen":0,"time":102471.906,"type":0},{"id":3,"sLen":0,"time":102556.18,"type":0},{"id":2,"sLen":0,"time":102640.453,"type":0},{"id":3,"sLen":0,"time":102808.992,"type":0},{"id":0,"sLen":0,"time":102977.531,"type":0},{"id":3,"sLen":0,"time":103061.8,"type":0},{"id":2,"sLen":0,"time":103146.07,"type":0},{"id":3,"sLen":0,"time":103314.609,"type":0},{"id":0,"sLen":0,"time":103483.148,"type":0},{"id":3,"sLen":0,"time":103567.414,"type":0},{"id":2,"sLen":0,"time":103651.688,"type":0},{"id":3,"sLen":0,"time":103820.227,"type":0},{"id":0,"sLen":0,"time":103988.766,"type":0},{"id":3,"sLen":0,"time":104073.031,"type":0},{"id":2,"sLen":0,"time":104157.305,"type":0},{"id":3,"sLen":0,"time":104325.844,"type":0},{"id":2,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104662.922,"type":0},{"id":0,"sLen":0,"time":104831.461,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":0,"sLen":0,"time":105168.539,"type":0},{"id":3,"sLen":0,"time":105252.813,"type":0},{"id":2,"sLen":0,"time":105337.078,"type":0},{"id":3,"sLen":0,"time":105505.617,"type":0},{"id":0,"sLen":0,"time":105674.156,"type":0},{"id":3,"sLen":0,"time":105758.43,"type":0},{"id":2,"sLen":0,"time":105842.695,"type":0},{"id":3,"sLen":0,"time":106011.234,"type":0},{"id":0,"sLen":0,"time":106179.773,"type":0},{"id":3,"sLen":0,"time":106264.047,"type":0},{"id":2,"sLen":0,"time":106348.313,"type":0},{"id":3,"sLen":0,"time":106516.852,"type":0},{"id":0,"sLen":0,"time":106685.391,"type":0},{"id":3,"sLen":0,"time":106769.664,"type":0},{"id":2,"sLen":0,"time":106853.93,"type":0},{"id":3,"sLen":0,"time":107022.469,"type":0},{"id":2,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107359.547,"type":0},{"id":0,"sLen":0,"time":107528.086,"type":0},{"id":3,"sLen":0,"time":107696.633,"type":0},{"id":3,"sLen":0,"time":107865.172,"type":0},{"id":2,"sLen":0,"time":107949.438,"type":0},{"id":3,"sLen":0,"time":108033.711,"type":0},{"id":2,"sLen":0,"time":108117.977,"type":0},{"id":0,"sLen":0,"time":108202.25,"type":0},{"id":1,"sLen":0,"time":108370.789,"type":0},{"id":3,"sLen":0,"time":108539.328,"type":0},{"id":2,"sLen":0,"time":108623.594,"type":0},{"id":3,"sLen":0,"time":108707.867,"type":0},{"id":2,"sLen":0,"time":108792.133,"type":0},{"id":0,"sLen":0,"time":108876.406,"type":0},{"id":1,"sLen":0,"time":109044.945,"type":0},{"id":3,"sLen":0,"time":109213.484,"type":0},{"id":0,"sLen":0,"time":109297.75,"type":0},{"id":2,"sLen":0,"time":109382.023,"type":0},{"id":3,"sLen":0,"time":109466.289,"type":0},{"id":0,"sLen":0,"time":109550.563,"type":0},{"id":1,"sLen":0,"time":109719.1,"type":0},{"id":3,"sLen":0,"time":109887.641,"type":0},{"id":2,"sLen":0,"time":109971.906,"type":0},{"id":3,"sLen":0,"time":110056.18,"type":0},{"id":2,"sLen":0,"time":110140.453,"type":0},{"id":0,"sLen":0,"time":110224.719,"type":0},{"id":1,"sLen":0,"time":110393.258,"type":0},{"id":2,"sLen":0,"time":113258.43,"type":0},{"id":3,"sLen":0,"time":113426.969,"type":0},{"id":1,"sLen":0,"time":113595.508,"type":0},{"id":3,"sLen":0,"time":113764.047,"type":0},{"id":0,"sLen":0,"time":113932.586,"type":0},{"id":1,"sLen":0,"time":114101.125,"type":0},{"id":0,"sLen":0,"time":114269.664,"type":0},{"id":3,"sLen":0,"time":114353.93,"type":0},{"id":1,"sLen":0,"time":114438.2,"type":0},{"id":0,"sLen":0,"time":114522.469,"type":0},{"id":2,"sLen":0,"time":114606.742,"type":0},{"id":3,"sLen":0,"time":114775.281,"type":0},{"id":1,"sLen":0,"time":114943.82,"type":0},{"id":3,"sLen":0,"time":115112.359,"type":0},{"id":0,"sLen":0,"time":115280.9,"type":0},{"id":3,"sLen":0,"time":115365.172,"type":0},{"id":1,"sLen":0,"time":115449.438,"type":0},{"id":0,"sLen":0,"time":115533.711,"type":0},{"id":3,"sLen":0,"time":115617.977,"type":0},{"id":1,"sLen":0,"time":115786.516,"type":0},{"id":2,"sLen":589.887633999984,"time":115955.055,"type":0}],"type":0,"characters":["tankman"],"visible":true},{"keyCount":4,"position":"boyfriend","notes":[{"id":1,"sLen":0,"time":5393.2583,"type":0},{"id":2,"sLen":0,"time":5561.798,"type":0},{"id":0,"sLen":0,"time":5730.337,"type":0},{"id":0,"sLen":0,"time":5898.87646,"type":0},{"id":2,"sLen":0,"time":6067.41553,"type":0},{"id":3,"sLen":0,"time":6235.955,"type":0},{"id":1,"sLen":0,"time":6404.494,"type":0},{"id":3,"sLen":0,"time":6573.03369,"type":0},{"id":0,"sLen":0,"time":6741.573,"type":0},{"id":3,"sLen":0,"time":6910.1123,"type":0},{"id":2,"sLen":0,"time":7078.652,"type":0},{"id":3,"sLen":0,"time":7247.191,"type":0},{"id":1,"sLen":0,"time":7415.73047,"type":0},{"id":3,"sLen":0,"time":7584.26953,"type":0},{"id":0,"sLen":0,"time":7752.809,"type":0},{"id":3,"sLen":0,"time":7921.348,"type":0},{"id":1,"sLen":0,"time":8089.8877,"type":0},{"id":2,"sLen":0,"time":8258.427,"type":0},{"id":0,"sLen":0,"time":8426.966,"type":0},{"id":0,"sLen":0,"time":8595.506,"type":0},{"id":2,"sLen":0,"time":8764.045,"type":0},{"id":3,"sLen":0,"time":8932.584,"type":0},{"id":1,"sLen":0,"time":9101.124,"type":0},{"id":3,"sLen":0,"time":9269.663,"type":0},{"id":0,"sLen":0,"time":9438.202,"type":0},{"id":3,"sLen":0,"time":9606.741,"type":0},{"id":2,"sLen":0,"time":9775.281,"type":0},{"id":3,"sLen":0,"time":9943.82,"type":0},{"id":1,"sLen":0,"time":10112.3594,"type":0},{"id":3,"sLen":0,"time":10280.8984,"type":0},{"id":0,"sLen":0,"time":10449.4385,"type":0},{"id":3,"sLen":0,"time":10617.9775,"type":0},{"id":1,"sLen":252.808990000001,"time":14831.4609,"type":0},{"id":2,"sLen":252.80899,"time":15168.5391,"type":0},{"id":3,"sLen":252.80899,"time":15505.6182,"type":0},{"id":0,"sLen":252.808990000001,"time":15842.6963,"type":0},{"id":1,"sLen":252.80899,"time":16179.7754,"type":0},{"id":0,"sLen":0,"time":16516.8535,"type":0},{"id":1,"sLen":0,"time":16685.3926,"type":0},{"id":3,"sLen":252.808990000001,"time":16853.9316,"type":0},{"id":1,"sLen":0,"time":17191.0117,"type":0},{"id":0,"sLen":0,"time":17528.09,"type":0},{"id":0,"sLen":0,"time":17865.168,"type":0},{"id":0,"sLen":0,"time":18202.248,"type":0},{"id":0,"sLen":0,"time":18370.7871,"type":0},{"id":2,"sLen":0,"time":18539.3262,"type":0},{"id":3,"sLen":0,"time":18623.5957,"type":0},{"id":2,"sLen":0,"time":18707.8652,"type":0},{"id":1,"sLen":0,"time":18876.4043,"type":0},{"id":3,"sLen":0,"time":19044.9434,"type":0},{"id":2,"sLen":0,"time":19213.4824,"type":0},{"id":1,"sLen":0,"time":19382.0215,"type":0},{"id":3,"sLen":0,"time":19550.5625,"type":0},{"id":2,"sLen":0,"time":19719.1016,"type":0},{"id":1,"sLen":0,"time":19887.64,"type":0},{"id":3,"sLen":0,"time":19971.91,"type":0},{"id":2,"sLen":0,"time":20056.18,"type":0},{"id":0,"sLen":0,"time":20224.7188,"type":0},{"id":3,"sLen":0,"time":20393.2578,"type":0},{"id":2,"sLen":0,"time":20561.7969,"type":0},{"id":3,"sLen":0,"time":20730.3379,"type":0},{"id":1,"sLen":0,"time":20898.877,"type":0},{"id":3,"sLen":0,"time":21067.416,"type":0},{"id":0,"sLen":0,"time":21235.9551,"type":0},{"id":3,"sLen":0,"time":21404.4941,"type":0},{"id":1,"sLen":0,"time":21573.0332,"type":0},{"id":1,"sLen":0,"time":21910.1133,"type":0},{"id":1,"sLen":0,"time":22247.1914,"type":0},{"id":1,"sLen":0,"time":22584.27,"type":0},{"id":2,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":252.808990000001,"time":23595.5059,"type":0},{"id":0,"sLen":252.808990000016,"time":23932.584,"type":0},{"id":3,"sLen":0,"time":24269.6621,"type":0},{"id":2,"sLen":0,"time":24438.2031,"type":0},{"id":0,"sLen":0,"time":24606.7422,"type":0},{"id":2,"sLen":0,"time":24775.2813,"type":0},{"id":3,"sLen":0,"time":24943.82,"type":0},{"id":2,"sLen":0,"time":25112.36,"type":0},{"id":0,"sLen":0,"time":25280.8984,"type":0},{"id":2,"sLen":0,"time":25449.4375,"type":0},{"id":3,"sLen":0,"time":25617.9766,"type":0},{"id":2,"sLen":0,"time":25702.248,"type":0},{"id":3,"sLen":0,"time":25786.5176,"type":0},{"id":3,"sLen":0,"time":25955.0566,"type":0},{"id":2,"sLen":0,"time":26123.5957,"type":0},{"id":3,"sLen":0,"time":26292.1348,"type":0},{"id":0,"sLen":0,"time":26460.6738,"type":0},{"id":1,"sLen":0,"time":26629.2129,"type":0},{"id":0,"sLen":0,"time":26797.752,"type":0},{"id":2,"sLen":505.617980999999,"time":26966.293,"type":0},{"id":3,"sLen":505.617980999985,"time":27640.45,"type":0},{"id":1,"sLen":505.617980999999,"time":28314.6074,"type":0},{"id":2,"sLen":505.617980999999,"time":28988.7637,"type":0},{"id":3,"sLen":0,"time":29662.9219,"type":0},{"id":2,"sLen":0,"time":29831.46,"type":0},{"id":3,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30168.54,"type":0},{"id":3,"sLen":0,"time":30337.0781,"type":0},{"id":2,"sLen":0,"time":30505.6172,"type":0},{"id":3,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":30842.6973,"type":0},{"id":3,"sLen":0,"time":31011.2363,"type":0},{"id":2,"sLen":0,"time":31095.5059,"type":0},{"id":3,"sLen":0,"time":31179.7754,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":3,"sLen":0,"time":31516.8535,"type":0},{"id":2,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":3,"sLen":0,"time":32022.4727,"type":0},{"id":1,"sLen":0,"time":32191.0117,"type":0},{"id":1,"sLen":674.157300000003,"time":32359.55,"type":0},{"id":2,"sLen":0,"time":37752.81,"type":0},{"id":3,"sLen":0,"time":37921.3477,"type":0},{"id":1,"sLen":0,"time":38089.8867,"type":0},{"id":3,"sLen":0,"time":38258.4258,"type":0},{"id":0,"sLen":0,"time":38426.9648,"type":0},{"id":3,"sLen":0,"time":38595.5039,"type":0},{"id":1,"sLen":0,"time":38764.043,"type":0},{"id":3,"sLen":0,"time":38932.5859,"type":0},{"id":3,"sLen":0,"time":39016.8555,"type":0},{"id":1,"sLen":0,"time":39101.125,"type":0},{"id":3,"sLen":0,"time":39269.6641,"type":0},{"id":1,"sLen":0,"time":39438.2031,"type":0},{"id":2,"sLen":0,"time":39606.7422,"type":0},{"id":0,"sLen":0,"time":39775.28,"type":0},{"id":2,"sLen":0,"time":39943.82,"type":0},{"id":0,"sLen":0,"time":40112.36,"type":0},{"id":3,"sLen":0,"time":40280.9,"type":0},{"id":1,"sLen":0,"time":40365.168,"type":0},{"id":2,"sLen":0,"time":40449.4375,"type":0},{"id":3,"sLen":0,"time":40617.9766,"type":0},{"id":2,"sLen":0,"time":40786.5156,"type":0},{"id":3,"sLen":0,"time":40955.0547,"type":0},{"id":0,"sLen":0,"time":41123.5938,"type":0},{"id":1,"sLen":0,"time":41292.1367,"type":0},{"id":0,"sLen":0,"time":41460.6758,"type":0},{"id":2,"sLen":0,"time":41544.9453,"type":0},{"id":0,"sLen":0,"time":41629.2148,"type":0},{"id":3,"sLen":0,"time":41797.7539,"type":0},{"id":1,"sLen":0,"time":41966.293,"type":0},{"id":0,"sLen":0,"time":42134.832,"type":0},{"id":1,"sLen":0,"time":42303.37,"type":0},{"id":0,"sLen":0,"time":42471.91,"type":0},{"id":1,"sLen":0,"time":42640.45,"type":0},{"id":1,"sLen":0,"time":42808.99,"type":0},{"id":3,"sLen":0,"time":42977.5273,"type":0},{"id":3,"sLen":0,"time":43061.7969,"type":0},{"id":3,"sLen":0,"time":48539.3242,"type":0},{"id":1,"sLen":0,"time":48623.5938,"type":0},{"id":3,"sLen":0,"time":48707.8633,"type":0},{"id":0,"sLen":0,"time":48876.4063,"type":0},{"id":3,"sLen":0,"time":49044.9453,"type":0},{"id":1,"sLen":0,"time":49129.2148,"type":0},{"id":2,"sLen":0,"time":49213.4844,"type":0},{"id":0,"sLen":0,"time":49382.0234,"type":0},{"id":1,"sLen":0,"time":49550.5625,"type":0},{"id":3,"sLen":0,"time":49719.1,"type":0},{"id":3,"sLen":0,"time":49803.37,"type":0},{"id":2,"sLen":0,"time":49887.64,"type":0},{"id":3,"sLen":0,"time":50056.18,"type":0},{"id":0,"sLen":0,"time":50140.45,"type":0},{"id":1,"sLen":0,"time":50224.72,"type":0},{"id":0,"sLen":0,"time":50393.2578,"type":0},{"id":2,"sLen":0,"time":50561.7969,"type":0},{"id":3,"sLen":0,"time":50730.3359,"type":0},{"id":1,"sLen":0,"time":50898.875,"type":0},{"id":3,"sLen":0,"time":51067.4141,"type":0},{"id":2,"sLen":0,"time":51151.6836,"type":0},{"id":0,"sLen":0,"time":51235.9531,"type":0},{"id":3,"sLen":0,"time":51404.4961,"type":0},{"id":1,"sLen":0,"time":51573.0352,"type":0},{"id":3,"sLen":0,"time":51741.5742,"type":0},{"id":0,"sLen":0,"time":51910.1133,"type":0},{"id":1,"sLen":0,"time":52078.6523,"type":0},{"id":3,"sLen":0,"time":52247.19,"type":0},{"id":2,"sLen":0,"time":52331.46,"type":0},{"id":3,"sLen":0,"time":52415.73,"type":0},{"id":1,"sLen":0,"time":52584.27,"type":0},{"id":3,"sLen":0,"time":52752.81,"type":0},{"id":0,"sLen":0,"time":52921.3477,"type":0},{"id":3,"sLen":0,"time":53089.8867,"type":0},{"id":1,"sLen":0,"time":53258.4258,"type":0},{"id":3,"sLen":0,"time":53426.9648,"type":0},{"id":2,"sLen":0,"time":53595.5039,"type":0},{"id":2,"sLen":0,"time":53764.043,"type":0},{"id":1,"sLen":842.696654999971,"time":53932.5859,"type":0},{"id":2,"sLen":505.617981000003,"time":55955.0547,"type":0},{"id":1,"sLen":674.157299999999,"time":56629.2148,"type":0},{"id":0,"sLen":252.808989999998,"time":58651.6836,"type":0},{"id":2,"sLen":84.2696599999836,"time":58988.7656,"type":0},{"id":1,"sLen":0,"time":59157.3047,"type":0},{"id":1,"sLen":0,"time":59325.8438,"type":0},{"id":2,"sLen":0,"time":59494.3828,"type":0},{"id":0,"sLen":0,"time":59662.92,"type":0},{"id":0,"sLen":0,"time":59831.46,"type":0},{"id":2,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60168.54,"type":0},{"id":1,"sLen":0,"time":60337.08,"type":0},{"id":3,"sLen":0,"time":60505.6172,"type":0},{"id":0,"sLen":0,"time":60674.1563,"type":0},{"id":1,"sLen":0,"time":60842.6953,"type":0},{"id":2,"sLen":0,"time":61011.2344,"type":0},{"id":3,"sLen":0,"time":61179.7734,"type":0},{"id":1,"sLen":0,"time":61348.3164,"type":0},{"id":3,"sLen":0,"time":61516.8555,"type":0},{"id":0,"sLen":0,"time":61685.3945,"type":0},{"id":3,"sLen":0,"time":61853.9336,"type":0},{"id":1,"sLen":0,"time":62022.4727,"type":0},{"id":2,"sLen":0,"time":62191.01,"type":0},{"id":0,"sLen":0,"time":62359.55,"type":0},{"id":0,"sLen":0,"time":62528.09,"type":0},{"id":2,"sLen":0,"time":62696.63,"type":0},{"id":3,"sLen":0,"time":62865.168,"type":0},{"id":1,"sLen":0,"time":63033.707,"type":0},{"id":3,"sLen":0,"time":63202.2461,"type":0},{"id":0,"sLen":0,"time":63370.7852,"type":0},{"id":2,"sLen":0,"time":63539.3242,"type":0},{"id":3,"sLen":0,"time":63623.5938,"type":0},{"id":1,"sLen":0,"time":63707.8633,"type":0},{"id":3,"sLen":0,"time":63876.4063,"type":0},{"id":1,"sLen":0,"time":64044.9453,"type":0},{"id":3,"sLen":0,"time":64213.4844,"type":0},{"id":0,"sLen":0,"time":64382.0234,"type":0},{"id":3,"sLen":0,"time":64550.5625,"type":0},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":0,"time":65056.18,"type":0},{"id":1,"sLen":0,"time":65224.72,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":168.539321999997,"time":65730.3359,"type":0},{"id":3,"sLen":421.348327999993,"time":66067.4141,"type":0},{"id":0,"sLen":84.2696600000054,"time":66573.03,"type":0},{"id":1,"sLen":421.348327999993,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":67247.19,"type":0},{"id":2,"sLen":589.887633999999,"time":67415.73,"type":0},{"id":1,"sLen":252.808990000005,"time":68089.89,"type":0},{"id":2,"sLen":252.808990000005,"time":68426.97,"type":0},{"id":3,"sLen":589.887633999999,"time":68764.05,"type":0},{"id":0,"sLen":589.887633999999,"time":69438.2,"type":0},{"id":1,"sLen":252.808990000005,"time":70112.36,"type":0},{"id":0,"sLen":0,"time":70449.44,"type":0},{"id":1,"sLen":0,"time":70617.98,"type":0},{"id":3,"sLen":252.808990000005,"time":70786.5156,"type":0},{"id":1,"sLen":0,"time":71123.59,"type":0},{"id":0,"sLen":0,"time":71460.67,"type":0},{"id":0,"sLen":0,"time":71797.75,"type":0},{"id":0,"sLen":0,"time":72134.83,"type":0},{"id":0,"sLen":0,"time":72303.37,"type":0},{"id":2,"sLen":0,"time":72471.91,"type":0},{"id":3,"sLen":0,"time":72556.18,"type":0},{"id":2,"sLen":0,"time":72640.45,"type":0},{"id":1,"sLen":0,"time":72808.99,"type":0},{"id":3,"sLen":0,"time":72977.53,"type":0},{"id":2,"sLen":0,"time":73146.07,"type":0},{"id":1,"sLen":0,"time":73314.61,"type":0},{"id":3,"sLen":0,"time":73483.15,"type":0},{"id":2,"sLen":0,"time":73651.69,"type":0},{"id":1,"sLen":0,"time":73820.23,"type":0},{"id":3,"sLen":0,"time":73904.49,"type":0},{"id":2,"sLen":0,"time":73988.7656,"type":0},{"id":0,"sLen":0,"time":74157.3047,"type":0},{"id":3,"sLen":0,"time":74325.84,"type":0},{"id":2,"sLen":0,"time":74494.38,"type":0},{"id":3,"sLen":0,"time":74662.92,"type":0},{"id":1,"sLen":0,"time":74831.46,"type":0},{"id":3,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75168.54,"type":0},{"id":3,"sLen":0,"time":75337.08,"type":0},{"id":1,"sLen":1264.04492,"time":75505.62,"type":0},{"id":0,"sLen":1264.04492,"time":76853.93,"type":0},{"id":3,"sLen":1264.04492,"time":78202.25,"type":0},{"id":0,"sLen":1264.04492,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":80898.875,"type":0},{"id":0,"sLen":0,"time":81067.4141,"type":0},{"id":1,"sLen":0,"time":81235.95,"type":0},{"id":0,"sLen":0,"time":81404.49,"type":0},{"id":2,"sLen":0,"time":81488.7656,"type":0},{"id":0,"sLen":0,"time":81573.03,"type":0},{"id":1,"sLen":0,"time":81741.57,"type":0},{"id":3,"sLen":0,"time":81910.11,"type":0},{"id":0,"sLen":0,"time":82078.65,"type":0},{"id":1,"sLen":0,"time":82247.19,"type":0},{"id":3,"sLen":0,"time":82331.46,"type":0},{"id":1,"sLen":0,"time":82415.73,"type":0},{"id":0,"sLen":0,"time":82584.27,"type":0},{"id":1,"sLen":0,"time":82752.81,"type":0},{"id":2,"sLen":252.808990000005,"time":82921.35,"type":0},{"id":0,"sLen":0,"time":83258.43,"type":0},{"id":0,"sLen":0,"time":83426.97,"type":0},{"id":1,"sLen":0,"time":83595.51,"type":0},{"id":0,"sLen":0,"time":83764.05,"type":0},{"id":3,"sLen":0,"time":83848.31,"type":0},{"id":3,"sLen":0,"time":83932.5859,"type":0},{"id":3,"sLen":0,"time":84101.125,"type":0},{"id":0,"sLen":0,"time":84185.39,"type":0},{"id":1,"sLen":0,"time":84269.6641,"type":0},{"id":1,"sLen":0,"time":84438.2,"type":0},{"id":3,"sLen":0,"time":84522.47,"type":0},{"id":2,"sLen":0,"time":84606.74,"type":0},{"id":0,"sLen":0,"time":84775.28,"type":0},{"id":1,"sLen":0,"time":84859.55,"type":0},{"id":2,"sLen":0,"time":84943.82,"type":0},{"id":3,"sLen":0,"time":85028.0859,"type":0},{"id":2,"sLen":0,"time":85112.36,"type":0},{"id":0,"sLen":84.2696600000054,"time":85196.63,"type":0},{"id":3,"sLen":0,"time":85365.17,"type":0},{"id":2,"sLen":0,"time":85449.44,"type":0},{"id":0,"sLen":0,"time":85617.98,"type":0},{"id":3,"sLen":0,"time":85786.5156,"type":0},{"id":0,"sLen":0,"time":85955.0547,"type":0},{"id":2,"sLen":0,"time":86123.59,"type":0},{"id":1,"sLen":0,"time":91685.39,"type":0},{"id":0,"sLen":0,"time":91769.6641,"type":0},{"id":3,"sLen":0,"time":91853.93,"type":0},{"id":2,"sLen":0,"time":92022.47,"type":0},{"id":1,"sLen":0,"time":92106.74,"type":0},{"id":3,"sLen":0,"time":92191.01,"type":0},{"id":2,"sLen":0,"time":92359.55,"type":0},{"id":3,"sLen":0,"time":92528.0859,"type":0},{"id":2,"sLen":0,"time":92696.63,"type":0},{"id":0,"sLen":0,"time":92865.17,"type":0},{"id":1,"sLen":0,"time":93033.71,"type":0},{"id":0,"sLen":0,"time":93117.98,"type":0},{"id":3,"sLen":0,"time":93202.25,"type":0},{"id":2,"sLen":0,"time":93370.79,"type":0},{"id":1,"sLen":0,"time":93455.0547,"type":0},{"id":3,"sLen":0,"time":93539.33,"type":0},{"id":2,"sLen":0,"time":93707.87,"type":0},{"id":3,"sLen":0,"time":93876.41,"type":0},{"id":1,"sLen":0,"time":94044.9453,"type":0},{"id":3,"sLen":0,"time":94213.4844,"type":0},{"id":2,"sLen":0,"time":94382.02,"type":0},{"id":3,"sLen":0,"time":94466.29,"type":0},{"id":2,"sLen":0,"time":94550.56,"type":0},{"id":1,"sLen":0,"time":94719.1,"type":0},{"id":0,"sLen":0,"time":94803.37,"type":0},{"id":1,"sLen":0,"time":94887.64,"type":0},{"id":3,"sLen":0,"time":95056.18,"type":0},{"id":3,"sLen":0,"time":95224.72,"type":0},{"id":1,"sLen":0,"time":95308.99,"type":0},{"id":3,"sLen":0,"time":95393.26,"type":0},{"id":1,"sLen":0,"time":95561.8,"type":0},{"id":0,"sLen":0,"time":95730.3359,"type":0},{"id":3,"sLen":0,"time":95814.61,"type":0},{"id":1,"sLen":0,"time":95898.875,"type":0},{"id":0,"sLen":0,"time":95983.15,"type":0},{"id":1,"sLen":0,"time":96067.4141,"type":0},{"id":3,"sLen":0,"time":96235.95,"type":0},{"id":0,"sLen":0,"time":96404.49,"type":0},{"id":3,"sLen":0,"time":96488.7656,"type":0},{"id":1,"sLen":0,"time":96573.03,"type":0},{"id":0,"sLen":0,"time":96657.3047,"type":0},{"id":1,"sLen":337.078643999994,"time":96741.57,"type":0},{"id":0,"sLen":589.887633999941,"time":98426.97,"type":0},{"id":2,"sLen":589.887633999999,"time":99101.125,"type":0},{"id":0,"sLen":589.887633999999,"time":101123.594,"type":0},{"id":2,"sLen":589.887633999984,"time":101797.75,"type":0},{"id":3,"sLen":0,"time":102471.906,"type":0},{"id":0,"sLen":0,"time":102556.18,"type":0},{"id":2,"sLen":0,"time":102640.453,"type":0},{"id":0,"sLen":0,"time":102808.992,"type":0},{"id":3,"sLen":0,"time":102977.531,"type":0},{"id":0,"sLen":0,"time":103061.8,"type":0},{"id":2,"sLen":0,"time":103146.07,"type":0},{"id":0,"sLen":0,"time":103314.609,"type":0},{"id":3,"sLen":0,"time":103483.148,"type":0},{"id":0,"sLen":0,"time":103567.414,"type":0},{"id":2,"sLen":0,"time":103651.688,"type":0},{"id":0,"sLen":0,"time":103820.227,"type":0},{"id":3,"sLen":0,"time":103988.766,"type":0},{"id":0,"sLen":0,"time":104073.031,"type":0},{"id":2,"sLen":0,"time":104157.305,"type":0},{"id":0,"sLen":0,"time":104325.844,"type":0},{"id":2,"sLen":0,"time":104494.383,"type":0},{"id":1,"sLen":0,"time":104662.922,"type":0},{"id":3,"sLen":0,"time":104831.461,"type":0},{"id":0,"sLen":0,"time":105000,"type":0},{"id":3,"sLen":0,"time":105168.539,"type":0},{"id":0,"sLen":0,"time":105252.813,"type":0},{"id":2,"sLen":0,"time":105337.078,"type":0},{"id":0,"sLen":0,"time":105505.617,"type":0},{"id":3,"sLen":0,"time":105674.156,"type":0},{"id":0,"sLen":0,"time":105758.43,"type":0},{"id":2,"sLen":0,"time":105842.695,"type":0},{"id":0,"sLen":0,"time":106011.234,"type":0},{"id":3,"sLen":0,"time":106179.773,"type":0},{"id":0,"sLen":0,"time":106264.047,"type":0},{"id":2,"sLen":0,"time":106348.313,"type":0},{"id":0,"sLen":0,"time":106516.852,"type":0},{"id":3,"sLen":0,"time":106685.391,"type":0},{"id":0,"sLen":0,"time":106769.664,"type":0},{"id":2,"sLen":0,"time":106853.93,"type":0},{"id":0,"sLen":0,"time":107022.469,"type":0},{"id":2,"sLen":0,"time":107191.008,"type":0},{"id":1,"sLen":0,"time":107359.547,"type":0},{"id":3,"sLen":0,"time":107528.086,"type":0},{"id":0,"sLen":0,"time":107696.633,"type":0},{"id":3,"sLen":589.887634000042,"time":107865.172,"type":0},{"id":1,"sLen":589.887634000042,"time":108539.328,"type":0},{"id":2,"sLen":589.887633999999,"time":109213.484,"type":0},{"id":1,"sLen":589.887633999999,"time":109887.641,"type":0},{"id":3,"sLen":0,"time":110561.8,"type":0},{"id":2,"sLen":0,"time":110646.07,"type":0},{"id":3,"sLen":0,"time":110730.336,"type":0},{"id":2,"sLen":0,"time":110814.609,"type":0},{"id":0,"sLen":0,"time":110898.875,"type":0},{"id":1,"sLen":0,"time":111067.414,"type":0},{"id":3,"sLen":0,"time":111235.953,"type":0},{"id":2,"sLen":0,"time":111320.227,"type":0},{"id":3,"sLen":0,"time":111404.492,"type":0},{"id":2,"sLen":0,"time":111488.766,"type":0},{"id":0,"sLen":0,"time":111573.031,"type":0},{"id":1,"sLen":0,"time":111741.57,"type":0},{"id":3,"sLen":0,"time":111910.109,"type":0},{"id":0,"sLen":0,"time":111994.383,"type":0},{"id":2,"sLen":0,"time":112078.648,"type":0},{"id":3,"sLen":0,"time":112162.922,"type":0},{"id":0,"sLen":0,"time":112247.188,"type":0},{"id":1,"sLen":0,"time":112415.727,"type":0},{"id":3,"sLen":0,"time":112584.273,"type":0},{"id":2,"sLen":0,"time":112668.539,"type":0},{"id":3,"sLen":0,"time":112752.813,"type":0},{"id":2,"sLen":0,"time":112837.078,"type":0},{"id":0,"sLen":0,"time":112921.352,"type":0},{"id":1,"sLen":0,"time":113089.891,"type":0},{"id":3,"sLen":589.887633999999,"time":113258.43,"type":0},{"id":1,"sLen":589.887633999999,"time":113932.586,"type":0},{"id":0,"sLen":589.887633999984,"time":114606.742,"type":0},{"id":1,"sLen":589.887633999999,"time":115280.9,"type":0},{"id":2,"sLen":0,"time":115955.055,"type":0},{"id":3,"sLen":0,"time":116123.594,"type":0},{"id":1,"sLen":0,"time":116292.133,"type":0},{"id":3,"sLen":0,"time":116460.672,"type":0},{"id":0,"sLen":0,"time":116629.211,"type":0},{"id":1,"sLen":0,"time":116797.75,"type":0},{"id":0,"sLen":0,"time":116966.289,"type":0},{"id":3,"sLen":0,"time":117050.563,"type":0},{"id":1,"sLen":0,"time":117134.828,"type":0},{"id":0,"sLen":0,"time":117219.1,"type":0},{"id":2,"sLen":0,"time":117303.367,"type":0},{"id":3,"sLen":0,"time":117471.906,"type":0},{"id":1,"sLen":0,"time":117640.453,"type":0},{"id":3,"sLen":0,"time":117808.992,"type":0},{"id":0,"sLen":0,"time":117977.531,"type":0},{"id":3,"sLen":0,"time":118061.8,"type":0},{"id":1,"sLen":0,"time":118146.07,"type":0},{"id":0,"sLen":0,"time":118230.336,"type":0},{"id":3,"sLen":0,"time":118314.609,"type":0},{"id":1,"sLen":0,"time":118483.148,"type":0}],"type":1,"characters":["bf-holding-gf"],"visible":true},{"keyCount":4,"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539325842697,"type":0},{"id":3,"sLen":0,"time":252.808988764045,"type":0},{"id":3,"sLen":0,"time":421.348314606742,"type":0},{"id":0,"sLen":0,"time":589.887640449438,"type":0},{"id":3,"sLen":0,"time":758.426966292135,"type":0},{"id":3,"sLen":0,"time":842.696629213483,"type":0},{"id":0,"sLen":0,"time":1011.23595505618,"type":0},{"id":0,"sLen":0,"time":1179.77528089888,"type":0},{"id":0,"sLen":0,"time":1264.04494382022,"type":0},{"id":3,"sLen":0,"time":1348.31460674157,"type":0},{"id":0,"sLen":0,"time":1516.85393258427,"type":0},{"id":0,"sLen":0,"time":1601.12359550562,"type":0},{"id":3,"sLen":0,"time":1853.93258426966,"type":0},{"id":0,"sLen":0,"time":2022.47191011236,"type":0},{"id":3,"sLen":0,"time":2106.74157303371,"type":0},{"id":3,"sLen":0,"time":2191.01123595506,"type":0},{"id":0,"sLen":0,"time":2359.55056179775,"type":0},{"id":0,"sLen":0,"time":2696.62921348315,"type":0},{"id":3,"sLen":0,"time":2865.16853932584,"type":0},{"id":3,"sLen":0,"time":2949.43820224719,"type":0},{"id":3,"sLen":0,"time":3117.97752808989,"type":0},{"id":0,"sLen":0,"time":3286.51685393258,"type":0},{"id":3,"sLen":0,"time":3455.05617977528,"type":0},{"id":3,"sLen":0,"time":3539.32584269663,"type":0},{"id":0,"sLen":0,"time":3707.86516853933,"type":0},{"id":0,"sLen":0,"time":3876.40449438202,"type":0},{"id":0,"sLen":0,"time":3960.67415730337,"type":0},{"id":3,"sLen":0,"time":4044.94382022472,"type":0},{"id":0,"sLen":0,"time":4213.48314606742,"type":0},{"id":0,"sLen":0,"time":4297.75280898876,"type":0},{"id":3,"sLen":0,"time":4550.56179775281,"type":0},{"id":0,"sLen":0,"time":4719.10112359551,"type":0},{"id":3,"sLen":0,"time":4803.37078651685,"type":0},{"id":3,"sLen":0,"time":4887.6404494382,"type":0},{"id":0,"sLen":0,"time":5056.1797752809,"type":0},{"id":0,"sLen":0,"time":5140.44943820225,"type":0},{"id":0,"sLen":0,"time":5224.7191011236,"type":0},{"id":0,"sLen":0,"time":5393.25842696629,"type":0},{"id":3,"sLen":0,"time":5561.79775280899,"type":0},{"id":3,"sLen":0,"time":5646.06741573034,"type":0},{"id":3,"sLen":0,"time":5814.60674157303,"type":0},{"id":0,"sLen":0,"time":5983.14606741573,"type":0},{"id":3,"sLen":0,"time":6151.68539325843,"type":0},{"id":3,"sLen":0,"time":6235.95505617978,"type":0},{"id":0,"sLen":0,"time":6404.49438202247,"type":0},{"id":0,"sLen":0,"time":6573.03370786517,"type":0},{"id":0,"sLen":0,"time":6657.30337078652,"type":0},{"id":3,"sLen":0,"time":6741.57303370786,"type":0},{"id":0,"sLen":0,"time":6910.11235955056,"type":0},{"id":0,"sLen":0,"time":6994.38202247191,"type":0},{"id":3,"sLen":0,"time":7247.19101123595,"type":0},{"id":0,"sLen":0,"time":7415.73033707865,"type":0},{"id":3,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7584.26966292135,"type":0},{"id":0,"sLen":0,"time":7752.80898876405,"type":0},{"id":0,"sLen":0,"time":8089.88764044944,"type":0},{"id":3,"sLen":0,"time":8258.42696629213,"type":0},{"id":3,"sLen":0,"time":8342.69662921348,"type":0},{"id":3,"sLen":0,"time":8511.23595505618,"type":0},{"id":0,"sLen":0,"time":8679.77528089888,"type":0},{"id":3,"sLen":0,"time":8848.31460674157,"type":0},{"id":3,"sLen":0,"time":8932.58426966292,"type":0},{"id":0,"sLen":0,"time":9101.12359550562,"type":0},{"id":0,"sLen":0,"time":9269.66292134831,"type":0},{"id":0,"sLen":0,"time":9353.93258426966,"type":0},{"id":3,"sLen":0,"time":9438.20224719101,"type":0},{"id":0,"sLen":0,"time":9606.74157303371,"type":0},{"id":0,"sLen":0,"time":9691.01123595505,"type":0},{"id":3,"sLen":0,"time":9943.8202247191,"type":0},{"id":0,"sLen":0,"time":10112.3595505618,"type":0},{"id":3,"sLen":0,"time":10196.6292134831,"type":0},{"id":3,"sLen":0,"time":10280.8988764045,"type":0},{"id":0,"sLen":0,"time":10449.4382022472,"type":0},{"id":0,"sLen":0,"time":21235.9550561798,"type":0},{"id":3,"sLen":0,"time":21320.2247191011,"type":0},{"id":0,"sLen":0,"time":21404.4943820225,"type":0},{"id":0,"sLen":0,"time":21573.0337078652,"type":0},{"id":3,"sLen":0,"time":21910.1123595506,"type":0},{"id":0,"sLen":0,"time":22247.191011236,"type":0},{"id":3,"sLen":0,"time":22584.2696629213,"type":0},{"id":0,"sLen":0,"time":22921.3483146067,"type":0},{"id":0,"sLen":0,"time":23258.4269662921,"type":0},{"id":3,"sLen":0,"time":23595.5056179775,"type":0},{"id":3,"sLen":0,"time":23932.5842696629,"type":0},{"id":0,"sLen":0,"time":24269.6629213483,"type":0},{"id":3,"sLen":0,"time":24606.7415730337,"type":0},{"id":0,"sLen":0,"time":24943.8202247191,"type":0},{"id":3,"sLen":0,"time":25280.8988764045,"type":0},{"id":0,"sLen":0,"time":25617.9775280899,"type":0},{"id":0,"sLen":0,"time":25955.0561797753,"type":0},{"id":3,"sLen":0,"time":26292.1348314607,"type":0},{"id":3,"sLen":0,"time":26629.2134831461,"type":0},{"id":3,"sLen":0,"time":26713.4831460674,"type":0},{"id":3,"sLen":0,"time":26797.7528089888,"type":0},{"id":3,"sLen":0,"time":26966.2921348315,"type":0},{"id":0,"sLen":0,"time":27303.3707865169,"type":0},{"id":0,"sLen":0,"time":27640.4494382022,"type":0},{"id":3,"sLen":0,"time":27977.5280898876,"type":0},{"id":3,"sLen":0,"time":28314.606741573,"type":0},{"id":0,"sLen":0,"time":28651.6853932584,"type":0},{"id":3,"sLen":0,"time":28988.7640449438,"type":0},{"id":0,"sLen":0,"time":29325.8426966292,"type":0},{"id":0,"sLen":0,"time":29662.9213483146,"type":0},{"id":0,"sLen":0,"time":29831.4606741573,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30168.5393258427,"type":0},{"id":3,"sLen":0,"time":30337.0786516854,"type":0},{"id":3,"sLen":0,"time":30505.6179775281,"type":0},{"id":3,"sLen":0,"time":30674.1573033708,"type":0},{"id":0,"sLen":0,"time":30842.6966292135,"type":0},{"id":0,"sLen":0,"time":31011.2359550562,"type":0},{"id":3,"sLen":0,"time":31348.3146067416,"type":0},{"id":3,"sLen":0,"time":31685.393258427,"type":0},{"id":0,"sLen":0,"time":31853.9325842697,"type":0},{"id":0,"sLen":0,"time":32359.5505617978,"type":0},{"id":3,"sLen":0,"time":32696.6292134831,"type":0},{"id":0,"sLen":0,"time":33033.7078651685,"type":0},{"id":0,"sLen":0,"time":33202.2471910112,"type":0},{"id":3,"sLen":0,"time":33370.7865168539,"type":0},{"id":0,"sLen":0,"time":33707.8651685393,"type":0},{"id":3,"sLen":0,"time":34044.9438202247,"type":0},{"id":3,"sLen":0,"time":34382.0224719101,"type":0},{"id":0,"sLen":0,"time":34550.5617977528,"type":0},{"id":3,"sLen":0,"time":34719.1011235955,"type":0},{"id":0,"sLen":0,"time":35056.1797752809,"type":0},{"id":3,"sLen":0,"time":35393.2584269663,"type":0},{"id":0,"sLen":0,"time":35730.3370786517,"type":0},{"id":0,"sLen":0,"time":35898.8764044944,"type":0},{"id":3,"sLen":0,"time":36067.4157303371,"type":0},{"id":0,"sLen":0,"time":36404.4943820225,"type":0},{"id":3,"sLen":0,"time":36741.5730337079,"type":0},{"id":3,"sLen":0,"time":37078.6516853933,"type":0},{"id":0,"sLen":0,"time":37247.191011236,"type":0},{"id":3,"sLen":0,"time":37415.7303370787,"type":0},{"id":0,"sLen":0,"time":37752.808988764,"type":0},{"id":3,"sLen":0,"time":38089.8876404494,"type":0},{"id":3,"sLen":0,"time":38426.9662921348,"type":0},{"id":0,"sLen":0,"time":38595.5056179775,"type":0},{"id":3,"sLen":0,"time":38764.0449438202,"type":0},{"id":0,"sLen":0,"time":39101.1235955056,"type":0},{"id":3,"sLen":0,"time":39438.202247191,"type":0},{"id":3,"sLen":0,"time":39775.2808988764,"type":0},{"id":0,"sLen":0,"time":39943.8202247191,"type":0},{"id":3,"sLen":0,"time":40112.3595505618,"type":0},{"id":0,"sLen":0,"time":40449.4382022472,"type":0},{"id":3,"sLen":0,"time":40786.5168539326,"type":0},{"id":3,"sLen":0,"time":41123.595505618,"type":0},{"id":0,"sLen":0,"time":41292.1348314607,"type":0},{"id":3,"sLen":0,"time":41460.6741573034,"type":0},{"id":0,"sLen":0,"time":41797.7528089888,"type":0},{"id":0,"sLen":0,"time":42134.8314606742,"type":0},{"id":0,"sLen":0,"time":42471.9101123596,"type":0},{"id":0,"sLen":0,"time":42640.4494382022,"type":0},{"id":3,"sLen":0,"time":42808.9887640449,"type":0},{"id":3,"sLen":0,"time":42893.2584269663,"type":0},{"id":3,"sLen":0,"time":42977.5280898876,"type":0},{"id":0,"sLen":0,"time":43146.0674157303,"type":0},{"id":3,"sLen":0,"time":43483.1460674157,"type":0},{"id":3,"sLen":0,"time":43820.2247191011,"type":0},{"id":0,"sLen":0,"time":43988.7640449438,"type":0},{"id":3,"sLen":0,"time":44157.3033707865,"type":0},{"id":0,"sLen":0,"time":44494.3820224719,"type":0},{"id":3,"sLen":0,"time":44831.4606741573,"type":0},{"id":0,"sLen":0,"time":45168.5393258427,"type":0},{"id":0,"sLen":0,"time":45337.0786516854,"type":0},{"id":3,"sLen":0,"time":45505.6179775281,"type":0},{"id":0,"sLen":0,"time":45842.6966292135,"type":0},{"id":3,"sLen":0,"time":46179.7752808989,"type":0},{"id":3,"sLen":0,"time":46516.8539325843,"type":0},{"id":0,"sLen":0,"time":46685.393258427,"type":0},{"id":3,"sLen":0,"time":46853.9325842697,"type":0},{"id":0,"sLen":0,"time":47191.0112359551,"type":0},{"id":3,"sLen":0,"time":47528.0898876404,"type":0},{"id":3,"sLen":0,"time":47865.1685393258,"type":0},{"id":0,"sLen":0,"time":48033.7078651685,"type":0},{"id":3,"sLen":0,"time":48202.2471910112,"type":0},{"id":0,"sLen":0,"time":48539.3258426966,"type":0},{"id":3,"sLen":0,"time":48876.404494382,"type":0},{"id":3,"sLen":0,"time":49213.4831460674,"type":0},{"id":0,"sLen":0,"time":49382.0224719101,"type":0},{"id":3,"sLen":0,"time":49550.5617977528,"type":0},{"id":0,"sLen":0,"time":49887.6404494382,"type":0},{"id":3,"sLen":0,"time":50224.7191011236,"type":0},{"id":0,"sLen":0,"time":50561.797752809,"type":0},{"id":0,"sLen":0,"time":50730.3370786517,"type":0},{"id":3,"sLen":0,"time":50898.8764044944,"type":0},{"id":0,"sLen":0,"time":51235.9550561798,"type":0},{"id":3,"sLen":0,"time":51573.0337078652,"type":0},{"id":3,"sLen":0,"time":51910.1123595506,"type":0},{"id":0,"sLen":0,"time":52078.6516853933,"type":0},{"id":3,"sLen":0,"time":52247.191011236,"type":0},{"id":0,"sLen":0,"time":52584.2696629213,"type":0},{"id":0,"sLen":0,"time":52921.3483146067,"type":0},{"id":0,"sLen":0,"time":53258.4269662921,"type":0},{"id":0,"sLen":0,"time":53426.9662921348,"type":0},{"id":3,"sLen":0,"time":53595.5056179775,"type":0},{"id":3,"sLen":0,"time":53679.7752808989,"type":0},{"id":3,"sLen":0,"time":53764.0449438202,"type":0},{"id":0,"sLen":0,"time":53932.5842696629,"type":0},{"id":3,"sLen":0,"time":54101.1235955056,"type":0},{"id":3,"sLen":0,"time":54185.393258427,"type":0},{"id":3,"sLen":0,"time":54353.9325842697,"type":0},{"id":0,"sLen":0,"time":54522.4719101124,"type":0},{"id":3,"sLen":0,"time":54691.0112359551,"type":0},{"id":3,"sLen":0,"time":54775.2808988764,"type":0},{"id":0,"sLen":0,"time":54943.8202247191,"type":0},{"id":0,"sLen":0,"time":55112.3595505618,"type":0},{"id":0,"sLen":0,"time":55196.6292134831,"type":0},{"id":3,"sLen":0,"time":55280.8988764045,"type":0},{"id":0,"sLen":0,"time":55449.4382022472,"type":0},{"id":0,"sLen":0,"time":55533.7078651685,"type":0},{"id":3,"sLen":0,"time":55786.5168539326,"type":0},{"id":0,"sLen":0,"time":55955.0561797753,"type":0},{"id":3,"sLen":0,"time":56039.3258426966,"type":0},{"id":3,"sLen":0,"time":56123.595505618,"type":0},{"id":0,"sLen":0,"time":56292.1348314607,"type":0},{"id":0,"sLen":0,"time":56629.2134831461,"type":0},{"id":3,"sLen":0,"time":56797.7528089888,"type":0},{"id":3,"sLen":0,"time":56882.0224719101,"type":0},{"id":3,"sLen":0,"time":57050.5617977528,"type":0},{"id":0,"sLen":0,"time":57219.1011235955,"type":0},{"id":3,"sLen":0,"time":57387.6404494382,"type":0},{"id":3,"sLen":0,"time":57471.9101123595,"type":0},{"id":0,"sLen":0,"time":57640.4494382022,"type":0},{"id":0,"sLen":0,"time":57808.9887640449,"type":0},{"id":0,"sLen":0,"time":57893.2584269663,"type":0},{"id":3,"sLen":0,"time":57977.5280898876,"type":0},{"id":0,"sLen":0,"time":58146.0674157303,"type":0},{"id":0,"sLen":0,"time":58230.3370786517,"type":0},{"id":3,"sLen":0,"time":58483.1460674157,"type":0},{"id":0,"sLen":0,"time":58651.6853932584,"type":0},{"id":3,"sLen":0,"time":58735.9550561798,"type":0},{"id":3,"sLen":0,"time":58820.2247191011,"type":0},{"id":0,"sLen":0,"time":58988.7640449438,"type":0},{"id":0,"sLen":0,"time":59073.0337078652,"type":0},{"id":0,"sLen":0,"time":59157.3033707865,"type":0},{"id":0,"sLen":0,"time":59325.8426966292,"type":0},{"id":3,"sLen":0,"time":59494.3820224719,"type":0},{"id":3,"sLen":0,"time":59578.6516853933,"type":0},{"id":3,"sLen":0,"time":59747.191011236,"type":0},{"id":0,"sLen":0,"time":59915.7303370786,"type":0},{"id":3,"sLen":0,"time":60084.2696629213,"type":0},{"id":3,"sLen":0,"time":60168.5393258427,"type":0},{"id":0,"sLen":0,"time":60337.0786516854,"type":0},{"id":0,"sLen":0,"time":60505.6179775281,"type":0},{"id":0,"sLen":0,"time":60589.8876404494,"type":0},{"id":3,"sLen":0,"time":60674.1573033708,"type":0},{"id":0,"sLen":0,"time":60842.6966292135,"type":0},{"id":0,"sLen":0,"time":60926.9662921348,"type":0},{"id":3,"sLen":0,"time":61179.7752808989,"type":0},{"id":0,"sLen":0,"time":61348.3146067416,"type":0},{"id":3,"sLen":0,"time":61432.5842696629,"type":0},{"id":3,"sLen":0,"time":61516.8539325843,"type":0},{"id":0,"sLen":0,"time":61685.393258427,"type":0},{"id":0,"sLen":0,"time":62022.4719101124,"type":0},{"id":3,"sLen":0,"time":62191.0112359551,"type":0},{"id":3,"sLen":0,"time":62275.2808988764,"type":0},{"id":3,"sLen":0,"time":62443.8202247191,"type":0},{"id":0,"sLen":0,"time":62612.3595505618,"type":0},{"id":3,"sLen":0,"time":62780.8988764045,"type":0},{"id":3,"sLen":0,"time":62865.1685393258,"type":0},{"id":0,"sLen":0,"time":63033.7078651685,"type":0},{"id":0,"sLen":0,"time":63202.2471910112,"type":0},{"id":0,"sLen":0,"time":63286.5168539326,"type":0},{"id":3,"sLen":0,"time":63370.7865168539,"type":0},{"id":0,"sLen":0,"time":63539.3258426966,"type":0},{"id":0,"sLen":0,"time":63623.595505618,"type":0},{"id":3,"sLen":0,"time":63876.404494382,"type":0},{"id":0,"sLen":0,"time":64044.9438202247,"type":0},{"id":3,"sLen":0,"time":64129.2134831461,"type":0},{"id":3,"sLen":0,"time":64213.4831460674,"type":0},{"id":0,"sLen":0,"time":64382.0224719101,"type":0},{"id":3,"sLen":0,"time":64719.1011235955,"type":0},{"id":0,"sLen":0,"time":65056.1797752809,"type":0},{"id":0,"sLen":0,"time":65393.2584269663,"type":0},{"id":0,"sLen":0,"time":65730.3370786517,"type":0},{"id":0,"sLen":0,"time":66067.4157303371,"type":0},{"id":3,"sLen":0,"time":66404.4943820225,"type":0},{"id":3,"sLen":0,"time":66741.5730337079,"type":0},{"id":3,"sLen":0,"time":67078.6516853933,"type":0},{"id":3,"sLen":0,"time":67415.7303370787,"type":0},{"id":0,"sLen":0,"time":67752.808988764,"type":0},{"id":0,"sLen":0,"time":68089.8876404494,"type":0},{"id":0,"sLen":0,"time":68426.9662921348,"type":0},{"id":0,"sLen":0,"time":68764.0449438202,"type":0},{"id":3,"sLen":0,"time":69101.1235955056,"type":0},{"id":3,"sLen":0,"time":69438.202247191,"type":0},{"id":3,"sLen":0,"time":69775.2808988764,"type":0},{"id":3,"sLen":0,"time":69859.5505617978,"type":0},{"id":3,"sLen":0,"time":69943.8202247191,"type":0},{"id":3,"sLen":0,"time":70112.3595505618,"type":0},{"id":0,"sLen":0,"time":70449.4382022472,"type":0},{"id":0,"sLen":0,"time":70786.5168539326,"type":0},{"id":0,"sLen":0,"time":71123.595505618,"type":0},{"id":0,"sLen":0,"time":71460.6741573034,"type":0},{"id":3,"sLen":0,"time":71797.7528089888,"type":0},{"id":3,"sLen":0,"time":72134.8314606741,"type":0},{"id":3,"sLen":0,"time":72471.9101123595,"type":0},{"id":3,"sLen":0,"time":72556.1797752809,"type":0},{"id":3,"sLen":0,"time":72640.4494382023,"type":0},{"id":3,"sLen":0,"time":72808.9887640449,"type":0},{"id":3,"sLen":0,"time":72893.2584269663,"type":0},{"id":3,"sLen":0,"time":72977.5280898877,"type":0},{"id":0,"sLen":0,"time":73146.0674157303,"type":0},{"id":0,"sLen":0,"time":73230.3370786517,"type":0},{"id":0,"sLen":0,"time":73314.6067415731,"type":0},{"id":0,"sLen":0,"time":73483.1460674157,"type":0},{"id":0,"sLen":0,"time":73567.4157303371,"type":0},{"id":0,"sLen":0,"time":73651.6853932585,"type":0},{"id":0,"sLen":0,"time":73820.2247191011,"type":0},{"id":0,"sLen":0,"time":73904.4943820225,"type":0},{"id":0,"sLen":0,"time":73988.7640449438,"type":0},{"id":0,"sLen":0,"time":74157.3033707866,"type":0},{"id":0,"sLen":0,"time":74241.5730337079,"type":0},{"id":0,"sLen":0,"time":74325.8426966292,"type":0},{"id":0,"sLen":0,"time":74410.1123595506,"type":0},{"id":3,"sLen":0,"time":74494.382022472,"type":0},{"id":3,"sLen":0,"time":74578.6516853933,"type":0},{"id":3,"sLen":0,"time":74662.9213483146,"type":0},{"id":3,"sLen":0,"time":74747.191011236,"type":0},{"id":0,"sLen":0,"time":74831.4606741573,"type":0},{"id":0,"sLen":0,"time":74915.7303370787,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75084.2696629214,"type":0},{"id":3,"sLen":0,"time":75168.5393258427,"type":0},{"id":0,"sLen":0,"time":75505.6179775281,"type":0},{"id":3,"sLen":0,"time":75842.6966292135,"type":0},{"id":0,"sLen":0,"time":76179.7752808989,"type":0},{"id":0,"sLen":0,"time":76516.8539325843,"type":0},{"id":3,"sLen":0,"time":76853.9325842697,"type":0},{"id":3,"sLen":0,"time":77191.0112359551,"type":0},{"id":0,"sLen":0,"time":77528.0898876405,"type":0},{"id":3,"sLen":0,"time":77865.1685393259,"type":0},{"id":3,"sLen":0,"time":78033.7078651686,"type":0},{"id":0,"sLen":0,"time":78202.2471910113,"type":0},{"id":0,"sLen":0,"time":78539.3258426967,"type":0},{"id":3,"sLen":0,"time":78876.4044943821,"type":0},{"id":0,"sLen":0,"time":79213.4831460675,"type":0},{"id":0,"sLen":0,"time":79550.5617977529,"type":0},{"id":3,"sLen":0,"time":79887.6404494383,"type":0},{"id":0,"sLen":0,"time":80140.4494382023,"type":0},{"id":0,"sLen":0,"time":80224.7191011237,"type":0},{"id":0,"sLen":0,"time":80308.988764045,"type":0},{"id":0,"sLen":0,"time":80477.5280898877,"type":0},{"id":3,"sLen":84.269662921346,"time":80730.3370786517,"type":0},{"id":0,"sLen":0,"time":80898.8764044945,"type":0},{"id":3,"sLen":0,"time":81067.4157303371,"type":0},{"id":0,"sLen":0,"time":81235.9550561799,"type":0},{"id":3,"sLen":0,"time":81404.4943820225,"type":0},{"id":0,"sLen":0,"time":81573.0337078652,"type":0},{"id":3,"sLen":0,"time":81741.5730337079,"type":0},{"id":0,"sLen":0,"time":81910.1123595506,"type":0},{"id":3,"sLen":0,"time":82078.6516853933,"type":0},{"id":3,"sLen":0,"time":82247.191011236,"type":0},{"id":0,"sLen":0,"time":82415.7303370787,"type":0},{"id":3,"sLen":0,"time":82584.2696629214,"type":0},{"id":0,"sLen":0,"time":82752.8089887641,"type":0},{"id":3,"sLen":0,"time":82921.3483146068,"type":0},{"id":0,"sLen":0,"time":83089.8876404495,"type":0},{"id":3,"sLen":0,"time":83258.4269662922,"type":0},{"id":3,"sLen":0,"time":83426.9662921349,"type":0},{"id":0,"sLen":0,"time":83595.5056179776,"type":0},{"id":0,"sLen":0,"time":83764.0449438203,"type":0},{"id":0,"sLen":0,"time":83932.584269663,"type":0},{"id":3,"sLen":0,"time":84269.6629213484,"type":0},{"id":3,"sLen":0,"time":84606.7415730338,"type":0},{"id":3,"sLen":0,"time":84775.2808988765,"type":0},{"id":3,"sLen":0,"time":84943.8202247192,"type":0},{"id":3,"sLen":0,"time":85280.8988764046,"type":0},{"id":0,"sLen":0,"time":85617.97752809,"type":0},{"id":0,"sLen":0,"time":85702.2471910113,"type":0},{"id":3,"sLen":0,"time":85870.786516854,"type":0},{"id":0,"sLen":0,"time":86039.3258426967,"type":0},{"id":0,"sLen":0,"time":86292.1348314607,"type":0},{"id":3,"sLen":0,"time":86629.2134831461,"type":0},{"id":0,"sLen":0,"time":87134.8314606741,"type":0},{"id":3,"sLen":0,"time":87303.3707865169,"type":0},{"id":0,"sLen":0,"time":87640.4494382023,"type":0},{"id":3,"sLen":0,"time":87977.5280898877,"type":0},{"id":0,"sLen":0,"time":88483.1460674157,"type":0},{"id":3,"sLen":0,"time":88651.6853932584,"type":0},{"id":0,"sLen":0,"time":88988.7640449438,"type":0},{"id":3,"sLen":0,"time":89325.8426966292,"type":0},{"id":0,"sLen":0,"time":89831.4606741573,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.0786516854,"type":0},{"id":3,"sLen":0,"time":90674.1573033708,"type":0},{"id":0,"sLen":0,"time":91179.7752808989,"type":0},{"id":3,"sLen":0,"time":91348.3146067416,"type":0},{"id":0,"sLen":0,"time":91685.393258427,"type":0},{"id":3,"sLen":0,"time":92022.4719101124,"type":0},{"id":0,"sLen":0,"time":92528.0898876405,"type":0},{"id":3,"sLen":0,"time":92696.6292134832,"type":0},{"id":0,"sLen":0,"time":93033.7078651686,"type":0},{"id":3,"sLen":0,"time":93370.786516854,"type":0},{"id":0,"sLen":0,"time":93876.404494382,"type":0},{"id":3,"sLen":0,"time":94044.9438202248,"type":0},{"id":0,"sLen":0,"time":94382.0224719102,"type":0},{"id":3,"sLen":0,"time":94719.1011235956,"type":0},{"id":0,"sLen":0,"time":95224.7191011236,"type":0},{"id":3,"sLen":0,"time":95393.2584269663,"type":0},{"id":0,"sLen":0,"time":95730.3370786517,"type":0},{"id":0,"sLen":0,"time":96067.4157303371,"type":0},{"id":0,"sLen":0,"time":96404.4943820225,"type":0},{"id":0,"sLen":0,"time":96573.0337078652,"type":0},{"id":3,"sLen":0,"time":96741.5730337079,"type":0},{"id":3,"sLen":0,"time":96825.8426966292,"type":0},{"id":3,"sLen":0,"time":96910.1123595505,"type":0},{"id":0,"sLen":0,"time":97078.6516853933,"type":0},{"id":3,"sLen":0,"time":97415.7303370787,"type":0},{"id":3,"sLen":0,"time":97752.808988764,"type":0},{"id":0,"sLen":0,"time":97921.3483146067,"type":0},{"id":3,"sLen":0,"time":98089.8876404494,"type":0},{"id":0,"sLen":0,"time":98426.9662921348,"type":0},{"id":3,"sLen":0,"time":98764.0449438202,"type":0},{"id":0,"sLen":0,"time":99101.1235955056,"type":0},{"id":0,"sLen":0,"time":99269.6629213483,"type":0},{"id":3,"sLen":0,"time":99438.202247191,"type":0},{"id":0,"sLen":0,"time":99775.2808988764,"type":0},{"id":3,"sLen":0,"time":100112.359550562,"type":0},{"id":3,"sLen":0,"time":100449.438202247,"type":0},{"id":0,"sLen":0,"time":100617.97752809,"type":0},{"id":3,"sLen":0,"time":100786.516853933,"type":0},{"id":0,"sLen":0,"time":101123.595505618,"type":0},{"id":3,"sLen":0,"time":101460.674157303,"type":0},{"id":3,"sLen":0,"time":101797.752808989,"type":0},{"id":0,"sLen":0,"time":101966.292134831,"type":0},{"id":3,"sLen":0,"time":102134.831460674,"type":0},{"id":0,"sLen":0,"time":102471.91011236,"type":0},{"id":3,"sLen":0,"time":102808.988764045,"type":0},{"id":3,"sLen":0,"time":103146.06741573,"type":0},{"id":0,"sLen":0,"time":103314.606741573,"type":0},{"id":3,"sLen":0,"time":103483.146067416,"type":0},{"id":0,"sLen":0,"time":103820.224719101,"type":0},{"id":3,"sLen":0,"time":104157.303370787,"type":0},{"id":0,"sLen":0,"time":104494.382022472,"type":0},{"id":0,"sLen":0,"time":104662.921348315,"type":0},{"id":3,"sLen":0,"time":104831.460674157,"type":0},{"id":0,"sLen":0,"time":105168.539325843,"type":0},{"id":3,"sLen":0,"time":105505.617977528,"type":0},{"id":3,"sLen":0,"time":105842.696629213,"type":0},{"id":0,"sLen":0,"time":106011.235955056,"type":0},{"id":3,"sLen":0,"time":106179.775280899,"type":0},{"id":0,"sLen":0,"time":106516.853932584,"type":0},{"id":0,"sLen":0,"time":106853.93258427,"type":0},{"id":0,"sLen":0,"time":107191.011235955,"type":0},{"id":3,"sLen":0,"time":107528.08988764,"type":0},{"id":0,"sLen":0,"time":107865.168539326,"type":0},{"id":0,"sLen":0,"time":108202.247191011,"type":0},{"id":0,"sLen":0,"time":108539.325842697,"type":0},{"id":0,"sLen":0,"time":108876.404494382,"type":0},{"id":3,"sLen":0,"time":109213.483146068,"type":0},{"id":3,"sLen":0,"time":109550.561797753,"type":0},{"id":3,"sLen":0,"time":109887.640449438,"type":0},{"id":3,"sLen":0,"time":110224.719101124,"type":0},{"id":0,"sLen":0,"time":110561.797752809,"type":0},{"id":0,"sLen":0,"time":110730.337078652,"type":0},{"id":0,"sLen":0,"time":110898.876404495,"type":0},{"id":3,"sLen":0,"time":111235.95505618,"type":0},{"id":3,"sLen":0,"time":111573.033707865,"type":0},{"id":3,"sLen":0,"time":111910.112359551,"type":0},{"id":3,"sLen":0,"time":112247.191011236,"type":0},{"id":0,"sLen":0,"time":112584.269662922,"type":0},{"id":3,"sLen":0,"time":112921.348314607,"type":0},{"id":0,"sLen":0,"time":113258.426966292,"type":0},{"id":3,"sLen":0,"time":113932.584269663,"type":0},{"id":0,"sLen":0,"time":114269.662921349,"type":0},{"id":3,"sLen":0,"time":114438.202247191,"type":0},{"id":0,"sLen":0,"time":114606.741573034,"type":0},{"id":3,"sLen":0,"time":114943.820224719,"type":0},{"id":0,"sLen":0,"time":115280.898876405,"type":0},{"id":3,"sLen":0,"time":115617.97752809,"type":0},{"id":3,"sLen":0,"time":115786.516853933,"type":0},{"id":0,"sLen":0,"time":115955.056179776,"type":0},{"id":3,"sLen":0,"time":116123.595505618,"type":0},{"id":0,"sLen":0,"time":116292.134831461,"type":0},{"id":0,"sLen":0,"time":116629.213483146,"type":0},{"id":3,"sLen":0,"time":116966.292134832,"type":0},{"id":3,"sLen":0,"time":117303.370786517,"type":0},{"id":0,"sLen":0,"time":117640.449438203,"type":0},{"id":3,"sLen":0,"time":117977.528089888,"type":0},{"id":3,"sLen":0,"time":118061.797752809,"type":0},{"id":0,"sLen":0,"time":118314.606741573,"type":0},{"id":3,"sLen":0,"time":118398.876404495,"type":0},{"id":0,"sLen":0,"time":118651.685393258,"type":0},{"id":3,"sLen":0,"time":118820.224719101,"type":0},{"id":3,"sLen":0,"time":118904.494382022,"type":0},{"id":3,"sLen":0,"time":119073.033707865,"type":0},{"id":0,"sLen":0,"time":119241.573033708,"type":0},{"id":3,"sLen":0,"time":119410.112359551,"type":0},{"id":3,"sLen":0,"time":119494.382022472,"type":0},{"id":0,"sLen":0,"time":119662.921348315,"type":0},{"id":0,"sLen":0,"time":119831.460674157,"type":0},{"id":0,"sLen":0,"time":119915.730337079,"type":0},{"id":3,"sLen":0,"time":120000,"type":0},{"id":0,"sLen":0,"time":120168.539325843,"type":0},{"id":0,"sLen":0,"time":120252.808988764,"type":0},{"id":3,"sLen":0,"time":120505.617977528,"type":0},{"id":0,"sLen":0,"time":120674.157303371,"type":0},{"id":3,"sLen":0,"time":120758.426966292,"type":0},{"id":3,"sLen":0,"time":120842.696629213,"type":0},{"id":0,"sLen":0,"time":121011.235955056,"type":0},{"id":0,"sLen":0,"time":121095.505617978,"type":0},{"id":0,"sLen":0,"time":121179.775280899,"type":0}],"visible":false,"type":2,"characters":["pico-speakers"]}],"scrollSpeed":2.6,"chartVersion":"1.6.0","stage":"tank","noteTypes":["No Anim Note"]} \ No newline at end of file diff --git a/assets/songs/stress/charts/normal.json b/assets/songs/stress/charts/normal.json index 111dc971..58f18c9f 100644 --- a/assets/songs/stress/charts/normal.json +++ b/assets/songs/stress/charts/normal.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":5393.25842696629,"name":"Camera Movement"},{"params":[0],"time":10786.5168539326,"name":"Camera Movement"},{"params":[1],"time":14831.4606741573,"name":"Camera Movement"},{"params":[0],"time":21573.0337078652,"name":"Camera Movement"},{"params":[1],"time":24269.6629213483,"name":"Camera Movement"},{"params":[0],"time":26966.2921348315,"name":"Camera Movement"},{"params":[1],"time":28314.606741573,"name":"Camera Movement"},{"params":[0],"time":31011.2359550562,"name":"Camera Movement"},{"params":[1],"time":37752.808988764,"name":"Camera Movement"},{"params":[0],"time":43146.0674157303,"name":"Camera Movement"},{"params":[1],"time":48539.3258426966,"name":"Camera Movement"},{"params":[0],"time":53932.5842696629,"name":"Camera Movement"},{"params":[1],"time":59325.8426966292,"name":"Camera Movement"},{"params":[0],"time":62022.4719101124,"name":"Camera Movement"},{"params":[0,"good!"],"time":62022.4719101124,"name":"Play Animation"},{"params":[1],"time":67415.7303370787,"name":"Camera Movement"},{"params":[0],"time":75505.6179775281,"name":"Camera Movement"},{"params":[1],"time":80898.8764044945,"name":"Camera Movement"},{"params":[0],"time":86292.1348314608,"name":"Camera Movement"},{"params":[1],"time":91685.3932584271,"name":"Camera Movement"},{"params":[0],"time":97078.6516853934,"name":"Camera Movement"},{"params":[1],"time":102471.91011236,"name":"Camera Movement"},{"params":[0],"time":107865.168539326,"name":"Camera Movement"},{"params":[1],"time":113258.426966292,"name":"Camera Movement"}],"scrollSpeed":2.2,"strumLines":[{"notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539322,"type":0},{"id":0,"sLen":0,"time":337.078644,"type":0},{"id":0,"sLen":0,"time":674.1573,"type":0},{"id":3,"sLen":0,"time":842.696655,"type":0},{"id":2,"sLen":0,"time":1011.23596,"type":0},{"id":1,"sLen":0,"time":1348.31458,"type":0},{"id":0,"sLen":0,"time":1516.85388,"type":0},{"id":3,"sLen":0,"time":1685.39331,"type":0},{"id":2,"sLen":0,"time":1853.93262,"type":0},{"id":0,"sLen":0,"time":2022.47192,"type":0},{"id":3,"sLen":0,"time":2191.01123,"type":0},{"id":1,"sLen":0,"time":2359.55054,"type":0},{"id":0,"sLen":0,"time":2528.08984,"type":0},{"id":2,"sLen":0,"time":2696.62915,"type":0},{"id":3,"sLen":0,"time":2865.16846,"type":0},{"id":0,"sLen":0,"time":3033.70776,"type":0},{"id":0,"sLen":0,"time":3370.78662,"type":0},{"id":3,"sLen":0,"time":3539.326,"type":0},{"id":2,"sLen":0,"time":3707.86523,"type":0},{"id":1,"sLen":0,"time":4044.94385,"type":0},{"id":0,"sLen":0,"time":4213.483,"type":0},{"id":3,"sLen":0,"time":4382.02246,"type":0},{"id":2,"sLen":0,"time":4550.562,"type":0},{"id":0,"sLen":0,"time":4719.101,"type":0},{"id":3,"sLen":0,"time":4887.64063,"type":0},{"id":1,"sLen":0,"time":5056.17969,"type":0},{"id":0,"sLen":0,"time":5224.719,"type":0},{"id":0,"sLen":0,"time":11123.5957,"type":0},{"id":1,"sLen":0,"time":11292.1348,"type":0},{"id":3,"sLen":252.808989999998,"time":11460.6738,"type":0},{"id":1,"sLen":252.80899,"time":11797.7529,"type":0},{"id":0,"sLen":0,"time":12134.8311,"type":0},{"id":0,"sLen":0,"time":12471.91,"type":0},{"id":0,"sLen":0,"time":12808.9883,"type":0},{"id":0,"sLen":0,"time":12977.5283,"type":0},{"id":3,"sLen":0,"time":13146.0674,"type":0},{"id":1,"sLen":0,"time":13483.1465,"type":0},{"id":0,"sLen":0,"time":13651.6855,"type":0},{"id":3,"sLen":0,"time":13820.2246,"type":0},{"id":1,"sLen":0,"time":13988.7637,"type":0},{"id":0,"sLen":0,"time":14157.3037,"type":0},{"id":3,"sLen":0,"time":14325.8428,"type":0},{"id":1,"sLen":0,"time":14494.3818,"type":0},{"id":0,"sLen":0,"time":14662.9209,"type":0},{"id":1,"sLen":0,"time":14831.4609,"type":0},{"id":0,"sLen":0,"time":15000,"type":0},{"id":3,"sLen":0,"time":15168.5391,"type":0},{"id":1,"sLen":0,"time":15337.0791,"type":0},{"id":0,"sLen":0,"time":15505.6182,"type":0},{"id":3,"sLen":0,"time":15674.1572,"type":0},{"id":1,"sLen":0,"time":15842.6963,"type":0},{"id":0,"sLen":0,"time":16011.2363,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":2,"sLen":0,"time":21741.5723,"type":0},{"id":0,"sLen":0,"time":21910.1133,"type":0},{"id":2,"sLen":0,"time":22078.6523,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":2,"sLen":0,"time":22415.73,"type":0},{"id":0,"sLen":0,"time":22584.27,"type":0},{"id":2,"sLen":0,"time":22752.8086,"type":0},{"id":3,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23089.8867,"type":0},{"id":3,"sLen":0,"time":23258.4277,"type":0},{"id":2,"sLen":0,"time":23426.9668,"type":0},{"id":3,"sLen":0,"time":23595.5059,"type":0},{"id":0,"sLen":0,"time":23764.0449,"type":0},{"id":1,"sLen":0,"time":23932.584,"type":0},{"id":0,"sLen":0,"time":24101.123,"type":0},{"id":2,"sLen":0,"time":24269.6621,"type":0},{"id":3,"sLen":0,"time":26966.293,"type":0},{"id":2,"sLen":0,"time":27134.832,"type":0},{"id":0,"sLen":0,"time":27303.3711,"type":0},{"id":2,"sLen":0,"time":27471.91,"type":0},{"id":3,"sLen":0,"time":27640.45,"type":0},{"id":2,"sLen":0,"time":27808.9883,"type":0},{"id":0,"sLen":0,"time":27977.5273,"type":0},{"id":2,"sLen":0,"time":28146.0684,"type":0},{"id":3,"sLen":0,"time":28314.6074,"type":0},{"id":2,"sLen":0,"time":28483.1465,"type":0},{"id":3,"sLen":0,"time":28651.6855,"type":0},{"id":2,"sLen":0,"time":28820.2246,"type":0},{"id":3,"sLen":0,"time":28988.7637,"type":0},{"id":0,"sLen":0,"time":29157.3027,"type":0},{"id":1,"sLen":0,"time":29325.8418,"type":0},{"id":0,"sLen":0,"time":29494.3828,"type":0},{"id":0,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":31011.2363,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":0,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":0,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":0,"sLen":0,"time":32359.55,"type":0},{"id":2,"sLen":0,"time":32696.6289,"type":0},{"id":3,"sLen":0,"time":32865.168,"type":0},{"id":0,"sLen":0,"time":33033.707,"type":0},{"id":2,"sLen":0,"time":33370.7852,"type":0},{"id":3,"sLen":0,"time":33539.3242,"type":0},{"id":1,"sLen":0,"time":33707.8633,"type":0},{"id":3,"sLen":0,"time":33876.4063,"type":0},{"id":1,"sLen":0,"time":34044.9453,"type":0},{"id":0,"sLen":0,"time":34213.4844,"type":0},{"id":1,"sLen":0,"time":34382.0234,"type":0},{"id":3,"sLen":0,"time":34550.5625,"type":0},{"id":1,"sLen":0,"time":34719.1,"type":0},{"id":0,"sLen":0,"time":34887.64,"type":0},{"id":0,"sLen":0,"time":35056.18,"type":0},{"id":2,"sLen":0,"time":35393.2578,"type":0},{"id":3,"sLen":0,"time":35561.7969,"type":0},{"id":0,"sLen":0,"time":35730.3359,"type":0},{"id":2,"sLen":0,"time":36067.4141,"type":0},{"id":3,"sLen":0,"time":36235.9531,"type":0},{"id":3,"sLen":0,"time":36404.4961,"type":0},{"id":1,"sLen":0,"time":36573.0352,"type":0},{"id":0,"sLen":0,"time":36741.5742,"type":0},{"id":1,"sLen":0,"time":36910.1133,"type":0},{"id":0,"sLen":0,"time":37078.6523,"type":0},{"id":1,"sLen":0,"time":37247.19,"type":0},{"id":1,"sLen":0,"time":37415.73,"type":0},{"id":0,"sLen":0,"time":37584.27,"type":0},{"id":3,"sLen":0,"time":43146.0664,"type":0},{"id":1,"sLen":0,"time":43314.6055,"type":0},{"id":0,"sLen":0,"time":43483.1445,"type":0},{"id":3,"sLen":0,"time":43651.6836,"type":0},{"id":2,"sLen":0,"time":43820.2266,"type":0},{"id":0,"sLen":0,"time":44157.3047,"type":0},{"id":3,"sLen":0,"time":44325.8438,"type":0},{"id":2,"sLen":0,"time":44494.3828,"type":0},{"id":3,"sLen":0,"time":44662.92,"type":0},{"id":0,"sLen":0,"time":44831.46,"type":0},{"id":2,"sLen":0,"time":45000,"type":0},{"id":3,"sLen":0,"time":45168.54,"type":0},{"id":2,"sLen":0,"time":45337.08,"type":0},{"id":3,"sLen":0,"time":45505.6172,"type":0},{"id":1,"sLen":0,"time":45674.1563,"type":0},{"id":0,"sLen":0,"time":45842.6953,"type":0},{"id":3,"sLen":0,"time":46011.2344,"type":0},{"id":1,"sLen":0,"time":46179.7734,"type":0},{"id":3,"sLen":0,"time":46348.3164,"type":0},{"id":0,"sLen":0,"time":46516.8555,"type":0},{"id":3,"sLen":0,"time":46685.3945,"type":0},{"id":1,"sLen":0,"time":46853.9336,"type":0},{"id":3,"sLen":0,"time":47022.4727,"type":0},{"id":2,"sLen":0,"time":47191.01,"type":0},{"id":1,"sLen":0,"time":47528.09,"type":0},{"id":1,"sLen":0,"time":47865.168,"type":0},{"id":1,"sLen":0,"time":48202.2461,"type":0},{"id":2,"sLen":0,"time":53932.5859,"type":0},{"id":3,"sLen":0,"time":54101.125,"type":0},{"id":0,"sLen":0,"time":54269.6641,"type":0},{"id":0,"sLen":0,"time":54606.7422,"type":0},{"id":3,"sLen":0,"time":54775.28,"type":0},{"id":2,"sLen":0,"time":54943.82,"type":0},{"id":1,"sLen":0,"time":55280.9,"type":0},{"id":0,"sLen":0,"time":55449.4375,"type":0},{"id":3,"sLen":0,"time":55617.9766,"type":0},{"id":2,"sLen":0,"time":55786.5156,"type":0},{"id":0,"sLen":0,"time":55955.0547,"type":0},{"id":3,"sLen":0,"time":56123.5938,"type":0},{"id":1,"sLen":0,"time":56292.1367,"type":0},{"id":0,"sLen":0,"time":56460.6758,"type":0},{"id":2,"sLen":0,"time":56629.2148,"type":0},{"id":3,"sLen":0,"time":56797.7539,"type":0},{"id":0,"sLen":0,"time":56966.293,"type":0},{"id":0,"sLen":0,"time":57303.37,"type":0},{"id":3,"sLen":0,"time":57471.91,"type":0},{"id":2,"sLen":0,"time":57640.45,"type":0},{"id":1,"sLen":0,"time":57977.5273,"type":0},{"id":0,"sLen":0,"time":58146.0664,"type":0},{"id":3,"sLen":0,"time":58314.6055,"type":0},{"id":2,"sLen":0,"time":58483.1445,"type":0},{"id":0,"sLen":0,"time":58651.6836,"type":0},{"id":3,"sLen":0,"time":58820.2266,"type":0},{"id":1,"sLen":84.2696599999908,"time":58988.7656,"type":0},{"id":0,"sLen":0,"time":59157.3047,"type":0},{"id":0,"sLen":168.539321999997,"time":59325.8438,"type":0},{"id":0,"sLen":168.539321999997,"time":60000,"type":0},{"id":0,"sLen":168.539322000004,"time":60674.1563,"type":0},{"id":0,"sLen":168.539321999997,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":62022.4727,"type":1},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":252.808989999998,"time":65056.18,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":252.808990000005,"time":65730.3359,"type":0},{"id":0,"sLen":0,"time":66067.4141,"type":0},{"id":0,"sLen":0,"time":66404.49,"type":0},{"id":0,"sLen":0,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":66910.11,"type":0},{"id":2,"sLen":0,"time":67078.65,"type":0},{"id":1,"sLen":0,"time":67415.73,"type":0},{"id":0,"sLen":0,"time":67584.27,"type":0},{"id":3,"sLen":0,"time":67752.81,"type":0},{"id":1,"sLen":0,"time":67921.35,"type":0},{"id":0,"sLen":0,"time":68089.89,"type":0},{"id":3,"sLen":0,"time":68258.43,"type":0},{"id":1,"sLen":0,"time":68426.97,"type":0},{"id":3,"sLen":0,"time":68595.51,"type":0},{"id":1,"sLen":0,"time":68764.05,"type":0},{"id":0,"sLen":0,"time":68932.5859,"type":0},{"id":3,"sLen":0,"time":69101.125,"type":0},{"id":2,"sLen":0,"time":69269.6641,"type":0},{"id":0,"sLen":0,"time":69438.2,"type":0},{"id":3,"sLen":0,"time":69606.74,"type":0},{"id":1,"sLen":0,"time":69775.28,"type":0},{"id":0,"sLen":0,"time":69943.82,"type":0},{"id":0,"sLen":0,"time":75505.62,"type":0},{"id":0,"sLen":0,"time":75674.16,"type":0},{"id":0,"sLen":0,"time":75842.6953,"type":0},{"id":2,"sLen":0,"time":76011.2344,"type":0},{"id":3,"sLen":0,"time":76179.77,"type":0},{"id":2,"sLen":0,"time":76348.31,"type":0},{"id":0,"sLen":0,"time":76516.85,"type":0},{"id":2,"sLen":0,"time":76685.39,"type":0},{"id":3,"sLen":0,"time":76853.93,"type":0},{"id":0,"sLen":0,"time":77022.47,"type":0},{"id":3,"sLen":0,"time":77191.01,"type":0},{"id":0,"sLen":0,"time":77359.55,"type":0},{"id":1,"sLen":252.808990000005,"time":77528.0859,"type":0},{"id":0,"sLen":0,"time":77865.17,"type":0},{"id":0,"sLen":0,"time":78033.71,"type":0},{"id":1,"sLen":0,"time":78202.25,"type":0},{"id":0,"sLen":0,"time":78370.79,"type":0},{"id":3,"sLen":0,"time":78539.33,"type":0},{"id":1,"sLen":0,"time":78876.41,"type":0},{"id":0,"sLen":0,"time":79044.9453,"type":0},{"id":2,"sLen":0,"time":79213.4844,"type":0},{"id":0,"sLen":0,"time":79382.02,"type":0},{"id":1,"sLen":0,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":79803.37,"type":0},{"id":1,"sLen":0,"time":80056.18,"type":0},{"id":0,"sLen":0,"time":80224.72,"type":0},{"id":3,"sLen":0,"time":80393.26,"type":0},{"id":0,"sLen":0,"time":80561.8,"type":0},{"id":2,"sLen":0,"time":80730.3359,"type":0},{"id":0,"sLen":0,"time":86292.13,"type":0},{"id":0,"sLen":0,"time":86460.67,"type":0},{"id":1,"sLen":0,"time":86629.21,"type":0},{"id":1,"sLen":0,"time":86797.75,"type":0},{"id":3,"sLen":0,"time":86966.29,"type":0},{"id":3,"sLen":0,"time":87134.83,"type":0},{"id":0,"sLen":0,"time":87303.37,"type":0},{"id":1,"sLen":0,"time":87471.91,"type":0},{"id":0,"sLen":0,"time":87640.45,"type":0},{"id":0,"sLen":0,"time":87808.99,"type":0},{"id":1,"sLen":0,"time":87977.53,"type":0},{"id":1,"sLen":0,"time":88146.07,"type":0},{"id":3,"sLen":0,"time":88314.61,"type":0},{"id":3,"sLen":0,"time":88483.15,"type":0},{"id":0,"sLen":0,"time":88651.69,"type":0},{"id":3,"sLen":0,"time":88820.23,"type":0},{"id":2,"sLen":0,"time":88988.7656,"type":0},{"id":0,"sLen":0,"time":89157.3047,"type":0},{"id":3,"sLen":0,"time":89325.84,"type":0},{"id":2,"sLen":0,"time":89494.38,"type":0},{"id":0,"sLen":0,"time":89662.92,"type":0},{"id":0,"sLen":0,"time":89831.46,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":1,"sLen":0,"time":90168.54,"type":0},{"id":0,"sLen":0,"time":90337.08,"type":0},{"id":1,"sLen":0,"time":90505.62,"type":0},{"id":3,"sLen":0,"time":90674.16,"type":0},{"id":1,"sLen":0,"time":90842.6953,"type":0},{"id":0,"sLen":0,"time":91011.2344,"type":0},{"id":1,"sLen":0,"time":91179.77,"type":0},{"id":3,"sLen":337.078643999994,"time":91348.31,"type":0},{"id":2,"sLen":0,"time":97078.65,"type":0},{"id":0,"sLen":0,"time":97247.19,"type":0},{"id":3,"sLen":0,"time":97415.73,"type":0},{"id":2,"sLen":0,"time":97584.27,"type":0},{"id":0,"sLen":0,"time":97752.81,"type":0},{"id":3,"sLen":0,"time":97921.35,"type":0},{"id":2,"sLen":0,"time":98089.89,"type":0},{"id":0,"sLen":0,"time":98258.43,"type":0},{"id":3,"sLen":0,"time":98426.97,"type":0},{"id":2,"sLen":0,"time":98595.51,"type":0},{"id":0,"sLen":0,"time":98764.05,"type":0},{"id":3,"sLen":0,"time":98932.5859,"type":0},{"id":1,"sLen":0,"time":99101.125,"type":0},{"id":3,"sLen":0,"time":99269.6641,"type":0},{"id":0,"sLen":0,"time":99438.2,"type":0},{"id":3,"sLen":0,"time":99606.74,"type":0},{"id":2,"sLen":0,"time":99775.28,"type":0},{"id":0,"sLen":0,"time":99943.82,"type":0},{"id":3,"sLen":0,"time":100112.359,"type":0},{"id":2,"sLen":0,"time":100280.9,"type":0},{"id":0,"sLen":0,"time":100449.438,"type":0},{"id":3,"sLen":0,"time":100617.977,"type":0},{"id":2,"sLen":0,"time":100786.516,"type":0},{"id":0,"sLen":0,"time":100955.055,"type":0},{"id":3,"sLen":0,"time":101123.594,"type":0},{"id":2,"sLen":0,"time":101292.133,"type":0},{"id":0,"sLen":0,"time":101460.672,"type":0},{"id":3,"sLen":0,"time":101629.211,"type":0},{"id":1,"sLen":0,"time":101797.75,"type":0},{"id":3,"sLen":0,"time":101966.289,"type":0},{"id":0,"sLen":0,"time":102134.828,"type":0},{"id":3,"sLen":0,"time":102303.367,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":0,"sLen":0,"time":102640.453,"type":0},{"id":3,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":0,"sLen":0,"time":103146.07,"type":0},{"id":3,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":0,"sLen":0,"time":103651.688,"type":0},{"id":3,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":0,"sLen":0,"time":104157.305,"type":0},{"id":3,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":3,"sLen":0,"time":104662.922,"type":0},{"id":0,"sLen":0,"time":104831.461,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":0,"sLen":0,"time":105337.078,"type":0},{"id":3,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":0,"sLen":0,"time":105842.695,"type":0},{"id":3,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":0,"sLen":0,"time":106348.313,"type":0},{"id":3,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":0,"sLen":0,"time":106853.93,"type":0},{"id":3,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":3,"sLen":0,"time":107359.547,"type":0},{"id":0,"sLen":0,"time":107528.086,"type":0},{"id":3,"sLen":0,"time":107696.633,"type":0},{"id":1,"sLen":0,"time":107865.172,"type":0},{"id":0,"sLen":0,"time":107949.438,"type":0},{"id":1,"sLen":0,"time":108033.711,"type":0},{"id":1,"sLen":0,"time":108202.25,"type":0},{"id":0,"sLen":0,"time":108370.789,"type":0},{"id":1,"sLen":0,"time":108539.328,"type":0},{"id":0,"sLen":0,"time":108623.594,"type":0},{"id":1,"sLen":0,"time":108707.867,"type":0},{"id":1,"sLen":0,"time":108876.406,"type":0},{"id":0,"sLen":0,"time":109044.945,"type":0},{"id":1,"sLen":0,"time":109213.484,"type":0},{"id":3,"sLen":0,"time":109297.75,"type":0},{"id":1,"sLen":0,"time":109382.023,"type":0},{"id":1,"sLen":0,"time":109550.563,"type":0},{"id":0,"sLen":0,"time":109719.1,"type":0},{"id":1,"sLen":0,"time":109887.641,"type":0},{"id":0,"sLen":0,"time":109971.906,"type":0},{"id":1,"sLen":0,"time":110056.18,"type":0},{"id":1,"sLen":0,"time":110224.719,"type":0},{"id":0,"sLen":0,"time":110393.258,"type":0},{"id":2,"sLen":0,"time":113258.43,"type":0},{"id":3,"sLen":0,"time":113426.969,"type":0},{"id":2,"sLen":0,"time":113595.508,"type":0},{"id":3,"sLen":0,"time":113764.047,"type":0},{"id":0,"sLen":0,"time":113932.586,"type":0},{"id":1,"sLen":0,"time":114101.125,"type":0},{"id":0,"sLen":0,"time":114269.664,"type":0},{"id":1,"sLen":0,"time":114438.2,"type":0},{"id":2,"sLen":0,"time":114606.742,"type":0},{"id":3,"sLen":0,"time":114775.281,"type":0},{"id":2,"sLen":0,"time":114943.82,"type":0},{"id":3,"sLen":0,"time":115112.359,"type":0},{"id":0,"sLen":0,"time":115280.9,"type":0},{"id":3,"sLen":0,"time":115449.438,"type":0},{"id":0,"sLen":0,"time":115617.977,"type":0},{"id":1,"sLen":0,"time":115786.516,"type":0},{"id":2,"sLen":589.887633999984,"time":115955.055,"type":0}],"position":"dad","characters":["tankman"],"visible":true,"type":0},{"notes":[{"id":2,"sLen":0,"time":5393.2583,"type":0},{"id":3,"sLen":0,"time":5561.798,"type":0},{"id":0,"sLen":0,"time":5730.337,"type":0},{"id":0,"sLen":0,"time":6067.41553,"type":0},{"id":3,"sLen":0,"time":6235.955,"type":0},{"id":2,"sLen":0,"time":6404.494,"type":0},{"id":1,"sLen":0,"time":6741.573,"type":0},{"id":0,"sLen":0,"time":6910.1123,"type":0},{"id":3,"sLen":0,"time":7078.652,"type":0},{"id":2,"sLen":0,"time":7247.191,"type":0},{"id":0,"sLen":0,"time":7415.73047,"type":0},{"id":3,"sLen":0,"time":7584.26953,"type":0},{"id":1,"sLen":0,"time":7752.809,"type":0},{"id":0,"sLen":0,"time":7921.348,"type":0},{"id":2,"sLen":0,"time":8089.8877,"type":0},{"id":3,"sLen":0,"time":8258.427,"type":0},{"id":0,"sLen":0,"time":8426.966,"type":0},{"id":0,"sLen":0,"time":8764.045,"type":0},{"id":3,"sLen":0,"time":8932.584,"type":0},{"id":2,"sLen":0,"time":9101.124,"type":0},{"id":1,"sLen":0,"time":9438.202,"type":0},{"id":0,"sLen":0,"time":9606.741,"type":0},{"id":3,"sLen":0,"time":9775.281,"type":0},{"id":2,"sLen":0,"time":9943.82,"type":0},{"id":0,"sLen":0,"time":10112.3594,"type":0},{"id":3,"sLen":0,"time":10280.8984,"type":0},{"id":1,"sLen":0,"time":10449.4385,"type":0},{"id":0,"sLen":0,"time":10617.9775,"type":0},{"id":0,"sLen":252.808990000001,"time":14831.4609,"type":0},{"id":1,"sLen":252.80899,"time":15168.5391,"type":0},{"id":3,"sLen":252.80899,"time":15505.6182,"type":0},{"id":1,"sLen":252.808990000001,"time":15842.6963,"type":0},{"id":3,"sLen":252.80899,"time":16179.7754,"type":0},{"id":0,"sLen":0,"time":16516.8535,"type":0},{"id":1,"sLen":0,"time":16685.3926,"type":0},{"id":3,"sLen":252.808990000001,"time":16853.9316,"type":0},{"id":1,"sLen":252.808990000001,"time":17191.0117,"type":0},{"id":0,"sLen":0,"time":17528.09,"type":0},{"id":0,"sLen":0,"time":17865.168,"type":0},{"id":0,"sLen":0,"time":18202.248,"type":0},{"id":0,"sLen":0,"time":18370.7871,"type":0},{"id":3,"sLen":0,"time":18539.3262,"type":0},{"id":1,"sLen":0,"time":18876.4043,"type":0},{"id":0,"sLen":0,"time":19044.9434,"type":0},{"id":3,"sLen":0,"time":19213.4824,"type":0},{"id":1,"sLen":0,"time":19382.0215,"type":0},{"id":0,"sLen":0,"time":19550.5625,"type":0},{"id":3,"sLen":0,"time":19719.1016,"type":0},{"id":1,"sLen":0,"time":19887.64,"type":0},{"id":0,"sLen":0,"time":20056.18,"type":0},{"id":1,"sLen":0,"time":20224.7188,"type":0},{"id":0,"sLen":0,"time":20393.2578,"type":0},{"id":3,"sLen":0,"time":20561.7969,"type":0},{"id":1,"sLen":0,"time":20730.3379,"type":0},{"id":0,"sLen":0,"time":20898.877,"type":0},{"id":3,"sLen":0,"time":21067.416,"type":0},{"id":1,"sLen":0,"time":21235.9551,"type":0},{"id":0,"sLen":0,"time":21404.4941,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":3,"sLen":0,"time":21910.1133,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":3,"sLen":0,"time":22584.27,"type":0},{"id":1,"sLen":0,"time":22921.3477,"type":0},{"id":1,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":252.808990000001,"time":23595.5059,"type":0},{"id":0,"sLen":252.808990000009,"time":23932.584,"type":0},{"id":3,"sLen":0,"time":24269.6621,"type":0},{"id":2,"sLen":0,"time":24438.2031,"type":0},{"id":0,"sLen":0,"time":24606.7422,"type":0},{"id":2,"sLen":0,"time":24775.2813,"type":0},{"id":3,"sLen":0,"time":24943.82,"type":0},{"id":2,"sLen":0,"time":25112.36,"type":0},{"id":0,"sLen":0,"time":25280.8984,"type":0},{"id":2,"sLen":0,"time":25449.4375,"type":0},{"id":3,"sLen":0,"time":25617.9766,"type":0},{"id":2,"sLen":0,"time":25786.5176,"type":0},{"id":3,"sLen":0,"time":25955.0566,"type":0},{"id":2,"sLen":0,"time":26123.5957,"type":0},{"id":3,"sLen":0,"time":26292.1348,"type":0},{"id":0,"sLen":0,"time":26460.6738,"type":0},{"id":1,"sLen":0,"time":26629.2129,"type":0},{"id":0,"sLen":0,"time":26797.752,"type":0},{"id":2,"sLen":589.887634000006,"time":26966.293,"type":0},{"id":3,"sLen":589.887633999995,"time":27640.45,"type":0},{"id":1,"sLen":589.887633999999,"time":28314.6074,"type":0},{"id":0,"sLen":589.887634000006,"time":28988.7637,"type":0},{"id":3,"sLen":0,"time":29662.9219,"type":0},{"id":2,"sLen":0,"time":29831.46,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":2,"sLen":0,"time":30168.54,"type":0},{"id":3,"sLen":0,"time":30337.0781,"type":0},{"id":2,"sLen":0,"time":30505.6172,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":2,"sLen":0,"time":30842.6973,"type":0},{"id":3,"sLen":0,"time":31011.2363,"type":0},{"id":2,"sLen":0,"time":31179.7754,"type":0},{"id":3,"sLen":0,"time":31348.3145,"type":0},{"id":2,"sLen":0,"time":31516.8535,"type":0},{"id":3,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":1,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":1,"sLen":674.157300000003,"time":32359.55,"type":0},{"id":0,"sLen":0,"time":37752.81,"type":0},{"id":2,"sLen":0,"time":38089.8867,"type":0},{"id":3,"sLen":0,"time":38258.4258,"type":0},{"id":0,"sLen":0,"time":38426.9648,"type":0},{"id":2,"sLen":0,"time":38764.043,"type":0},{"id":3,"sLen":0,"time":38932.5859,"type":0},{"id":1,"sLen":0,"time":39101.125,"type":0},{"id":3,"sLen":0,"time":39269.6641,"type":0},{"id":1,"sLen":0,"time":39438.2031,"type":0},{"id":0,"sLen":0,"time":39606.7422,"type":0},{"id":1,"sLen":0,"time":39775.28,"type":0},{"id":3,"sLen":0,"time":39943.82,"type":0},{"id":1,"sLen":0,"time":40112.36,"type":0},{"id":0,"sLen":0,"time":40280.9,"type":0},{"id":0,"sLen":0,"time":40449.4375,"type":0},{"id":2,"sLen":0,"time":40786.5156,"type":0},{"id":3,"sLen":0,"time":40955.0547,"type":0},{"id":0,"sLen":0,"time":41123.5938,"type":0},{"id":2,"sLen":0,"time":41460.6758,"type":0},{"id":3,"sLen":0,"time":41629.2148,"type":0},{"id":3,"sLen":0,"time":41797.7539,"type":0},{"id":1,"sLen":0,"time":41966.293,"type":0},{"id":0,"sLen":0,"time":42134.832,"type":0},{"id":1,"sLen":0,"time":42303.37,"type":0},{"id":0,"sLen":0,"time":42471.91,"type":0},{"id":1,"sLen":0,"time":42640.45,"type":0},{"id":1,"sLen":0,"time":42808.99,"type":0},{"id":0,"sLen":0,"time":42977.5273,"type":0},{"id":3,"sLen":0,"time":48539.3242,"type":0},{"id":1,"sLen":0,"time":48707.8633,"type":0},{"id":0,"sLen":0,"time":48876.4063,"type":0},{"id":3,"sLen":0,"time":49044.9453,"type":0},{"id":2,"sLen":0,"time":49213.4844,"type":0},{"id":0,"sLen":0,"time":49550.5625,"type":0},{"id":3,"sLen":0,"time":49719.1,"type":0},{"id":2,"sLen":0,"time":49887.64,"type":0},{"id":3,"sLen":0,"time":50056.18,"type":0},{"id":0,"sLen":0,"time":50224.72,"type":0},{"id":2,"sLen":0,"time":50393.2578,"type":0},{"id":3,"sLen":0,"time":50561.7969,"type":0},{"id":2,"sLen":0,"time":50730.3359,"type":0},{"id":3,"sLen":0,"time":50898.875,"type":0},{"id":1,"sLen":0,"time":51067.4141,"type":0},{"id":0,"sLen":0,"time":51235.9531,"type":0},{"id":3,"sLen":0,"time":51404.4961,"type":0},{"id":1,"sLen":0,"time":51573.0352,"type":0},{"id":3,"sLen":0,"time":51741.5742,"type":0},{"id":0,"sLen":0,"time":51910.1133,"type":0},{"id":3,"sLen":0,"time":52078.6523,"type":0},{"id":1,"sLen":0,"time":52247.19,"type":0},{"id":3,"sLen":0,"time":52415.73,"type":0},{"id":2,"sLen":0,"time":52584.27,"type":0},{"id":1,"sLen":0,"time":52921.3477,"type":0},{"id":1,"sLen":0,"time":53258.4258,"type":0},{"id":1,"sLen":0,"time":53595.5039,"type":0},{"id":1,"sLen":1011.23595999999,"time":53932.5859,"type":0},{"id":2,"sLen":505.617981000003,"time":55955.0547,"type":0},{"id":1,"sLen":674.157299999999,"time":56629.2148,"type":0},{"id":0,"sLen":252.808989999998,"time":58651.6836,"type":0},{"id":2,"sLen":84.2696599999908,"time":58988.7656,"type":0},{"id":0,"sLen":0,"time":59157.3047,"type":0},{"id":2,"sLen":0,"time":59325.8438,"type":0},{"id":3,"sLen":0,"time":59494.3828,"type":0},{"id":0,"sLen":0,"time":59662.92,"type":0},{"id":0,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60168.54,"type":0},{"id":2,"sLen":0,"time":60337.08,"type":0},{"id":1,"sLen":0,"time":60674.1563,"type":0},{"id":0,"sLen":0,"time":60842.6953,"type":0},{"id":3,"sLen":0,"time":61011.2344,"type":0},{"id":2,"sLen":0,"time":61179.7734,"type":0},{"id":0,"sLen":0,"time":61348.3164,"type":0},{"id":3,"sLen":0,"time":61516.8555,"type":0},{"id":1,"sLen":0,"time":61685.3945,"type":0},{"id":0,"sLen":0,"time":61853.9336,"type":0},{"id":2,"sLen":0,"time":62022.4727,"type":0},{"id":3,"sLen":0,"time":62191.01,"type":0},{"id":0,"sLen":0,"time":62359.55,"type":0},{"id":0,"sLen":0,"time":62696.63,"type":0},{"id":3,"sLen":0,"time":62865.168,"type":0},{"id":2,"sLen":0,"time":63033.707,"type":0},{"id":1,"sLen":0,"time":63370.7852,"type":0},{"id":0,"sLen":0,"time":63539.3242,"type":0},{"id":3,"sLen":0,"time":63707.8633,"type":0},{"id":2,"sLen":0,"time":63876.4063,"type":0},{"id":0,"sLen":0,"time":64044.9453,"type":0},{"id":3,"sLen":0,"time":64213.4844,"type":0},{"id":1,"sLen":0,"time":64382.0234,"type":0},{"id":0,"sLen":0,"time":64550.5625,"type":0},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":252.808989999998,"time":65056.18,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":252.808990000005,"time":65730.3359,"type":0},{"id":3,"sLen":505.617981000003,"time":66067.4141,"type":0},{"id":1,"sLen":505.617981000003,"time":66741.57,"type":0},{"id":0,"sLen":505.617981000003,"time":67415.73,"type":0},{"id":1,"sLen":252.808990000005,"time":68089.89,"type":0},{"id":0,"sLen":252.808990000005,"time":68426.97,"type":0},{"id":3,"sLen":589.887633999999,"time":68764.05,"type":0},{"id":0,"sLen":589.887633999999,"time":69438.2,"type":0},{"id":1,"sLen":252.808990000005,"time":70112.36,"type":0},{"id":0,"sLen":252.808990000005,"time":70449.44,"type":0},{"id":3,"sLen":252.808990000005,"time":70786.5156,"type":0},{"id":1,"sLen":252.808990000005,"time":71123.59,"type":0},{"id":0,"sLen":0,"time":71460.67,"type":0},{"id":0,"sLen":0,"time":71797.75,"type":0},{"id":0,"sLen":0,"time":72134.83,"type":0},{"id":0,"sLen":0,"time":72303.37,"type":0},{"id":2,"sLen":0,"time":72471.91,"type":0},{"id":1,"sLen":0,"time":72808.99,"type":0},{"id":0,"sLen":0,"time":72977.53,"type":0},{"id":3,"sLen":0,"time":73146.07,"type":0},{"id":1,"sLen":0,"time":73314.61,"type":0},{"id":0,"sLen":0,"time":73483.15,"type":0},{"id":3,"sLen":0,"time":73651.69,"type":0},{"id":1,"sLen":0,"time":73820.23,"type":0},{"id":3,"sLen":0,"time":73988.7656,"type":0},{"id":1,"sLen":0,"time":74157.3047,"type":0},{"id":0,"sLen":0,"time":74325.84,"type":0},{"id":3,"sLen":0,"time":74494.38,"type":0},{"id":2,"sLen":0,"time":74662.92,"type":0},{"id":0,"sLen":0,"time":74831.46,"type":0},{"id":3,"sLen":0,"time":75000,"type":0},{"id":1,"sLen":0,"time":75168.54,"type":0},{"id":0,"sLen":0,"time":75337.08,"type":0},{"id":1,"sLen":1264.04492,"time":75505.62,"type":0},{"id":0,"sLen":1264.04492,"time":76853.93,"type":0},{"id":3,"sLen":1264.04492,"time":78202.25,"type":0},{"id":0,"sLen":1264.04492,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":80898.875,"type":0},{"id":0,"sLen":0,"time":81067.4141,"type":0},{"id":0,"sLen":0,"time":81235.95,"type":0},{"id":2,"sLen":0,"time":81404.49,"type":0},{"id":3,"sLen":0,"time":81573.03,"type":0},{"id":2,"sLen":0,"time":81741.57,"type":0},{"id":0,"sLen":0,"time":81910.11,"type":0},{"id":2,"sLen":0,"time":82078.65,"type":0},{"id":3,"sLen":0,"time":82247.19,"type":0},{"id":0,"sLen":0,"time":82415.73,"type":0},{"id":3,"sLen":0,"time":82584.27,"type":0},{"id":0,"sLen":0,"time":82752.81,"type":0},{"id":1,"sLen":252.808990000005,"time":82921.35,"type":0},{"id":0,"sLen":0,"time":83258.43,"type":0},{"id":0,"sLen":0,"time":83426.97,"type":0},{"id":1,"sLen":0,"time":83595.51,"type":0},{"id":0,"sLen":0,"time":83764.05,"type":0},{"id":3,"sLen":0,"time":83932.5859,"type":0},{"id":1,"sLen":0,"time":84269.6641,"type":0},{"id":0,"sLen":0,"time":84438.2,"type":0},{"id":2,"sLen":0,"time":84606.74,"type":0},{"id":0,"sLen":0,"time":84775.28,"type":0},{"id":1,"sLen":0,"time":84943.82,"type":0},{"id":0,"sLen":0,"time":85196.63,"type":0},{"id":1,"sLen":0,"time":85449.44,"type":0},{"id":0,"sLen":0,"time":85617.98,"type":0},{"id":3,"sLen":0,"time":85786.5156,"type":0},{"id":0,"sLen":0,"time":85955.0547,"type":0},{"id":2,"sLen":0,"time":86123.59,"type":0},{"id":0,"sLen":0,"time":91685.39,"type":0},{"id":0,"sLen":0,"time":91853.93,"type":0},{"id":1,"sLen":0,"time":92022.47,"type":0},{"id":1,"sLen":0,"time":92191.01,"type":0},{"id":3,"sLen":0,"time":92359.55,"type":0},{"id":3,"sLen":0,"time":92528.0859,"type":0},{"id":0,"sLen":0,"time":92696.63,"type":0},{"id":1,"sLen":0,"time":92865.17,"type":0},{"id":0,"sLen":0,"time":93033.71,"type":0},{"id":0,"sLen":0,"time":93202.25,"type":0},{"id":1,"sLen":0,"time":93370.79,"type":0},{"id":1,"sLen":0,"time":93539.33,"type":0},{"id":3,"sLen":0,"time":93707.87,"type":0},{"id":3,"sLen":0,"time":93876.41,"type":0},{"id":0,"sLen":0,"time":94044.9453,"type":0},{"id":3,"sLen":0,"time":94213.4844,"type":0},{"id":2,"sLen":0,"time":94382.02,"type":0},{"id":0,"sLen":0,"time":94550.56,"type":0},{"id":3,"sLen":0,"time":94719.1,"type":0},{"id":2,"sLen":0,"time":94887.64,"type":0},{"id":0,"sLen":0,"time":95056.18,"type":0},{"id":0,"sLen":0,"time":95224.72,"type":0},{"id":3,"sLen":0,"time":95393.26,"type":0},{"id":1,"sLen":0,"time":95561.8,"type":0},{"id":0,"sLen":0,"time":95730.3359,"type":0},{"id":1,"sLen":0,"time":95898.875,"type":0},{"id":3,"sLen":0,"time":96067.4141,"type":0},{"id":1,"sLen":0,"time":96235.95,"type":0},{"id":0,"sLen":0,"time":96404.49,"type":0},{"id":1,"sLen":0,"time":96573.03,"type":0},{"id":3,"sLen":337.078643999994,"time":96741.57,"type":0},{"id":0,"sLen":589.88763399997,"time":98426.97,"type":0},{"id":2,"sLen":589.887633999999,"time":99101.125,"type":0},{"id":0,"sLen":589.887633999999,"time":101123.594,"type":0},{"id":2,"sLen":589.887633999984,"time":101797.75,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":3,"sLen":0,"time":102640.453,"type":0},{"id":0,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":3,"sLen":0,"time":103146.07,"type":0},{"id":0,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":3,"sLen":0,"time":103651.688,"type":0},{"id":0,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":3,"sLen":0,"time":104157.305,"type":0},{"id":0,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":0,"sLen":0,"time":104662.922,"type":0},{"id":3,"sLen":0,"time":104831.461,"type":0},{"id":0,"sLen":0,"time":105000,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":3,"sLen":0,"time":105337.078,"type":0},{"id":0,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":3,"sLen":0,"time":105842.695,"type":0},{"id":0,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":3,"sLen":0,"time":106348.313,"type":0},{"id":0,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":3,"sLen":0,"time":106853.93,"type":0},{"id":0,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":0,"sLen":0,"time":107359.547,"type":0},{"id":3,"sLen":0,"time":107528.086,"type":0},{"id":0,"sLen":0,"time":107696.633,"type":0},{"id":3,"sLen":589.887634000013,"time":107865.172,"type":0},{"id":1,"sLen":589.887634000013,"time":108539.328,"type":0},{"id":0,"sLen":589.887633999999,"time":109213.484,"type":0},{"id":1,"sLen":589.887633999999,"time":109887.641,"type":0},{"id":1,"sLen":0,"time":110561.8,"type":0},{"id":0,"sLen":0,"time":110646.07,"type":0},{"id":1,"sLen":0,"time":110730.336,"type":0},{"id":1,"sLen":0,"time":110898.875,"type":0},{"id":0,"sLen":0,"time":111067.414,"type":0},{"id":1,"sLen":0,"time":111235.953,"type":0},{"id":0,"sLen":0,"time":111320.227,"type":0},{"id":1,"sLen":0,"time":111404.492,"type":0},{"id":1,"sLen":0,"time":111573.031,"type":0},{"id":0,"sLen":0,"time":111741.57,"type":0},{"id":1,"sLen":0,"time":111910.109,"type":0},{"id":3,"sLen":0,"time":111994.383,"type":0},{"id":1,"sLen":0,"time":112078.648,"type":0},{"id":1,"sLen":0,"time":112247.188,"type":0},{"id":0,"sLen":0,"time":112415.727,"type":0},{"id":1,"sLen":0,"time":112584.273,"type":0},{"id":0,"sLen":0,"time":112668.539,"type":0},{"id":1,"sLen":0,"time":112752.813,"type":0},{"id":1,"sLen":0,"time":112921.352,"type":0},{"id":0,"sLen":0,"time":113089.891,"type":0},{"id":3,"sLen":589.887633999999,"time":113258.43,"type":0},{"id":1,"sLen":589.887633999999,"time":113932.586,"type":0},{"id":0,"sLen":589.887633999984,"time":114606.742,"type":0},{"id":1,"sLen":589.887633999999,"time":115280.9,"type":0},{"id":2,"sLen":0,"time":115955.055,"type":0},{"id":3,"sLen":0,"time":116123.594,"type":0},{"id":2,"sLen":0,"time":116292.133,"type":0},{"id":3,"sLen":0,"time":116460.672,"type":0},{"id":0,"sLen":0,"time":116629.211,"type":0},{"id":1,"sLen":0,"time":116797.75,"type":0},{"id":0,"sLen":0,"time":116966.289,"type":0},{"id":1,"sLen":0,"time":117134.828,"type":0},{"id":2,"sLen":0,"time":117303.367,"type":0},{"id":3,"sLen":0,"time":117471.906,"type":0},{"id":2,"sLen":0,"time":117640.453,"type":0},{"id":3,"sLen":0,"time":117808.992,"type":0},{"id":0,"sLen":0,"time":117977.531,"type":0},{"id":3,"sLen":0,"time":118146.07,"type":0},{"id":0,"sLen":0,"time":118314.609,"type":0},{"id":1,"sLen":0,"time":118483.148,"type":0}],"position":"boyfriend","characters":["bf-holding-gf"],"visible":true,"type":1},{"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539325842697,"type":0},{"id":3,"sLen":0,"time":252.808988764045,"type":0},{"id":3,"sLen":0,"time":421.348314606742,"type":0},{"id":0,"sLen":0,"time":589.887640449438,"type":0},{"id":3,"sLen":0,"time":758.426966292135,"type":0},{"id":3,"sLen":0,"time":842.696629213483,"type":0},{"id":0,"sLen":0,"time":1011.23595505618,"type":0},{"id":0,"sLen":0,"time":1179.77528089888,"type":0},{"id":0,"sLen":0,"time":1264.04494382022,"type":0},{"id":3,"sLen":0,"time":1348.31460674157,"type":0},{"id":0,"sLen":0,"time":1516.85393258427,"type":0},{"id":0,"sLen":0,"time":1601.12359550562,"type":0},{"id":3,"sLen":0,"time":1853.93258426966,"type":0},{"id":0,"sLen":0,"time":2022.47191011236,"type":0},{"id":3,"sLen":0,"time":2106.74157303371,"type":0},{"id":3,"sLen":0,"time":2191.01123595506,"type":0},{"id":0,"sLen":0,"time":2359.55056179775,"type":0},{"id":0,"sLen":0,"time":2696.62921348315,"type":0},{"id":3,"sLen":0,"time":2865.16853932584,"type":0},{"id":3,"sLen":0,"time":2949.43820224719,"type":0},{"id":3,"sLen":0,"time":3117.97752808989,"type":0},{"id":0,"sLen":0,"time":3286.51685393258,"type":0},{"id":3,"sLen":0,"time":3455.05617977528,"type":0},{"id":3,"sLen":0,"time":3539.32584269663,"type":0},{"id":0,"sLen":0,"time":3707.86516853933,"type":0},{"id":0,"sLen":0,"time":3876.40449438202,"type":0},{"id":0,"sLen":0,"time":3960.67415730337,"type":0},{"id":3,"sLen":0,"time":4044.94382022472,"type":0},{"id":0,"sLen":0,"time":4213.48314606742,"type":0},{"id":0,"sLen":0,"time":4297.75280898876,"type":0},{"id":3,"sLen":0,"time":4550.56179775281,"type":0},{"id":0,"sLen":0,"time":4719.10112359551,"type":0},{"id":3,"sLen":0,"time":4803.37078651685,"type":0},{"id":3,"sLen":0,"time":4887.6404494382,"type":0},{"id":0,"sLen":0,"time":5056.1797752809,"type":0},{"id":0,"sLen":0,"time":5140.44943820225,"type":0},{"id":0,"sLen":0,"time":5224.7191011236,"type":0},{"id":0,"sLen":0,"time":5393.25842696629,"type":0},{"id":3,"sLen":0,"time":5561.79775280899,"type":0},{"id":3,"sLen":0,"time":5646.06741573034,"type":0},{"id":3,"sLen":0,"time":5814.60674157303,"type":0},{"id":0,"sLen":0,"time":5983.14606741573,"type":0},{"id":3,"sLen":0,"time":6151.68539325843,"type":0},{"id":3,"sLen":0,"time":6235.95505617978,"type":0},{"id":0,"sLen":0,"time":6404.49438202247,"type":0},{"id":0,"sLen":0,"time":6573.03370786517,"type":0},{"id":0,"sLen":0,"time":6657.30337078652,"type":0},{"id":3,"sLen":0,"time":6741.57303370786,"type":0},{"id":0,"sLen":0,"time":6910.11235955056,"type":0},{"id":0,"sLen":0,"time":6994.38202247191,"type":0},{"id":3,"sLen":0,"time":7247.19101123595,"type":0},{"id":0,"sLen":0,"time":7415.73033707865,"type":0},{"id":3,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7584.26966292135,"type":0},{"id":0,"sLen":0,"time":7752.80898876405,"type":0},{"id":0,"sLen":0,"time":8089.88764044944,"type":0},{"id":3,"sLen":0,"time":8258.42696629213,"type":0},{"id":3,"sLen":0,"time":8342.69662921348,"type":0},{"id":3,"sLen":0,"time":8511.23595505618,"type":0},{"id":0,"sLen":0,"time":8679.77528089888,"type":0},{"id":3,"sLen":0,"time":8848.31460674157,"type":0},{"id":3,"sLen":0,"time":8932.58426966292,"type":0},{"id":0,"sLen":0,"time":9101.12359550562,"type":0},{"id":0,"sLen":0,"time":9269.66292134831,"type":0},{"id":0,"sLen":0,"time":9353.93258426966,"type":0},{"id":3,"sLen":0,"time":9438.20224719101,"type":0},{"id":0,"sLen":0,"time":9606.74157303371,"type":0},{"id":0,"sLen":0,"time":9691.01123595505,"type":0},{"id":3,"sLen":0,"time":9943.8202247191,"type":0},{"id":0,"sLen":0,"time":10112.3595505618,"type":0},{"id":3,"sLen":0,"time":10196.6292134831,"type":0},{"id":3,"sLen":0,"time":10280.8988764045,"type":0},{"id":0,"sLen":0,"time":10449.4382022472,"type":0},{"id":0,"sLen":0,"time":21235.9550561798,"type":0},{"id":3,"sLen":0,"time":21320.2247191011,"type":0},{"id":0,"sLen":0,"time":21404.4943820225,"type":0},{"id":0,"sLen":0,"time":21573.0337078652,"type":0},{"id":3,"sLen":0,"time":21910.1123595506,"type":0},{"id":0,"sLen":0,"time":22247.191011236,"type":0},{"id":3,"sLen":0,"time":22584.2696629213,"type":0},{"id":0,"sLen":0,"time":22921.3483146067,"type":0},{"id":0,"sLen":0,"time":23258.4269662921,"type":0},{"id":3,"sLen":0,"time":23595.5056179775,"type":0},{"id":3,"sLen":0,"time":23932.5842696629,"type":0},{"id":0,"sLen":0,"time":24269.6629213483,"type":0},{"id":3,"sLen":0,"time":24606.7415730337,"type":0},{"id":0,"sLen":0,"time":24943.8202247191,"type":0},{"id":3,"sLen":0,"time":25280.8988764045,"type":0},{"id":0,"sLen":0,"time":25617.9775280899,"type":0},{"id":0,"sLen":0,"time":25955.0561797753,"type":0},{"id":3,"sLen":0,"time":26292.1348314607,"type":0},{"id":3,"sLen":0,"time":26629.2134831461,"type":0},{"id":3,"sLen":0,"time":26713.4831460674,"type":0},{"id":3,"sLen":0,"time":26797.7528089888,"type":0},{"id":3,"sLen":0,"time":26966.2921348315,"type":0},{"id":0,"sLen":0,"time":27303.3707865169,"type":0},{"id":0,"sLen":0,"time":27640.4494382022,"type":0},{"id":3,"sLen":0,"time":27977.5280898876,"type":0},{"id":3,"sLen":0,"time":28314.606741573,"type":0},{"id":0,"sLen":0,"time":28651.6853932584,"type":0},{"id":3,"sLen":0,"time":28988.7640449438,"type":0},{"id":0,"sLen":0,"time":29325.8426966292,"type":0},{"id":0,"sLen":0,"time":29662.9213483146,"type":0},{"id":0,"sLen":0,"time":29831.4606741573,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30168.5393258427,"type":0},{"id":3,"sLen":0,"time":30337.0786516854,"type":0},{"id":3,"sLen":0,"time":30505.6179775281,"type":0},{"id":3,"sLen":0,"time":30674.1573033708,"type":0},{"id":0,"sLen":0,"time":30842.6966292135,"type":0},{"id":0,"sLen":0,"time":31011.2359550562,"type":0},{"id":3,"sLen":0,"time":31348.3146067416,"type":0},{"id":3,"sLen":0,"time":31685.393258427,"type":0},{"id":0,"sLen":0,"time":31853.9325842697,"type":0},{"id":0,"sLen":0,"time":32359.5505617978,"type":0},{"id":3,"sLen":0,"time":32696.6292134831,"type":0},{"id":0,"sLen":0,"time":33033.7078651685,"type":0},{"id":0,"sLen":0,"time":33202.2471910112,"type":0},{"id":3,"sLen":0,"time":33370.7865168539,"type":0},{"id":0,"sLen":0,"time":33707.8651685393,"type":0},{"id":3,"sLen":0,"time":34044.9438202247,"type":0},{"id":3,"sLen":0,"time":34382.0224719101,"type":0},{"id":0,"sLen":0,"time":34550.5617977528,"type":0},{"id":3,"sLen":0,"time":34719.1011235955,"type":0},{"id":0,"sLen":0,"time":35056.1797752809,"type":0},{"id":3,"sLen":0,"time":35393.2584269663,"type":0},{"id":0,"sLen":0,"time":35730.3370786517,"type":0},{"id":0,"sLen":0,"time":35898.8764044944,"type":0},{"id":3,"sLen":0,"time":36067.4157303371,"type":0},{"id":0,"sLen":0,"time":36404.4943820225,"type":0},{"id":3,"sLen":0,"time":36741.5730337079,"type":0},{"id":3,"sLen":0,"time":37078.6516853933,"type":0},{"id":0,"sLen":0,"time":37247.191011236,"type":0},{"id":3,"sLen":0,"time":37415.7303370787,"type":0},{"id":0,"sLen":0,"time":37752.808988764,"type":0},{"id":3,"sLen":0,"time":38089.8876404494,"type":0},{"id":3,"sLen":0,"time":38426.9662921348,"type":0},{"id":0,"sLen":0,"time":38595.5056179775,"type":0},{"id":3,"sLen":0,"time":38764.0449438202,"type":0},{"id":0,"sLen":0,"time":39101.1235955056,"type":0},{"id":3,"sLen":0,"time":39438.202247191,"type":0},{"id":3,"sLen":0,"time":39775.2808988764,"type":0},{"id":0,"sLen":0,"time":39943.8202247191,"type":0},{"id":3,"sLen":0,"time":40112.3595505618,"type":0},{"id":0,"sLen":0,"time":40449.4382022472,"type":0},{"id":3,"sLen":0,"time":40786.5168539326,"type":0},{"id":3,"sLen":0,"time":41123.595505618,"type":0},{"id":0,"sLen":0,"time":41292.1348314607,"type":0},{"id":3,"sLen":0,"time":41460.6741573034,"type":0},{"id":0,"sLen":0,"time":41797.7528089888,"type":0},{"id":0,"sLen":0,"time":42134.8314606742,"type":0},{"id":0,"sLen":0,"time":42471.9101123596,"type":0},{"id":0,"sLen":0,"time":42640.4494382022,"type":0},{"id":3,"sLen":0,"time":42808.9887640449,"type":0},{"id":3,"sLen":0,"time":42893.2584269663,"type":0},{"id":3,"sLen":0,"time":42977.5280898876,"type":0},{"id":0,"sLen":0,"time":43146.0674157303,"type":0},{"id":3,"sLen":0,"time":43483.1460674157,"type":0},{"id":3,"sLen":0,"time":43820.2247191011,"type":0},{"id":0,"sLen":0,"time":43988.7640449438,"type":0},{"id":3,"sLen":0,"time":44157.3033707865,"type":0},{"id":0,"sLen":0,"time":44494.3820224719,"type":0},{"id":3,"sLen":0,"time":44831.4606741573,"type":0},{"id":0,"sLen":0,"time":45168.5393258427,"type":0},{"id":0,"sLen":0,"time":45337.0786516854,"type":0},{"id":3,"sLen":0,"time":45505.6179775281,"type":0},{"id":0,"sLen":0,"time":45842.6966292135,"type":0},{"id":3,"sLen":0,"time":46179.7752808989,"type":0},{"id":3,"sLen":0,"time":46516.8539325843,"type":0},{"id":0,"sLen":0,"time":46685.393258427,"type":0},{"id":3,"sLen":0,"time":46853.9325842697,"type":0},{"id":0,"sLen":0,"time":47191.0112359551,"type":0},{"id":3,"sLen":0,"time":47528.0898876404,"type":0},{"id":3,"sLen":0,"time":47865.1685393258,"type":0},{"id":0,"sLen":0,"time":48033.7078651685,"type":0},{"id":3,"sLen":0,"time":48202.2471910112,"type":0},{"id":0,"sLen":0,"time":48539.3258426966,"type":0},{"id":3,"sLen":0,"time":48876.404494382,"type":0},{"id":3,"sLen":0,"time":49213.4831460674,"type":0},{"id":0,"sLen":0,"time":49382.0224719101,"type":0},{"id":3,"sLen":0,"time":49550.5617977528,"type":0},{"id":0,"sLen":0,"time":49887.6404494382,"type":0},{"id":3,"sLen":0,"time":50224.7191011236,"type":0},{"id":0,"sLen":0,"time":50561.797752809,"type":0},{"id":0,"sLen":0,"time":50730.3370786517,"type":0},{"id":3,"sLen":0,"time":50898.8764044944,"type":0},{"id":0,"sLen":0,"time":51235.9550561798,"type":0},{"id":3,"sLen":0,"time":51573.0337078652,"type":0},{"id":3,"sLen":0,"time":51910.1123595506,"type":0},{"id":0,"sLen":0,"time":52078.6516853933,"type":0},{"id":3,"sLen":0,"time":52247.191011236,"type":0},{"id":0,"sLen":0,"time":52584.2696629213,"type":0},{"id":0,"sLen":0,"time":52921.3483146067,"type":0},{"id":0,"sLen":0,"time":53258.4269662921,"type":0},{"id":0,"sLen":0,"time":53426.9662921348,"type":0},{"id":3,"sLen":0,"time":53595.5056179775,"type":0},{"id":3,"sLen":0,"time":53679.7752808989,"type":0},{"id":3,"sLen":0,"time":53764.0449438202,"type":0},{"id":0,"sLen":0,"time":53932.5842696629,"type":0},{"id":3,"sLen":0,"time":54101.1235955056,"type":0},{"id":3,"sLen":0,"time":54185.393258427,"type":0},{"id":3,"sLen":0,"time":54353.9325842697,"type":0},{"id":0,"sLen":0,"time":54522.4719101124,"type":0},{"id":3,"sLen":0,"time":54691.0112359551,"type":0},{"id":3,"sLen":0,"time":54775.2808988764,"type":0},{"id":0,"sLen":0,"time":54943.8202247191,"type":0},{"id":0,"sLen":0,"time":55112.3595505618,"type":0},{"id":0,"sLen":0,"time":55196.6292134831,"type":0},{"id":3,"sLen":0,"time":55280.8988764045,"type":0},{"id":0,"sLen":0,"time":55449.4382022472,"type":0},{"id":0,"sLen":0,"time":55533.7078651685,"type":0},{"id":3,"sLen":0,"time":55786.5168539326,"type":0},{"id":0,"sLen":0,"time":55955.0561797753,"type":0},{"id":3,"sLen":0,"time":56039.3258426966,"type":0},{"id":3,"sLen":0,"time":56123.595505618,"type":0},{"id":0,"sLen":0,"time":56292.1348314607,"type":0},{"id":0,"sLen":0,"time":56629.2134831461,"type":0},{"id":3,"sLen":0,"time":56797.7528089888,"type":0},{"id":3,"sLen":0,"time":56882.0224719101,"type":0},{"id":3,"sLen":0,"time":57050.5617977528,"type":0},{"id":0,"sLen":0,"time":57219.1011235955,"type":0},{"id":3,"sLen":0,"time":57387.6404494382,"type":0},{"id":3,"sLen":0,"time":57471.9101123595,"type":0},{"id":0,"sLen":0,"time":57640.4494382022,"type":0},{"id":0,"sLen":0,"time":57808.9887640449,"type":0},{"id":0,"sLen":0,"time":57893.2584269663,"type":0},{"id":3,"sLen":0,"time":57977.5280898876,"type":0},{"id":0,"sLen":0,"time":58146.0674157303,"type":0},{"id":0,"sLen":0,"time":58230.3370786517,"type":0},{"id":3,"sLen":0,"time":58483.1460674157,"type":0},{"id":0,"sLen":0,"time":58651.6853932584,"type":0},{"id":3,"sLen":0,"time":58735.9550561798,"type":0},{"id":3,"sLen":0,"time":58820.2247191011,"type":0},{"id":0,"sLen":0,"time":58988.7640449438,"type":0},{"id":0,"sLen":0,"time":59073.0337078652,"type":0},{"id":0,"sLen":0,"time":59157.3033707865,"type":0},{"id":0,"sLen":0,"time":59325.8426966292,"type":0},{"id":3,"sLen":0,"time":59494.3820224719,"type":0},{"id":3,"sLen":0,"time":59578.6516853933,"type":0},{"id":3,"sLen":0,"time":59747.191011236,"type":0},{"id":0,"sLen":0,"time":59915.7303370786,"type":0},{"id":3,"sLen":0,"time":60084.2696629213,"type":0},{"id":3,"sLen":0,"time":60168.5393258427,"type":0},{"id":0,"sLen":0,"time":60337.0786516854,"type":0},{"id":0,"sLen":0,"time":60505.6179775281,"type":0},{"id":0,"sLen":0,"time":60589.8876404494,"type":0},{"id":3,"sLen":0,"time":60674.1573033708,"type":0},{"id":0,"sLen":0,"time":60842.6966292135,"type":0},{"id":0,"sLen":0,"time":60926.9662921348,"type":0},{"id":3,"sLen":0,"time":61179.7752808989,"type":0},{"id":0,"sLen":0,"time":61348.3146067416,"type":0},{"id":3,"sLen":0,"time":61432.5842696629,"type":0},{"id":3,"sLen":0,"time":61516.8539325843,"type":0},{"id":0,"sLen":0,"time":61685.393258427,"type":0},{"id":0,"sLen":0,"time":62022.4719101124,"type":0},{"id":3,"sLen":0,"time":62191.0112359551,"type":0},{"id":3,"sLen":0,"time":62275.2808988764,"type":0},{"id":3,"sLen":0,"time":62443.8202247191,"type":0},{"id":0,"sLen":0,"time":62612.3595505618,"type":0},{"id":3,"sLen":0,"time":62780.8988764045,"type":0},{"id":3,"sLen":0,"time":62865.1685393258,"type":0},{"id":0,"sLen":0,"time":63033.7078651685,"type":0},{"id":0,"sLen":0,"time":63202.2471910112,"type":0},{"id":0,"sLen":0,"time":63286.5168539326,"type":0},{"id":3,"sLen":0,"time":63370.7865168539,"type":0},{"id":0,"sLen":0,"time":63539.3258426966,"type":0},{"id":0,"sLen":0,"time":63623.595505618,"type":0},{"id":3,"sLen":0,"time":63876.404494382,"type":0},{"id":0,"sLen":0,"time":64044.9438202247,"type":0},{"id":3,"sLen":0,"time":64129.2134831461,"type":0},{"id":3,"sLen":0,"time":64213.4831460674,"type":0},{"id":0,"sLen":0,"time":64382.0224719101,"type":0},{"id":3,"sLen":0,"time":64719.1011235955,"type":0},{"id":0,"sLen":0,"time":65056.1797752809,"type":0},{"id":0,"sLen":0,"time":65393.2584269663,"type":0},{"id":0,"sLen":0,"time":65730.3370786517,"type":0},{"id":0,"sLen":0,"time":66067.4157303371,"type":0},{"id":3,"sLen":0,"time":66404.4943820225,"type":0},{"id":3,"sLen":0,"time":66741.5730337079,"type":0},{"id":3,"sLen":0,"time":67078.6516853933,"type":0},{"id":3,"sLen":0,"time":67415.7303370787,"type":0},{"id":0,"sLen":0,"time":67752.808988764,"type":0},{"id":0,"sLen":0,"time":68089.8876404494,"type":0},{"id":0,"sLen":0,"time":68426.9662921348,"type":0},{"id":0,"sLen":0,"time":68764.0449438202,"type":0},{"id":3,"sLen":0,"time":69101.1235955056,"type":0},{"id":3,"sLen":0,"time":69438.202247191,"type":0},{"id":3,"sLen":0,"time":69775.2808988764,"type":0},{"id":3,"sLen":0,"time":69859.5505617978,"type":0},{"id":3,"sLen":0,"time":69943.8202247191,"type":0},{"id":3,"sLen":0,"time":70112.3595505618,"type":0},{"id":0,"sLen":0,"time":70449.4382022472,"type":0},{"id":0,"sLen":0,"time":70786.5168539326,"type":0},{"id":0,"sLen":0,"time":71123.595505618,"type":0},{"id":0,"sLen":0,"time":71460.6741573034,"type":0},{"id":3,"sLen":0,"time":71797.7528089888,"type":0},{"id":3,"sLen":0,"time":72134.8314606741,"type":0},{"id":3,"sLen":0,"time":72471.9101123595,"type":0},{"id":3,"sLen":0,"time":72556.1797752809,"type":0},{"id":3,"sLen":0,"time":72640.4494382023,"type":0},{"id":3,"sLen":0,"time":72808.9887640449,"type":0},{"id":3,"sLen":0,"time":72893.2584269663,"type":0},{"id":3,"sLen":0,"time":72977.5280898877,"type":0},{"id":0,"sLen":0,"time":73146.0674157303,"type":0},{"id":0,"sLen":0,"time":73230.3370786517,"type":0},{"id":0,"sLen":0,"time":73314.6067415731,"type":0},{"id":0,"sLen":0,"time":73483.1460674157,"type":0},{"id":0,"sLen":0,"time":73567.4157303371,"type":0},{"id":0,"sLen":0,"time":73651.6853932585,"type":0},{"id":0,"sLen":0,"time":73820.2247191011,"type":0},{"id":0,"sLen":0,"time":73904.4943820225,"type":0},{"id":0,"sLen":0,"time":73988.7640449438,"type":0},{"id":0,"sLen":0,"time":74157.3033707866,"type":0},{"id":0,"sLen":0,"time":74241.5730337079,"type":0},{"id":0,"sLen":0,"time":74325.8426966292,"type":0},{"id":0,"sLen":0,"time":74410.1123595506,"type":0},{"id":3,"sLen":0,"time":74494.382022472,"type":0},{"id":3,"sLen":0,"time":74578.6516853933,"type":0},{"id":3,"sLen":0,"time":74662.9213483146,"type":0},{"id":3,"sLen":0,"time":74747.191011236,"type":0},{"id":0,"sLen":0,"time":74831.4606741573,"type":0},{"id":0,"sLen":0,"time":74915.7303370787,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75084.2696629214,"type":0},{"id":3,"sLen":0,"time":75168.5393258427,"type":0},{"id":0,"sLen":0,"time":75505.6179775281,"type":0},{"id":3,"sLen":0,"time":75842.6966292135,"type":0},{"id":0,"sLen":0,"time":76179.7752808989,"type":0},{"id":0,"sLen":0,"time":76516.8539325843,"type":0},{"id":3,"sLen":0,"time":76853.9325842697,"type":0},{"id":3,"sLen":0,"time":77191.0112359551,"type":0},{"id":0,"sLen":0,"time":77528.0898876405,"type":0},{"id":3,"sLen":0,"time":77865.1685393259,"type":0},{"id":3,"sLen":0,"time":78033.7078651686,"type":0},{"id":0,"sLen":0,"time":78202.2471910113,"type":0},{"id":0,"sLen":0,"time":78539.3258426967,"type":0},{"id":3,"sLen":0,"time":78876.4044943821,"type":0},{"id":0,"sLen":0,"time":79213.4831460675,"type":0},{"id":0,"sLen":0,"time":79550.5617977529,"type":0},{"id":3,"sLen":0,"time":79887.6404494383,"type":0},{"id":0,"sLen":0,"time":80140.4494382023,"type":0},{"id":0,"sLen":0,"time":80224.7191011237,"type":0},{"id":0,"sLen":0,"time":80308.988764045,"type":0},{"id":0,"sLen":0,"time":80477.5280898877,"type":0},{"id":3,"sLen":84.269662921346,"time":80730.3370786517,"type":0},{"id":0,"sLen":0,"time":80898.8764044945,"type":0},{"id":3,"sLen":0,"time":81067.4157303371,"type":0},{"id":0,"sLen":0,"time":81235.9550561799,"type":0},{"id":3,"sLen":0,"time":81404.4943820225,"type":0},{"id":0,"sLen":0,"time":81573.0337078652,"type":0},{"id":3,"sLen":0,"time":81741.5730337079,"type":0},{"id":0,"sLen":0,"time":81910.1123595506,"type":0},{"id":3,"sLen":0,"time":82078.6516853933,"type":0},{"id":3,"sLen":0,"time":82247.191011236,"type":0},{"id":0,"sLen":0,"time":82415.7303370787,"type":0},{"id":3,"sLen":0,"time":82584.2696629214,"type":0},{"id":0,"sLen":0,"time":82752.8089887641,"type":0},{"id":3,"sLen":0,"time":82921.3483146068,"type":0},{"id":0,"sLen":0,"time":83089.8876404495,"type":0},{"id":3,"sLen":0,"time":83258.4269662922,"type":0},{"id":3,"sLen":0,"time":83426.9662921349,"type":0},{"id":0,"sLen":0,"time":83595.5056179776,"type":0},{"id":0,"sLen":0,"time":83764.0449438203,"type":0},{"id":0,"sLen":0,"time":83932.584269663,"type":0},{"id":3,"sLen":0,"time":84269.6629213484,"type":0},{"id":3,"sLen":0,"time":84606.7415730338,"type":0},{"id":3,"sLen":0,"time":84775.2808988765,"type":0},{"id":3,"sLen":0,"time":84943.8202247192,"type":0},{"id":3,"sLen":0,"time":85280.8988764046,"type":0},{"id":0,"sLen":0,"time":85617.97752809,"type":0},{"id":0,"sLen":0,"time":85702.2471910113,"type":0},{"id":3,"sLen":0,"time":85870.786516854,"type":0},{"id":0,"sLen":0,"time":86039.3258426967,"type":0},{"id":0,"sLen":0,"time":86292.1348314607,"type":0},{"id":3,"sLen":0,"time":86629.2134831461,"type":0},{"id":0,"sLen":0,"time":87134.8314606741,"type":0},{"id":3,"sLen":0,"time":87303.3707865169,"type":0},{"id":0,"sLen":0,"time":87640.4494382023,"type":0},{"id":3,"sLen":0,"time":87977.5280898877,"type":0},{"id":0,"sLen":0,"time":88483.1460674157,"type":0},{"id":3,"sLen":0,"time":88651.6853932584,"type":0},{"id":0,"sLen":0,"time":88988.7640449438,"type":0},{"id":3,"sLen":0,"time":89325.8426966292,"type":0},{"id":0,"sLen":0,"time":89831.4606741573,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.0786516854,"type":0},{"id":3,"sLen":0,"time":90674.1573033708,"type":0},{"id":0,"sLen":0,"time":91179.7752808989,"type":0},{"id":3,"sLen":0,"time":91348.3146067416,"type":0},{"id":0,"sLen":0,"time":91685.393258427,"type":0},{"id":3,"sLen":0,"time":92022.4719101124,"type":0},{"id":0,"sLen":0,"time":92528.0898876405,"type":0},{"id":3,"sLen":0,"time":92696.6292134832,"type":0},{"id":0,"sLen":0,"time":93033.7078651686,"type":0},{"id":3,"sLen":0,"time":93370.786516854,"type":0},{"id":0,"sLen":0,"time":93876.404494382,"type":0},{"id":3,"sLen":0,"time":94044.9438202248,"type":0},{"id":0,"sLen":0,"time":94382.0224719102,"type":0},{"id":3,"sLen":0,"time":94719.1011235956,"type":0},{"id":0,"sLen":0,"time":95224.7191011236,"type":0},{"id":3,"sLen":0,"time":95393.2584269663,"type":0},{"id":0,"sLen":0,"time":95730.3370786517,"type":0},{"id":0,"sLen":0,"time":96067.4157303371,"type":0},{"id":0,"sLen":0,"time":96404.4943820225,"type":0},{"id":0,"sLen":0,"time":96573.0337078652,"type":0},{"id":3,"sLen":0,"time":96741.5730337079,"type":0},{"id":3,"sLen":0,"time":96825.8426966292,"type":0},{"id":3,"sLen":0,"time":96910.1123595505,"type":0},{"id":0,"sLen":0,"time":97078.6516853933,"type":0},{"id":3,"sLen":0,"time":97415.7303370787,"type":0},{"id":3,"sLen":0,"time":97752.808988764,"type":0},{"id":0,"sLen":0,"time":97921.3483146067,"type":0},{"id":3,"sLen":0,"time":98089.8876404494,"type":0},{"id":0,"sLen":0,"time":98426.9662921348,"type":0},{"id":3,"sLen":0,"time":98764.0449438202,"type":0},{"id":0,"sLen":0,"time":99101.1235955056,"type":0},{"id":0,"sLen":0,"time":99269.6629213483,"type":0},{"id":3,"sLen":0,"time":99438.202247191,"type":0},{"id":0,"sLen":0,"time":99775.2808988764,"type":0},{"id":3,"sLen":0,"time":100112.359550562,"type":0},{"id":3,"sLen":0,"time":100449.438202247,"type":0},{"id":0,"sLen":0,"time":100617.97752809,"type":0},{"id":3,"sLen":0,"time":100786.516853933,"type":0},{"id":0,"sLen":0,"time":101123.595505618,"type":0},{"id":3,"sLen":0,"time":101460.674157303,"type":0},{"id":3,"sLen":0,"time":101797.752808989,"type":0},{"id":0,"sLen":0,"time":101966.292134831,"type":0},{"id":3,"sLen":0,"time":102134.831460674,"type":0},{"id":0,"sLen":0,"time":102471.91011236,"type":0},{"id":3,"sLen":0,"time":102808.988764045,"type":0},{"id":3,"sLen":0,"time":103146.06741573,"type":0},{"id":0,"sLen":0,"time":103314.606741573,"type":0},{"id":3,"sLen":0,"time":103483.146067416,"type":0},{"id":0,"sLen":0,"time":103820.224719101,"type":0},{"id":3,"sLen":0,"time":104157.303370787,"type":0},{"id":0,"sLen":0,"time":104494.382022472,"type":0},{"id":0,"sLen":0,"time":104662.921348315,"type":0},{"id":3,"sLen":0,"time":104831.460674157,"type":0},{"id":0,"sLen":0,"time":105168.539325843,"type":0},{"id":3,"sLen":0,"time":105505.617977528,"type":0},{"id":3,"sLen":0,"time":105842.696629213,"type":0},{"id":0,"sLen":0,"time":106011.235955056,"type":0},{"id":3,"sLen":0,"time":106179.775280899,"type":0},{"id":0,"sLen":0,"time":106516.853932584,"type":0},{"id":0,"sLen":0,"time":106853.93258427,"type":0},{"id":0,"sLen":0,"time":107191.011235955,"type":0},{"id":3,"sLen":0,"time":107528.08988764,"type":0},{"id":0,"sLen":0,"time":107865.168539326,"type":0},{"id":0,"sLen":0,"time":108202.247191011,"type":0},{"id":0,"sLen":0,"time":108539.325842697,"type":0},{"id":0,"sLen":0,"time":108876.404494382,"type":0},{"id":3,"sLen":0,"time":109213.483146068,"type":0},{"id":3,"sLen":0,"time":109550.561797753,"type":0},{"id":3,"sLen":0,"time":109887.640449438,"type":0},{"id":3,"sLen":0,"time":110224.719101124,"type":0},{"id":0,"sLen":0,"time":110561.797752809,"type":0},{"id":0,"sLen":0,"time":110730.337078652,"type":0},{"id":0,"sLen":0,"time":110898.876404495,"type":0},{"id":3,"sLen":0,"time":111235.95505618,"type":0},{"id":3,"sLen":0,"time":111573.033707865,"type":0},{"id":3,"sLen":0,"time":111910.112359551,"type":0},{"id":3,"sLen":0,"time":112247.191011236,"type":0},{"id":0,"sLen":0,"time":112584.269662922,"type":0},{"id":3,"sLen":0,"time":112921.348314607,"type":0},{"id":0,"sLen":0,"time":113258.426966292,"type":0},{"id":3,"sLen":0,"time":113932.584269663,"type":0},{"id":0,"sLen":0,"time":114269.662921349,"type":0},{"id":3,"sLen":0,"time":114438.202247191,"type":0},{"id":0,"sLen":0,"time":114606.741573034,"type":0},{"id":3,"sLen":0,"time":114943.820224719,"type":0},{"id":0,"sLen":0,"time":115280.898876405,"type":0},{"id":3,"sLen":0,"time":115617.97752809,"type":0},{"id":3,"sLen":0,"time":115786.516853933,"type":0},{"id":0,"sLen":0,"time":115955.056179776,"type":0},{"id":3,"sLen":0,"time":116123.595505618,"type":0},{"id":0,"sLen":0,"time":116292.134831461,"type":0},{"id":0,"sLen":0,"time":116629.213483146,"type":0},{"id":3,"sLen":0,"time":116966.292134832,"type":0},{"id":3,"sLen":0,"time":117303.370786517,"type":0},{"id":0,"sLen":0,"time":117640.449438203,"type":0},{"id":3,"sLen":0,"time":117977.528089888,"type":0},{"id":3,"sLen":0,"time":118061.797752809,"type":0},{"id":0,"sLen":0,"time":118314.606741573,"type":0},{"id":3,"sLen":0,"time":118398.876404495,"type":0},{"id":0,"sLen":0,"time":118651.685393258,"type":0},{"id":3,"sLen":0,"time":118820.224719101,"type":0},{"id":3,"sLen":0,"time":118904.494382022,"type":0},{"id":3,"sLen":0,"time":119073.033707865,"type":0},{"id":0,"sLen":0,"time":119241.573033708,"type":0},{"id":3,"sLen":0,"time":119410.112359551,"type":0},{"id":3,"sLen":0,"time":119494.382022472,"type":0},{"id":0,"sLen":0,"time":119662.921348315,"type":0},{"id":0,"sLen":0,"time":119831.460674157,"type":0},{"id":0,"sLen":0,"time":119915.730337079,"type":0},{"id":3,"sLen":0,"time":120000,"type":0},{"id":0,"sLen":0,"time":120168.539325843,"type":0},{"id":0,"sLen":0,"time":120252.808988764,"type":0},{"id":3,"sLen":0,"time":120505.617977528,"type":0},{"id":0,"sLen":0,"time":120674.157303371,"type":0},{"id":3,"sLen":0,"time":120758.426966292,"type":0},{"id":3,"sLen":0,"time":120842.696629213,"type":0},{"id":0,"sLen":0,"time":121011.235955056,"type":0},{"id":0,"sLen":0,"time":121095.505617978,"type":0},{"id":0,"sLen":0,"time":121179.775280899,"type":0}],"position":"girlfriend","type":2,"characters":["pico-speakers"],"visible":false}],"codenameChart":true,"stage":"tank","noteTypes":["No Anim Note"]} \ No newline at end of file +{"events":[{"params":[1],"time":5393.25842696629,"name":"Camera Movement"},{"params":[0],"time":10786.5168539326,"name":"Camera Movement"},{"params":[1],"time":14831.4606741573,"name":"Camera Movement"},{"params":[0],"time":21573.0337078652,"name":"Camera Movement"},{"params":[1],"time":24269.6629213483,"name":"Camera Movement"},{"params":[0],"time":26966.2921348315,"name":"Camera Movement"},{"params":[1],"time":28314.606741573,"name":"Camera Movement"},{"params":[0],"time":31011.2359550562,"name":"Camera Movement"},{"params":[1],"time":37752.808988764,"name":"Camera Movement"},{"params":[0],"time":43146.0674157303,"name":"Camera Movement"},{"params":[1],"time":48539.3258426966,"name":"Camera Movement"},{"params":[0],"time":53932.5842696629,"name":"Camera Movement"},{"params":[1],"time":59325.8426966292,"name":"Camera Movement"},{"params":[0],"time":62022.4719101124,"name":"Camera Movement"},{"params":[1],"time":67415.7303370787,"name":"Camera Movement"},{"params":[0],"time":75505.6179775281,"name":"Camera Movement"},{"params":[1],"time":80898.8764044945,"name":"Camera Movement"},{"params":[0],"time":86292.1348314608,"name":"Camera Movement"},{"params":[1],"time":91685.3932584271,"name":"Camera Movement"},{"params":[0],"time":97078.6516853934,"name":"Camera Movement"},{"params":[1],"time":102471.91011236,"name":"Camera Movement"},{"params":[0],"time":107865.168539326,"name":"Camera Movement"},{"params":[1],"time":113258.426966292,"name":"Camera Movement"}],"scrollSpeed":2.2,"strumLines":[{"notes":[{"id":2,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539322,"type":0},{"id":0,"sLen":0,"time":337.078644,"type":0},{"id":0,"sLen":0,"time":674.1573,"type":0},{"id":3,"sLen":0,"time":842.696655,"type":0},{"id":2,"sLen":0,"time":1011.23596,"type":0},{"id":1,"sLen":0,"time":1348.31458,"type":0},{"id":0,"sLen":0,"time":1516.85388,"type":0},{"id":3,"sLen":0,"time":1685.39331,"type":0},{"id":2,"sLen":0,"time":1853.93262,"type":0},{"id":0,"sLen":0,"time":2022.47192,"type":0},{"id":3,"sLen":0,"time":2191.01123,"type":0},{"id":1,"sLen":0,"time":2359.55054,"type":0},{"id":0,"sLen":0,"time":2528.08984,"type":0},{"id":2,"sLen":0,"time":2696.62915,"type":0},{"id":3,"sLen":0,"time":2865.16846,"type":0},{"id":0,"sLen":0,"time":3033.70776,"type":0},{"id":0,"sLen":0,"time":3370.78662,"type":0},{"id":3,"sLen":0,"time":3539.326,"type":0},{"id":2,"sLen":0,"time":3707.86523,"type":0},{"id":1,"sLen":0,"time":4044.94385,"type":0},{"id":0,"sLen":0,"time":4213.483,"type":0},{"id":3,"sLen":0,"time":4382.02246,"type":0},{"id":2,"sLen":0,"time":4550.562,"type":0},{"id":0,"sLen":0,"time":4719.101,"type":0},{"id":3,"sLen":0,"time":4887.64063,"type":0},{"id":1,"sLen":0,"time":5056.17969,"type":0},{"id":0,"sLen":0,"time":5224.719,"type":0},{"id":0,"sLen":0,"time":11123.5957,"type":0},{"id":1,"sLen":0,"time":11292.1348,"type":0},{"id":3,"sLen":252.808989999998,"time":11460.6738,"type":0},{"id":1,"sLen":252.80899,"time":11797.7529,"type":0},{"id":0,"sLen":0,"time":12134.8311,"type":0},{"id":0,"sLen":0,"time":12471.91,"type":0},{"id":0,"sLen":0,"time":12808.9883,"type":0},{"id":0,"sLen":0,"time":12977.5283,"type":0},{"id":3,"sLen":0,"time":13146.0674,"type":0},{"id":1,"sLen":0,"time":13483.1465,"type":0},{"id":0,"sLen":0,"time":13651.6855,"type":0},{"id":3,"sLen":0,"time":13820.2246,"type":0},{"id":1,"sLen":0,"time":13988.7637,"type":0},{"id":0,"sLen":0,"time":14157.3037,"type":0},{"id":3,"sLen":0,"time":14325.8428,"type":0},{"id":1,"sLen":0,"time":14494.3818,"type":0},{"id":0,"sLen":0,"time":14662.9209,"type":0},{"id":1,"sLen":0,"time":14831.4609,"type":0},{"id":0,"sLen":0,"time":15000,"type":0},{"id":3,"sLen":0,"time":15168.5391,"type":0},{"id":1,"sLen":0,"time":15337.0791,"type":0},{"id":0,"sLen":0,"time":15505.6182,"type":0},{"id":3,"sLen":0,"time":15674.1572,"type":0},{"id":1,"sLen":0,"time":15842.6963,"type":0},{"id":0,"sLen":0,"time":16011.2363,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":2,"sLen":0,"time":21741.5723,"type":0},{"id":0,"sLen":0,"time":21910.1133,"type":0},{"id":2,"sLen":0,"time":22078.6523,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":2,"sLen":0,"time":22415.73,"type":0},{"id":0,"sLen":0,"time":22584.27,"type":0},{"id":2,"sLen":0,"time":22752.8086,"type":0},{"id":3,"sLen":0,"time":22921.3477,"type":0},{"id":2,"sLen":0,"time":23089.8867,"type":0},{"id":3,"sLen":0,"time":23258.4277,"type":0},{"id":2,"sLen":0,"time":23426.9668,"type":0},{"id":3,"sLen":0,"time":23595.5059,"type":0},{"id":0,"sLen":0,"time":23764.0449,"type":0},{"id":1,"sLen":0,"time":23932.584,"type":0},{"id":0,"sLen":0,"time":24101.123,"type":0},{"id":2,"sLen":0,"time":24269.6621,"type":0},{"id":3,"sLen":0,"time":26966.293,"type":0},{"id":2,"sLen":0,"time":27134.832,"type":0},{"id":0,"sLen":0,"time":27303.3711,"type":0},{"id":2,"sLen":0,"time":27471.91,"type":0},{"id":3,"sLen":0,"time":27640.45,"type":0},{"id":2,"sLen":0,"time":27808.9883,"type":0},{"id":0,"sLen":0,"time":27977.5273,"type":0},{"id":2,"sLen":0,"time":28146.0684,"type":0},{"id":3,"sLen":0,"time":28314.6074,"type":0},{"id":2,"sLen":0,"time":28483.1465,"type":0},{"id":3,"sLen":0,"time":28651.6855,"type":0},{"id":2,"sLen":0,"time":28820.2246,"type":0},{"id":3,"sLen":0,"time":28988.7637,"type":0},{"id":0,"sLen":0,"time":29157.3027,"type":0},{"id":1,"sLen":0,"time":29325.8418,"type":0},{"id":0,"sLen":0,"time":29494.3828,"type":0},{"id":0,"sLen":0,"time":29662.9219,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":0,"sLen":0,"time":30337.0781,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":0,"sLen":0,"time":31011.2363,"type":0},{"id":0,"sLen":0,"time":31348.3145,"type":0},{"id":0,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":0,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":0,"sLen":0,"time":32359.55,"type":0},{"id":2,"sLen":0,"time":32696.6289,"type":0},{"id":3,"sLen":0,"time":32865.168,"type":0},{"id":0,"sLen":0,"time":33033.707,"type":0},{"id":2,"sLen":0,"time":33370.7852,"type":0},{"id":3,"sLen":0,"time":33539.3242,"type":0},{"id":1,"sLen":0,"time":33707.8633,"type":0},{"id":3,"sLen":0,"time":33876.4063,"type":0},{"id":1,"sLen":0,"time":34044.9453,"type":0},{"id":0,"sLen":0,"time":34213.4844,"type":0},{"id":1,"sLen":0,"time":34382.0234,"type":0},{"id":3,"sLen":0,"time":34550.5625,"type":0},{"id":1,"sLen":0,"time":34719.1,"type":0},{"id":0,"sLen":0,"time":34887.64,"type":0},{"id":0,"sLen":0,"time":35056.18,"type":0},{"id":2,"sLen":0,"time":35393.2578,"type":0},{"id":3,"sLen":0,"time":35561.7969,"type":0},{"id":0,"sLen":0,"time":35730.3359,"type":0},{"id":2,"sLen":0,"time":36067.4141,"type":0},{"id":3,"sLen":0,"time":36235.9531,"type":0},{"id":3,"sLen":0,"time":36404.4961,"type":0},{"id":1,"sLen":0,"time":36573.0352,"type":0},{"id":0,"sLen":0,"time":36741.5742,"type":0},{"id":1,"sLen":0,"time":36910.1133,"type":0},{"id":0,"sLen":0,"time":37078.6523,"type":0},{"id":1,"sLen":0,"time":37247.19,"type":0},{"id":1,"sLen":0,"time":37415.73,"type":0},{"id":0,"sLen":0,"time":37584.27,"type":0},{"id":3,"sLen":0,"time":43146.0664,"type":0},{"id":1,"sLen":0,"time":43314.6055,"type":0},{"id":0,"sLen":0,"time":43483.1445,"type":0},{"id":3,"sLen":0,"time":43651.6836,"type":0},{"id":2,"sLen":0,"time":43820.2266,"type":0},{"id":0,"sLen":0,"time":44157.3047,"type":0},{"id":3,"sLen":0,"time":44325.8438,"type":0},{"id":2,"sLen":0,"time":44494.3828,"type":0},{"id":3,"sLen":0,"time":44662.92,"type":0},{"id":0,"sLen":0,"time":44831.46,"type":0},{"id":2,"sLen":0,"time":45000,"type":0},{"id":3,"sLen":0,"time":45168.54,"type":0},{"id":2,"sLen":0,"time":45337.08,"type":0},{"id":3,"sLen":0,"time":45505.6172,"type":0},{"id":1,"sLen":0,"time":45674.1563,"type":0},{"id":0,"sLen":0,"time":45842.6953,"type":0},{"id":3,"sLen":0,"time":46011.2344,"type":0},{"id":1,"sLen":0,"time":46179.7734,"type":0},{"id":3,"sLen":0,"time":46348.3164,"type":0},{"id":0,"sLen":0,"time":46516.8555,"type":0},{"id":3,"sLen":0,"time":46685.3945,"type":0},{"id":1,"sLen":0,"time":46853.9336,"type":0},{"id":3,"sLen":0,"time":47022.4727,"type":0},{"id":2,"sLen":0,"time":47191.01,"type":0},{"id":1,"sLen":0,"time":47528.09,"type":0},{"id":1,"sLen":0,"time":47865.168,"type":0},{"id":1,"sLen":0,"time":48202.2461,"type":0},{"id":2,"sLen":0,"time":53932.5859,"type":0},{"id":3,"sLen":0,"time":54101.125,"type":0},{"id":0,"sLen":0,"time":54269.6641,"type":0},{"id":0,"sLen":0,"time":54606.7422,"type":0},{"id":3,"sLen":0,"time":54775.28,"type":0},{"id":2,"sLen":0,"time":54943.82,"type":0},{"id":1,"sLen":0,"time":55280.9,"type":0},{"id":0,"sLen":0,"time":55449.4375,"type":0},{"id":3,"sLen":0,"time":55617.9766,"type":0},{"id":2,"sLen":0,"time":55786.5156,"type":0},{"id":0,"sLen":0,"time":55955.0547,"type":0},{"id":3,"sLen":0,"time":56123.5938,"type":0},{"id":1,"sLen":0,"time":56292.1367,"type":0},{"id":0,"sLen":0,"time":56460.6758,"type":0},{"id":2,"sLen":0,"time":56629.2148,"type":0},{"id":3,"sLen":0,"time":56797.7539,"type":0},{"id":0,"sLen":0,"time":56966.293,"type":0},{"id":0,"sLen":0,"time":57303.37,"type":0},{"id":3,"sLen":0,"time":57471.91,"type":0},{"id":2,"sLen":0,"time":57640.45,"type":0},{"id":1,"sLen":0,"time":57977.5273,"type":0},{"id":0,"sLen":0,"time":58146.0664,"type":0},{"id":3,"sLen":0,"time":58314.6055,"type":0},{"id":2,"sLen":0,"time":58483.1445,"type":0},{"id":0,"sLen":0,"time":58651.6836,"type":0},{"id":3,"sLen":0,"time":58820.2266,"type":0},{"id":1,"sLen":84.2696599999908,"time":58988.7656,"type":0},{"id":0,"sLen":0,"time":59157.3047,"type":0},{"id":0,"sLen":168.539321999997,"time":59325.8438,"type":0},{"id":0,"sLen":168.539321999997,"time":60000,"type":0},{"id":0,"sLen":168.539322000004,"time":60674.1563,"type":0},{"id":0,"sLen":168.539321999997,"time":61348.3164,"type":0},{"id":1,"sLen":0,"time":62022.4727,"type":1},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":252.808989999998,"time":65056.18,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":252.808990000005,"time":65730.3359,"type":0},{"id":0,"sLen":0,"time":66067.4141,"type":0},{"id":0,"sLen":0,"time":66404.49,"type":0},{"id":0,"sLen":0,"time":66741.57,"type":0},{"id":0,"sLen":0,"time":66910.11,"type":0},{"id":2,"sLen":0,"time":67078.65,"type":0},{"id":1,"sLen":0,"time":67415.73,"type":0},{"id":0,"sLen":0,"time":67584.27,"type":0},{"id":3,"sLen":0,"time":67752.81,"type":0},{"id":1,"sLen":0,"time":67921.35,"type":0},{"id":0,"sLen":0,"time":68089.89,"type":0},{"id":3,"sLen":0,"time":68258.43,"type":0},{"id":1,"sLen":0,"time":68426.97,"type":0},{"id":3,"sLen":0,"time":68595.51,"type":0},{"id":1,"sLen":0,"time":68764.05,"type":0},{"id":0,"sLen":0,"time":68932.5859,"type":0},{"id":3,"sLen":0,"time":69101.125,"type":0},{"id":2,"sLen":0,"time":69269.6641,"type":0},{"id":0,"sLen":0,"time":69438.2,"type":0},{"id":3,"sLen":0,"time":69606.74,"type":0},{"id":1,"sLen":0,"time":69775.28,"type":0},{"id":0,"sLen":0,"time":69943.82,"type":0},{"id":0,"sLen":0,"time":75505.62,"type":0},{"id":0,"sLen":0,"time":75674.16,"type":0},{"id":0,"sLen":0,"time":75842.6953,"type":0},{"id":2,"sLen":0,"time":76011.2344,"type":0},{"id":3,"sLen":0,"time":76179.77,"type":0},{"id":2,"sLen":0,"time":76348.31,"type":0},{"id":0,"sLen":0,"time":76516.85,"type":0},{"id":2,"sLen":0,"time":76685.39,"type":0},{"id":3,"sLen":0,"time":76853.93,"type":0},{"id":0,"sLen":0,"time":77022.47,"type":0},{"id":3,"sLen":0,"time":77191.01,"type":0},{"id":0,"sLen":0,"time":77359.55,"type":0},{"id":1,"sLen":252.808990000005,"time":77528.0859,"type":0},{"id":0,"sLen":0,"time":77865.17,"type":0},{"id":0,"sLen":0,"time":78033.71,"type":0},{"id":1,"sLen":0,"time":78202.25,"type":0},{"id":0,"sLen":0,"time":78370.79,"type":0},{"id":3,"sLen":0,"time":78539.33,"type":0},{"id":1,"sLen":0,"time":78876.41,"type":0},{"id":0,"sLen":0,"time":79044.9453,"type":0},{"id":2,"sLen":0,"time":79213.4844,"type":0},{"id":0,"sLen":0,"time":79382.02,"type":0},{"id":1,"sLen":0,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":79803.37,"type":0},{"id":1,"sLen":0,"time":80056.18,"type":0},{"id":0,"sLen":0,"time":80224.72,"type":0},{"id":3,"sLen":0,"time":80393.26,"type":0},{"id":0,"sLen":0,"time":80561.8,"type":0},{"id":2,"sLen":0,"time":80730.3359,"type":0},{"id":0,"sLen":0,"time":86292.13,"type":0},{"id":0,"sLen":0,"time":86460.67,"type":0},{"id":1,"sLen":0,"time":86629.21,"type":0},{"id":1,"sLen":0,"time":86797.75,"type":0},{"id":3,"sLen":0,"time":86966.29,"type":0},{"id":3,"sLen":0,"time":87134.83,"type":0},{"id":0,"sLen":0,"time":87303.37,"type":0},{"id":1,"sLen":0,"time":87471.91,"type":0},{"id":0,"sLen":0,"time":87640.45,"type":0},{"id":0,"sLen":0,"time":87808.99,"type":0},{"id":1,"sLen":0,"time":87977.53,"type":0},{"id":1,"sLen":0,"time":88146.07,"type":0},{"id":3,"sLen":0,"time":88314.61,"type":0},{"id":3,"sLen":0,"time":88483.15,"type":0},{"id":0,"sLen":0,"time":88651.69,"type":0},{"id":3,"sLen":0,"time":88820.23,"type":0},{"id":2,"sLen":0,"time":88988.7656,"type":0},{"id":0,"sLen":0,"time":89157.3047,"type":0},{"id":3,"sLen":0,"time":89325.84,"type":0},{"id":2,"sLen":0,"time":89494.38,"type":0},{"id":0,"sLen":0,"time":89662.92,"type":0},{"id":0,"sLen":0,"time":89831.46,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":1,"sLen":0,"time":90168.54,"type":0},{"id":0,"sLen":0,"time":90337.08,"type":0},{"id":1,"sLen":0,"time":90505.62,"type":0},{"id":3,"sLen":0,"time":90674.16,"type":0},{"id":1,"sLen":0,"time":90842.6953,"type":0},{"id":0,"sLen":0,"time":91011.2344,"type":0},{"id":1,"sLen":0,"time":91179.77,"type":0},{"id":3,"sLen":337.078643999994,"time":91348.31,"type":0},{"id":2,"sLen":0,"time":97078.65,"type":0},{"id":0,"sLen":0,"time":97247.19,"type":0},{"id":3,"sLen":0,"time":97415.73,"type":0},{"id":2,"sLen":0,"time":97584.27,"type":0},{"id":0,"sLen":0,"time":97752.81,"type":0},{"id":3,"sLen":0,"time":97921.35,"type":0},{"id":2,"sLen":0,"time":98089.89,"type":0},{"id":0,"sLen":0,"time":98258.43,"type":0},{"id":3,"sLen":0,"time":98426.97,"type":0},{"id":2,"sLen":0,"time":98595.51,"type":0},{"id":0,"sLen":0,"time":98764.05,"type":0},{"id":3,"sLen":0,"time":98932.5859,"type":0},{"id":1,"sLen":0,"time":99101.125,"type":0},{"id":3,"sLen":0,"time":99269.6641,"type":0},{"id":0,"sLen":0,"time":99438.2,"type":0},{"id":3,"sLen":0,"time":99606.74,"type":0},{"id":2,"sLen":0,"time":99775.28,"type":0},{"id":0,"sLen":0,"time":99943.82,"type":0},{"id":3,"sLen":0,"time":100112.359,"type":0},{"id":2,"sLen":0,"time":100280.9,"type":0},{"id":0,"sLen":0,"time":100449.438,"type":0},{"id":3,"sLen":0,"time":100617.977,"type":0},{"id":2,"sLen":0,"time":100786.516,"type":0},{"id":0,"sLen":0,"time":100955.055,"type":0},{"id":3,"sLen":0,"time":101123.594,"type":0},{"id":2,"sLen":0,"time":101292.133,"type":0},{"id":0,"sLen":0,"time":101460.672,"type":0},{"id":3,"sLen":0,"time":101629.211,"type":0},{"id":1,"sLen":0,"time":101797.75,"type":0},{"id":3,"sLen":0,"time":101966.289,"type":0},{"id":0,"sLen":0,"time":102134.828,"type":0},{"id":3,"sLen":0,"time":102303.367,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":0,"sLen":0,"time":102640.453,"type":0},{"id":3,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":0,"sLen":0,"time":103146.07,"type":0},{"id":3,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":0,"sLen":0,"time":103651.688,"type":0},{"id":3,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":0,"sLen":0,"time":104157.305,"type":0},{"id":3,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":3,"sLen":0,"time":104662.922,"type":0},{"id":0,"sLen":0,"time":104831.461,"type":0},{"id":3,"sLen":0,"time":105000,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":0,"sLen":0,"time":105337.078,"type":0},{"id":3,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":0,"sLen":0,"time":105842.695,"type":0},{"id":3,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":0,"sLen":0,"time":106348.313,"type":0},{"id":3,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":0,"sLen":0,"time":106853.93,"type":0},{"id":3,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":3,"sLen":0,"time":107359.547,"type":0},{"id":0,"sLen":0,"time":107528.086,"type":0},{"id":3,"sLen":0,"time":107696.633,"type":0},{"id":1,"sLen":0,"time":107865.172,"type":0},{"id":0,"sLen":0,"time":107949.438,"type":0},{"id":1,"sLen":0,"time":108033.711,"type":0},{"id":1,"sLen":0,"time":108202.25,"type":0},{"id":0,"sLen":0,"time":108370.789,"type":0},{"id":1,"sLen":0,"time":108539.328,"type":0},{"id":0,"sLen":0,"time":108623.594,"type":0},{"id":1,"sLen":0,"time":108707.867,"type":0},{"id":1,"sLen":0,"time":108876.406,"type":0},{"id":0,"sLen":0,"time":109044.945,"type":0},{"id":1,"sLen":0,"time":109213.484,"type":0},{"id":3,"sLen":0,"time":109297.75,"type":0},{"id":1,"sLen":0,"time":109382.023,"type":0},{"id":1,"sLen":0,"time":109550.563,"type":0},{"id":0,"sLen":0,"time":109719.1,"type":0},{"id":1,"sLen":0,"time":109887.641,"type":0},{"id":0,"sLen":0,"time":109971.906,"type":0},{"id":1,"sLen":0,"time":110056.18,"type":0},{"id":1,"sLen":0,"time":110224.719,"type":0},{"id":0,"sLen":0,"time":110393.258,"type":0},{"id":2,"sLen":0,"time":113258.43,"type":0},{"id":3,"sLen":0,"time":113426.969,"type":0},{"id":2,"sLen":0,"time":113595.508,"type":0},{"id":3,"sLen":0,"time":113764.047,"type":0},{"id":0,"sLen":0,"time":113932.586,"type":0},{"id":1,"sLen":0,"time":114101.125,"type":0},{"id":0,"sLen":0,"time":114269.664,"type":0},{"id":1,"sLen":0,"time":114438.2,"type":0},{"id":2,"sLen":0,"time":114606.742,"type":0},{"id":3,"sLen":0,"time":114775.281,"type":0},{"id":2,"sLen":0,"time":114943.82,"type":0},{"id":3,"sLen":0,"time":115112.359,"type":0},{"id":0,"sLen":0,"time":115280.9,"type":0},{"id":3,"sLen":0,"time":115449.438,"type":0},{"id":0,"sLen":0,"time":115617.977,"type":0},{"id":1,"sLen":0,"time":115786.516,"type":0},{"id":2,"sLen":589.887633999984,"time":115955.055,"type":0}],"position":"dad","characters":["tankman"],"visible":true,"type":0},{"notes":[{"id":2,"sLen":0,"time":5393.2583,"type":0},{"id":3,"sLen":0,"time":5561.798,"type":0},{"id":0,"sLen":0,"time":5730.337,"type":0},{"id":0,"sLen":0,"time":6067.41553,"type":0},{"id":3,"sLen":0,"time":6235.955,"type":0},{"id":2,"sLen":0,"time":6404.494,"type":0},{"id":1,"sLen":0,"time":6741.573,"type":0},{"id":0,"sLen":0,"time":6910.1123,"type":0},{"id":3,"sLen":0,"time":7078.652,"type":0},{"id":2,"sLen":0,"time":7247.191,"type":0},{"id":0,"sLen":0,"time":7415.73047,"type":0},{"id":3,"sLen":0,"time":7584.26953,"type":0},{"id":1,"sLen":0,"time":7752.809,"type":0},{"id":0,"sLen":0,"time":7921.348,"type":0},{"id":2,"sLen":0,"time":8089.8877,"type":0},{"id":3,"sLen":0,"time":8258.427,"type":0},{"id":0,"sLen":0,"time":8426.966,"type":0},{"id":0,"sLen":0,"time":8764.045,"type":0},{"id":3,"sLen":0,"time":8932.584,"type":0},{"id":2,"sLen":0,"time":9101.124,"type":0},{"id":1,"sLen":0,"time":9438.202,"type":0},{"id":0,"sLen":0,"time":9606.741,"type":0},{"id":3,"sLen":0,"time":9775.281,"type":0},{"id":2,"sLen":0,"time":9943.82,"type":0},{"id":0,"sLen":0,"time":10112.3594,"type":0},{"id":3,"sLen":0,"time":10280.8984,"type":0},{"id":1,"sLen":0,"time":10449.4385,"type":0},{"id":0,"sLen":0,"time":10617.9775,"type":0},{"id":0,"sLen":252.808990000001,"time":14831.4609,"type":0},{"id":1,"sLen":252.80899,"time":15168.5391,"type":0},{"id":3,"sLen":252.80899,"time":15505.6182,"type":0},{"id":1,"sLen":252.808990000001,"time":15842.6963,"type":0},{"id":3,"sLen":252.80899,"time":16179.7754,"type":0},{"id":0,"sLen":0,"time":16516.8535,"type":0},{"id":1,"sLen":0,"time":16685.3926,"type":0},{"id":3,"sLen":252.808990000001,"time":16853.9316,"type":0},{"id":1,"sLen":252.808990000001,"time":17191.0117,"type":0},{"id":0,"sLen":0,"time":17528.09,"type":0},{"id":0,"sLen":0,"time":17865.168,"type":0},{"id":0,"sLen":0,"time":18202.248,"type":0},{"id":0,"sLen":0,"time":18370.7871,"type":0},{"id":3,"sLen":0,"time":18539.3262,"type":0},{"id":1,"sLen":0,"time":18876.4043,"type":0},{"id":0,"sLen":0,"time":19044.9434,"type":0},{"id":3,"sLen":0,"time":19213.4824,"type":0},{"id":1,"sLen":0,"time":19382.0215,"type":0},{"id":0,"sLen":0,"time":19550.5625,"type":0},{"id":3,"sLen":0,"time":19719.1016,"type":0},{"id":1,"sLen":0,"time":19887.64,"type":0},{"id":0,"sLen":0,"time":20056.18,"type":0},{"id":1,"sLen":0,"time":20224.7188,"type":0},{"id":0,"sLen":0,"time":20393.2578,"type":0},{"id":3,"sLen":0,"time":20561.7969,"type":0},{"id":1,"sLen":0,"time":20730.3379,"type":0},{"id":0,"sLen":0,"time":20898.877,"type":0},{"id":3,"sLen":0,"time":21067.416,"type":0},{"id":1,"sLen":0,"time":21235.9551,"type":0},{"id":0,"sLen":0,"time":21404.4941,"type":0},{"id":3,"sLen":0,"time":21573.0332,"type":0},{"id":3,"sLen":0,"time":21910.1133,"type":0},{"id":3,"sLen":0,"time":22247.1914,"type":0},{"id":3,"sLen":0,"time":22584.27,"type":0},{"id":1,"sLen":0,"time":22921.3477,"type":0},{"id":1,"sLen":0,"time":23258.4277,"type":0},{"id":3,"sLen":252.808990000001,"time":23595.5059,"type":0},{"id":0,"sLen":252.808990000009,"time":23932.584,"type":0},{"id":3,"sLen":0,"time":24269.6621,"type":0},{"id":2,"sLen":0,"time":24438.2031,"type":0},{"id":0,"sLen":0,"time":24606.7422,"type":0},{"id":2,"sLen":0,"time":24775.2813,"type":0},{"id":3,"sLen":0,"time":24943.82,"type":0},{"id":2,"sLen":0,"time":25112.36,"type":0},{"id":0,"sLen":0,"time":25280.8984,"type":0},{"id":2,"sLen":0,"time":25449.4375,"type":0},{"id":3,"sLen":0,"time":25617.9766,"type":0},{"id":2,"sLen":0,"time":25786.5176,"type":0},{"id":3,"sLen":0,"time":25955.0566,"type":0},{"id":2,"sLen":0,"time":26123.5957,"type":0},{"id":3,"sLen":0,"time":26292.1348,"type":0},{"id":0,"sLen":0,"time":26460.6738,"type":0},{"id":1,"sLen":0,"time":26629.2129,"type":0},{"id":0,"sLen":0,"time":26797.752,"type":0},{"id":2,"sLen":589.887634000006,"time":26966.293,"type":0},{"id":3,"sLen":589.887633999995,"time":27640.45,"type":0},{"id":1,"sLen":589.887633999999,"time":28314.6074,"type":0},{"id":0,"sLen":589.887634000006,"time":28988.7637,"type":0},{"id":3,"sLen":0,"time":29662.9219,"type":0},{"id":2,"sLen":0,"time":29831.46,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":2,"sLen":0,"time":30168.54,"type":0},{"id":3,"sLen":0,"time":30337.0781,"type":0},{"id":2,"sLen":0,"time":30505.6172,"type":0},{"id":0,"sLen":0,"time":30674.1582,"type":0},{"id":2,"sLen":0,"time":30842.6973,"type":0},{"id":3,"sLen":0,"time":31011.2363,"type":0},{"id":2,"sLen":0,"time":31179.7754,"type":0},{"id":3,"sLen":0,"time":31348.3145,"type":0},{"id":2,"sLen":0,"time":31516.8535,"type":0},{"id":3,"sLen":0,"time":31685.3926,"type":0},{"id":0,"sLen":0,"time":31853.9316,"type":0},{"id":1,"sLen":0,"time":32022.4727,"type":0},{"id":0,"sLen":0,"time":32191.0117,"type":0},{"id":1,"sLen":674.157300000003,"time":32359.55,"type":0},{"id":0,"sLen":0,"time":37752.81,"type":0},{"id":2,"sLen":0,"time":38089.8867,"type":0},{"id":3,"sLen":0,"time":38258.4258,"type":0},{"id":0,"sLen":0,"time":38426.9648,"type":0},{"id":2,"sLen":0,"time":38764.043,"type":0},{"id":3,"sLen":0,"time":38932.5859,"type":0},{"id":1,"sLen":0,"time":39101.125,"type":0},{"id":3,"sLen":0,"time":39269.6641,"type":0},{"id":1,"sLen":0,"time":39438.2031,"type":0},{"id":0,"sLen":0,"time":39606.7422,"type":0},{"id":1,"sLen":0,"time":39775.28,"type":0},{"id":3,"sLen":0,"time":39943.82,"type":0},{"id":1,"sLen":0,"time":40112.36,"type":0},{"id":0,"sLen":0,"time":40280.9,"type":0},{"id":0,"sLen":0,"time":40449.4375,"type":0},{"id":2,"sLen":0,"time":40786.5156,"type":0},{"id":3,"sLen":0,"time":40955.0547,"type":0},{"id":0,"sLen":0,"time":41123.5938,"type":0},{"id":2,"sLen":0,"time":41460.6758,"type":0},{"id":3,"sLen":0,"time":41629.2148,"type":0},{"id":3,"sLen":0,"time":41797.7539,"type":0},{"id":1,"sLen":0,"time":41966.293,"type":0},{"id":0,"sLen":0,"time":42134.832,"type":0},{"id":1,"sLen":0,"time":42303.37,"type":0},{"id":0,"sLen":0,"time":42471.91,"type":0},{"id":1,"sLen":0,"time":42640.45,"type":0},{"id":1,"sLen":0,"time":42808.99,"type":0},{"id":0,"sLen":0,"time":42977.5273,"type":0},{"id":3,"sLen":0,"time":48539.3242,"type":0},{"id":1,"sLen":0,"time":48707.8633,"type":0},{"id":0,"sLen":0,"time":48876.4063,"type":0},{"id":3,"sLen":0,"time":49044.9453,"type":0},{"id":2,"sLen":0,"time":49213.4844,"type":0},{"id":0,"sLen":0,"time":49550.5625,"type":0},{"id":3,"sLen":0,"time":49719.1,"type":0},{"id":2,"sLen":0,"time":49887.64,"type":0},{"id":3,"sLen":0,"time":50056.18,"type":0},{"id":0,"sLen":0,"time":50224.72,"type":0},{"id":2,"sLen":0,"time":50393.2578,"type":0},{"id":3,"sLen":0,"time":50561.7969,"type":0},{"id":2,"sLen":0,"time":50730.3359,"type":0},{"id":3,"sLen":0,"time":50898.875,"type":0},{"id":1,"sLen":0,"time":51067.4141,"type":0},{"id":0,"sLen":0,"time":51235.9531,"type":0},{"id":3,"sLen":0,"time":51404.4961,"type":0},{"id":1,"sLen":0,"time":51573.0352,"type":0},{"id":3,"sLen":0,"time":51741.5742,"type":0},{"id":0,"sLen":0,"time":51910.1133,"type":0},{"id":3,"sLen":0,"time":52078.6523,"type":0},{"id":1,"sLen":0,"time":52247.19,"type":0},{"id":3,"sLen":0,"time":52415.73,"type":0},{"id":2,"sLen":0,"time":52584.27,"type":0},{"id":1,"sLen":0,"time":52921.3477,"type":0},{"id":1,"sLen":0,"time":53258.4258,"type":0},{"id":1,"sLen":0,"time":53595.5039,"type":0},{"id":1,"sLen":1011.23595999999,"time":53932.5859,"type":0},{"id":2,"sLen":505.617981000003,"time":55955.0547,"type":0},{"id":1,"sLen":674.157299999999,"time":56629.2148,"type":0},{"id":0,"sLen":252.808989999998,"time":58651.6836,"type":0},{"id":2,"sLen":84.2696599999908,"time":58988.7656,"type":0},{"id":0,"sLen":0,"time":59157.3047,"type":0},{"id":2,"sLen":0,"time":59325.8438,"type":0},{"id":3,"sLen":0,"time":59494.3828,"type":0},{"id":0,"sLen":0,"time":59662.92,"type":0},{"id":0,"sLen":0,"time":60000,"type":0},{"id":3,"sLen":0,"time":60168.54,"type":0},{"id":2,"sLen":0,"time":60337.08,"type":0},{"id":1,"sLen":0,"time":60674.1563,"type":0},{"id":0,"sLen":0,"time":60842.6953,"type":0},{"id":3,"sLen":0,"time":61011.2344,"type":0},{"id":2,"sLen":0,"time":61179.7734,"type":0},{"id":0,"sLen":0,"time":61348.3164,"type":0},{"id":3,"sLen":0,"time":61516.8555,"type":0},{"id":1,"sLen":0,"time":61685.3945,"type":0},{"id":0,"sLen":0,"time":61853.9336,"type":0},{"id":2,"sLen":0,"time":62022.4727,"type":0},{"id":3,"sLen":0,"time":62191.01,"type":0},{"id":0,"sLen":0,"time":62359.55,"type":0},{"id":0,"sLen":0,"time":62696.63,"type":0},{"id":3,"sLen":0,"time":62865.168,"type":0},{"id":2,"sLen":0,"time":63033.707,"type":0},{"id":1,"sLen":0,"time":63370.7852,"type":0},{"id":0,"sLen":0,"time":63539.3242,"type":0},{"id":3,"sLen":0,"time":63707.8633,"type":0},{"id":2,"sLen":0,"time":63876.4063,"type":0},{"id":0,"sLen":0,"time":64044.9453,"type":0},{"id":3,"sLen":0,"time":64213.4844,"type":0},{"id":1,"sLen":0,"time":64382.0234,"type":0},{"id":0,"sLen":0,"time":64550.5625,"type":0},{"id":1,"sLen":252.808989999998,"time":64719.1,"type":0},{"id":0,"sLen":252.808989999998,"time":65056.18,"type":0},{"id":3,"sLen":252.808989999998,"time":65393.2578,"type":0},{"id":1,"sLen":252.808990000005,"time":65730.3359,"type":0},{"id":3,"sLen":505.617981000003,"time":66067.4141,"type":0},{"id":1,"sLen":505.617981000003,"time":66741.57,"type":0},{"id":0,"sLen":505.617981000003,"time":67415.73,"type":0},{"id":1,"sLen":252.808990000005,"time":68089.89,"type":0},{"id":0,"sLen":252.808990000005,"time":68426.97,"type":0},{"id":3,"sLen":589.887633999999,"time":68764.05,"type":0},{"id":0,"sLen":589.887633999999,"time":69438.2,"type":0},{"id":1,"sLen":252.808990000005,"time":70112.36,"type":0},{"id":0,"sLen":252.808990000005,"time":70449.44,"type":0},{"id":3,"sLen":252.808990000005,"time":70786.5156,"type":0},{"id":1,"sLen":252.808990000005,"time":71123.59,"type":0},{"id":0,"sLen":0,"time":71460.67,"type":0},{"id":0,"sLen":0,"time":71797.75,"type":0},{"id":0,"sLen":0,"time":72134.83,"type":0},{"id":0,"sLen":0,"time":72303.37,"type":0},{"id":2,"sLen":0,"time":72471.91,"type":0},{"id":1,"sLen":0,"time":72808.99,"type":0},{"id":0,"sLen":0,"time":72977.53,"type":0},{"id":3,"sLen":0,"time":73146.07,"type":0},{"id":1,"sLen":0,"time":73314.61,"type":0},{"id":0,"sLen":0,"time":73483.15,"type":0},{"id":3,"sLen":0,"time":73651.69,"type":0},{"id":1,"sLen":0,"time":73820.23,"type":0},{"id":3,"sLen":0,"time":73988.7656,"type":0},{"id":1,"sLen":0,"time":74157.3047,"type":0},{"id":0,"sLen":0,"time":74325.84,"type":0},{"id":3,"sLen":0,"time":74494.38,"type":0},{"id":2,"sLen":0,"time":74662.92,"type":0},{"id":0,"sLen":0,"time":74831.46,"type":0},{"id":3,"sLen":0,"time":75000,"type":0},{"id":1,"sLen":0,"time":75168.54,"type":0},{"id":0,"sLen":0,"time":75337.08,"type":0},{"id":1,"sLen":1264.04492,"time":75505.62,"type":0},{"id":0,"sLen":1264.04492,"time":76853.93,"type":0},{"id":3,"sLen":1264.04492,"time":78202.25,"type":0},{"id":0,"sLen":1264.04492,"time":79550.56,"type":0},{"id":0,"sLen":0,"time":80898.875,"type":0},{"id":0,"sLen":0,"time":81067.4141,"type":0},{"id":0,"sLen":0,"time":81235.95,"type":0},{"id":2,"sLen":0,"time":81404.49,"type":0},{"id":3,"sLen":0,"time":81573.03,"type":0},{"id":2,"sLen":0,"time":81741.57,"type":0},{"id":0,"sLen":0,"time":81910.11,"type":0},{"id":2,"sLen":0,"time":82078.65,"type":0},{"id":3,"sLen":0,"time":82247.19,"type":0},{"id":0,"sLen":0,"time":82415.73,"type":0},{"id":3,"sLen":0,"time":82584.27,"type":0},{"id":0,"sLen":0,"time":82752.81,"type":0},{"id":1,"sLen":252.808990000005,"time":82921.35,"type":0},{"id":0,"sLen":0,"time":83258.43,"type":0},{"id":0,"sLen":0,"time":83426.97,"type":0},{"id":1,"sLen":0,"time":83595.51,"type":0},{"id":0,"sLen":0,"time":83764.05,"type":0},{"id":3,"sLen":0,"time":83932.5859,"type":0},{"id":1,"sLen":0,"time":84269.6641,"type":0},{"id":0,"sLen":0,"time":84438.2,"type":0},{"id":2,"sLen":0,"time":84606.74,"type":0},{"id":0,"sLen":0,"time":84775.28,"type":0},{"id":1,"sLen":0,"time":84943.82,"type":0},{"id":0,"sLen":0,"time":85196.63,"type":0},{"id":1,"sLen":0,"time":85449.44,"type":0},{"id":0,"sLen":0,"time":85617.98,"type":0},{"id":3,"sLen":0,"time":85786.5156,"type":0},{"id":0,"sLen":0,"time":85955.0547,"type":0},{"id":2,"sLen":0,"time":86123.59,"type":0},{"id":0,"sLen":0,"time":91685.39,"type":0},{"id":0,"sLen":0,"time":91853.93,"type":0},{"id":1,"sLen":0,"time":92022.47,"type":0},{"id":1,"sLen":0,"time":92191.01,"type":0},{"id":3,"sLen":0,"time":92359.55,"type":0},{"id":3,"sLen":0,"time":92528.0859,"type":0},{"id":0,"sLen":0,"time":92696.63,"type":0},{"id":1,"sLen":0,"time":92865.17,"type":0},{"id":0,"sLen":0,"time":93033.71,"type":0},{"id":0,"sLen":0,"time":93202.25,"type":0},{"id":1,"sLen":0,"time":93370.79,"type":0},{"id":1,"sLen":0,"time":93539.33,"type":0},{"id":3,"sLen":0,"time":93707.87,"type":0},{"id":3,"sLen":0,"time":93876.41,"type":0},{"id":0,"sLen":0,"time":94044.9453,"type":0},{"id":3,"sLen":0,"time":94213.4844,"type":0},{"id":2,"sLen":0,"time":94382.02,"type":0},{"id":0,"sLen":0,"time":94550.56,"type":0},{"id":3,"sLen":0,"time":94719.1,"type":0},{"id":2,"sLen":0,"time":94887.64,"type":0},{"id":0,"sLen":0,"time":95056.18,"type":0},{"id":0,"sLen":0,"time":95224.72,"type":0},{"id":3,"sLen":0,"time":95393.26,"type":0},{"id":1,"sLen":0,"time":95561.8,"type":0},{"id":0,"sLen":0,"time":95730.3359,"type":0},{"id":1,"sLen":0,"time":95898.875,"type":0},{"id":3,"sLen":0,"time":96067.4141,"type":0},{"id":1,"sLen":0,"time":96235.95,"type":0},{"id":0,"sLen":0,"time":96404.49,"type":0},{"id":1,"sLen":0,"time":96573.03,"type":0},{"id":3,"sLen":337.078643999994,"time":96741.57,"type":0},{"id":0,"sLen":589.88763399997,"time":98426.97,"type":0},{"id":2,"sLen":589.887633999999,"time":99101.125,"type":0},{"id":0,"sLen":589.887633999999,"time":101123.594,"type":0},{"id":2,"sLen":589.887633999984,"time":101797.75,"type":0},{"id":2,"sLen":0,"time":102471.906,"type":0},{"id":3,"sLen":0,"time":102640.453,"type":0},{"id":0,"sLen":0,"time":102808.992,"type":0},{"id":2,"sLen":0,"time":102977.531,"type":0},{"id":3,"sLen":0,"time":103146.07,"type":0},{"id":0,"sLen":0,"time":103314.609,"type":0},{"id":2,"sLen":0,"time":103483.148,"type":0},{"id":3,"sLen":0,"time":103651.688,"type":0},{"id":0,"sLen":0,"time":103820.227,"type":0},{"id":2,"sLen":0,"time":103988.766,"type":0},{"id":3,"sLen":0,"time":104157.305,"type":0},{"id":0,"sLen":0,"time":104325.844,"type":0},{"id":1,"sLen":0,"time":104494.383,"type":0},{"id":0,"sLen":0,"time":104662.922,"type":0},{"id":3,"sLen":0,"time":104831.461,"type":0},{"id":0,"sLen":0,"time":105000,"type":0},{"id":2,"sLen":0,"time":105168.539,"type":0},{"id":3,"sLen":0,"time":105337.078,"type":0},{"id":0,"sLen":0,"time":105505.617,"type":0},{"id":2,"sLen":0,"time":105674.156,"type":0},{"id":3,"sLen":0,"time":105842.695,"type":0},{"id":0,"sLen":0,"time":106011.234,"type":0},{"id":2,"sLen":0,"time":106179.773,"type":0},{"id":3,"sLen":0,"time":106348.313,"type":0},{"id":0,"sLen":0,"time":106516.852,"type":0},{"id":2,"sLen":0,"time":106685.391,"type":0},{"id":3,"sLen":0,"time":106853.93,"type":0},{"id":0,"sLen":0,"time":107022.469,"type":0},{"id":1,"sLen":0,"time":107191.008,"type":0},{"id":0,"sLen":0,"time":107359.547,"type":0},{"id":3,"sLen":0,"time":107528.086,"type":0},{"id":0,"sLen":0,"time":107696.633,"type":0},{"id":3,"sLen":589.887634000013,"time":107865.172,"type":0},{"id":1,"sLen":589.887634000013,"time":108539.328,"type":0},{"id":0,"sLen":589.887633999999,"time":109213.484,"type":0},{"id":1,"sLen":589.887633999999,"time":109887.641,"type":0},{"id":1,"sLen":0,"time":110561.8,"type":0},{"id":0,"sLen":0,"time":110646.07,"type":0},{"id":1,"sLen":0,"time":110730.336,"type":0},{"id":1,"sLen":0,"time":110898.875,"type":0},{"id":0,"sLen":0,"time":111067.414,"type":0},{"id":1,"sLen":0,"time":111235.953,"type":0},{"id":0,"sLen":0,"time":111320.227,"type":0},{"id":1,"sLen":0,"time":111404.492,"type":0},{"id":1,"sLen":0,"time":111573.031,"type":0},{"id":0,"sLen":0,"time":111741.57,"type":0},{"id":1,"sLen":0,"time":111910.109,"type":0},{"id":3,"sLen":0,"time":111994.383,"type":0},{"id":1,"sLen":0,"time":112078.648,"type":0},{"id":1,"sLen":0,"time":112247.188,"type":0},{"id":0,"sLen":0,"time":112415.727,"type":0},{"id":1,"sLen":0,"time":112584.273,"type":0},{"id":0,"sLen":0,"time":112668.539,"type":0},{"id":1,"sLen":0,"time":112752.813,"type":0},{"id":1,"sLen":0,"time":112921.352,"type":0},{"id":0,"sLen":0,"time":113089.891,"type":0},{"id":3,"sLen":589.887633999999,"time":113258.43,"type":0},{"id":1,"sLen":589.887633999999,"time":113932.586,"type":0},{"id":0,"sLen":589.887633999984,"time":114606.742,"type":0},{"id":1,"sLen":589.887633999999,"time":115280.9,"type":0},{"id":2,"sLen":0,"time":115955.055,"type":0},{"id":3,"sLen":0,"time":116123.594,"type":0},{"id":2,"sLen":0,"time":116292.133,"type":0},{"id":3,"sLen":0,"time":116460.672,"type":0},{"id":0,"sLen":0,"time":116629.211,"type":0},{"id":1,"sLen":0,"time":116797.75,"type":0},{"id":0,"sLen":0,"time":116966.289,"type":0},{"id":1,"sLen":0,"time":117134.828,"type":0},{"id":2,"sLen":0,"time":117303.367,"type":0},{"id":3,"sLen":0,"time":117471.906,"type":0},{"id":2,"sLen":0,"time":117640.453,"type":0},{"id":3,"sLen":0,"time":117808.992,"type":0},{"id":0,"sLen":0,"time":117977.531,"type":0},{"id":3,"sLen":0,"time":118146.07,"type":0},{"id":0,"sLen":0,"time":118314.609,"type":0},{"id":1,"sLen":0,"time":118483.148,"type":0}],"position":"boyfriend","characters":["bf-holding-gf"],"visible":true,"type":1},{"notes":[{"id":0,"sLen":0,"time":0,"type":0},{"id":3,"sLen":0,"time":168.539325842697,"type":0},{"id":3,"sLen":0,"time":252.808988764045,"type":0},{"id":3,"sLen":0,"time":421.348314606742,"type":0},{"id":0,"sLen":0,"time":589.887640449438,"type":0},{"id":3,"sLen":0,"time":758.426966292135,"type":0},{"id":3,"sLen":0,"time":842.696629213483,"type":0},{"id":0,"sLen":0,"time":1011.23595505618,"type":0},{"id":0,"sLen":0,"time":1179.77528089888,"type":0},{"id":0,"sLen":0,"time":1264.04494382022,"type":0},{"id":3,"sLen":0,"time":1348.31460674157,"type":0},{"id":0,"sLen":0,"time":1516.85393258427,"type":0},{"id":0,"sLen":0,"time":1601.12359550562,"type":0},{"id":3,"sLen":0,"time":1853.93258426966,"type":0},{"id":0,"sLen":0,"time":2022.47191011236,"type":0},{"id":3,"sLen":0,"time":2106.74157303371,"type":0},{"id":3,"sLen":0,"time":2191.01123595506,"type":0},{"id":0,"sLen":0,"time":2359.55056179775,"type":0},{"id":0,"sLen":0,"time":2696.62921348315,"type":0},{"id":3,"sLen":0,"time":2865.16853932584,"type":0},{"id":3,"sLen":0,"time":2949.43820224719,"type":0},{"id":3,"sLen":0,"time":3117.97752808989,"type":0},{"id":0,"sLen":0,"time":3286.51685393258,"type":0},{"id":3,"sLen":0,"time":3455.05617977528,"type":0},{"id":3,"sLen":0,"time":3539.32584269663,"type":0},{"id":0,"sLen":0,"time":3707.86516853933,"type":0},{"id":0,"sLen":0,"time":3876.40449438202,"type":0},{"id":0,"sLen":0,"time":3960.67415730337,"type":0},{"id":3,"sLen":0,"time":4044.94382022472,"type":0},{"id":0,"sLen":0,"time":4213.48314606742,"type":0},{"id":0,"sLen":0,"time":4297.75280898876,"type":0},{"id":3,"sLen":0,"time":4550.56179775281,"type":0},{"id":0,"sLen":0,"time":4719.10112359551,"type":0},{"id":3,"sLen":0,"time":4803.37078651685,"type":0},{"id":3,"sLen":0,"time":4887.6404494382,"type":0},{"id":0,"sLen":0,"time":5056.1797752809,"type":0},{"id":0,"sLen":0,"time":5140.44943820225,"type":0},{"id":0,"sLen":0,"time":5224.7191011236,"type":0},{"id":0,"sLen":0,"time":5393.25842696629,"type":0},{"id":3,"sLen":0,"time":5561.79775280899,"type":0},{"id":3,"sLen":0,"time":5646.06741573034,"type":0},{"id":3,"sLen":0,"time":5814.60674157303,"type":0},{"id":0,"sLen":0,"time":5983.14606741573,"type":0},{"id":3,"sLen":0,"time":6151.68539325843,"type":0},{"id":3,"sLen":0,"time":6235.95505617978,"type":0},{"id":0,"sLen":0,"time":6404.49438202247,"type":0},{"id":0,"sLen":0,"time":6573.03370786517,"type":0},{"id":0,"sLen":0,"time":6657.30337078652,"type":0},{"id":3,"sLen":0,"time":6741.57303370786,"type":0},{"id":0,"sLen":0,"time":6910.11235955056,"type":0},{"id":0,"sLen":0,"time":6994.38202247191,"type":0},{"id":3,"sLen":0,"time":7247.19101123595,"type":0},{"id":0,"sLen":0,"time":7415.73033707865,"type":0},{"id":3,"sLen":0,"time":7500,"type":0},{"id":3,"sLen":0,"time":7584.26966292135,"type":0},{"id":0,"sLen":0,"time":7752.80898876405,"type":0},{"id":0,"sLen":0,"time":8089.88764044944,"type":0},{"id":3,"sLen":0,"time":8258.42696629213,"type":0},{"id":3,"sLen":0,"time":8342.69662921348,"type":0},{"id":3,"sLen":0,"time":8511.23595505618,"type":0},{"id":0,"sLen":0,"time":8679.77528089888,"type":0},{"id":3,"sLen":0,"time":8848.31460674157,"type":0},{"id":3,"sLen":0,"time":8932.58426966292,"type":0},{"id":0,"sLen":0,"time":9101.12359550562,"type":0},{"id":0,"sLen":0,"time":9269.66292134831,"type":0},{"id":0,"sLen":0,"time":9353.93258426966,"type":0},{"id":3,"sLen":0,"time":9438.20224719101,"type":0},{"id":0,"sLen":0,"time":9606.74157303371,"type":0},{"id":0,"sLen":0,"time":9691.01123595505,"type":0},{"id":3,"sLen":0,"time":9943.8202247191,"type":0},{"id":0,"sLen":0,"time":10112.3595505618,"type":0},{"id":3,"sLen":0,"time":10196.6292134831,"type":0},{"id":3,"sLen":0,"time":10280.8988764045,"type":0},{"id":0,"sLen":0,"time":10449.4382022472,"type":0},{"id":0,"sLen":0,"time":21235.9550561798,"type":0},{"id":3,"sLen":0,"time":21320.2247191011,"type":0},{"id":0,"sLen":0,"time":21404.4943820225,"type":0},{"id":0,"sLen":0,"time":21573.0337078652,"type":0},{"id":3,"sLen":0,"time":21910.1123595506,"type":0},{"id":0,"sLen":0,"time":22247.191011236,"type":0},{"id":3,"sLen":0,"time":22584.2696629213,"type":0},{"id":0,"sLen":0,"time":22921.3483146067,"type":0},{"id":0,"sLen":0,"time":23258.4269662921,"type":0},{"id":3,"sLen":0,"time":23595.5056179775,"type":0},{"id":3,"sLen":0,"time":23932.5842696629,"type":0},{"id":0,"sLen":0,"time":24269.6629213483,"type":0},{"id":3,"sLen":0,"time":24606.7415730337,"type":0},{"id":0,"sLen":0,"time":24943.8202247191,"type":0},{"id":3,"sLen":0,"time":25280.8988764045,"type":0},{"id":0,"sLen":0,"time":25617.9775280899,"type":0},{"id":0,"sLen":0,"time":25955.0561797753,"type":0},{"id":3,"sLen":0,"time":26292.1348314607,"type":0},{"id":3,"sLen":0,"time":26629.2134831461,"type":0},{"id":3,"sLen":0,"time":26713.4831460674,"type":0},{"id":3,"sLen":0,"time":26797.7528089888,"type":0},{"id":3,"sLen":0,"time":26966.2921348315,"type":0},{"id":0,"sLen":0,"time":27303.3707865169,"type":0},{"id":0,"sLen":0,"time":27640.4494382022,"type":0},{"id":3,"sLen":0,"time":27977.5280898876,"type":0},{"id":3,"sLen":0,"time":28314.606741573,"type":0},{"id":0,"sLen":0,"time":28651.6853932584,"type":0},{"id":3,"sLen":0,"time":28988.7640449438,"type":0},{"id":0,"sLen":0,"time":29325.8426966292,"type":0},{"id":0,"sLen":0,"time":29662.9213483146,"type":0},{"id":0,"sLen":0,"time":29831.4606741573,"type":0},{"id":0,"sLen":0,"time":30000,"type":0},{"id":3,"sLen":0,"time":30168.5393258427,"type":0},{"id":3,"sLen":0,"time":30337.0786516854,"type":0},{"id":3,"sLen":0,"time":30505.6179775281,"type":0},{"id":3,"sLen":0,"time":30674.1573033708,"type":0},{"id":0,"sLen":0,"time":30842.6966292135,"type":0},{"id":0,"sLen":0,"time":31011.2359550562,"type":0},{"id":3,"sLen":0,"time":31348.3146067416,"type":0},{"id":3,"sLen":0,"time":31685.393258427,"type":0},{"id":0,"sLen":0,"time":31853.9325842697,"type":0},{"id":0,"sLen":0,"time":32359.5505617978,"type":0},{"id":3,"sLen":0,"time":32696.6292134831,"type":0},{"id":0,"sLen":0,"time":33033.7078651685,"type":0},{"id":0,"sLen":0,"time":33202.2471910112,"type":0},{"id":3,"sLen":0,"time":33370.7865168539,"type":0},{"id":0,"sLen":0,"time":33707.8651685393,"type":0},{"id":3,"sLen":0,"time":34044.9438202247,"type":0},{"id":3,"sLen":0,"time":34382.0224719101,"type":0},{"id":0,"sLen":0,"time":34550.5617977528,"type":0},{"id":3,"sLen":0,"time":34719.1011235955,"type":0},{"id":0,"sLen":0,"time":35056.1797752809,"type":0},{"id":3,"sLen":0,"time":35393.2584269663,"type":0},{"id":0,"sLen":0,"time":35730.3370786517,"type":0},{"id":0,"sLen":0,"time":35898.8764044944,"type":0},{"id":3,"sLen":0,"time":36067.4157303371,"type":0},{"id":0,"sLen":0,"time":36404.4943820225,"type":0},{"id":3,"sLen":0,"time":36741.5730337079,"type":0},{"id":3,"sLen":0,"time":37078.6516853933,"type":0},{"id":0,"sLen":0,"time":37247.191011236,"type":0},{"id":3,"sLen":0,"time":37415.7303370787,"type":0},{"id":0,"sLen":0,"time":37752.808988764,"type":0},{"id":3,"sLen":0,"time":38089.8876404494,"type":0},{"id":3,"sLen":0,"time":38426.9662921348,"type":0},{"id":0,"sLen":0,"time":38595.5056179775,"type":0},{"id":3,"sLen":0,"time":38764.0449438202,"type":0},{"id":0,"sLen":0,"time":39101.1235955056,"type":0},{"id":3,"sLen":0,"time":39438.202247191,"type":0},{"id":3,"sLen":0,"time":39775.2808988764,"type":0},{"id":0,"sLen":0,"time":39943.8202247191,"type":0},{"id":3,"sLen":0,"time":40112.3595505618,"type":0},{"id":0,"sLen":0,"time":40449.4382022472,"type":0},{"id":3,"sLen":0,"time":40786.5168539326,"type":0},{"id":3,"sLen":0,"time":41123.595505618,"type":0},{"id":0,"sLen":0,"time":41292.1348314607,"type":0},{"id":3,"sLen":0,"time":41460.6741573034,"type":0},{"id":0,"sLen":0,"time":41797.7528089888,"type":0},{"id":0,"sLen":0,"time":42134.8314606742,"type":0},{"id":0,"sLen":0,"time":42471.9101123596,"type":0},{"id":0,"sLen":0,"time":42640.4494382022,"type":0},{"id":3,"sLen":0,"time":42808.9887640449,"type":0},{"id":3,"sLen":0,"time":42893.2584269663,"type":0},{"id":3,"sLen":0,"time":42977.5280898876,"type":0},{"id":0,"sLen":0,"time":43146.0674157303,"type":0},{"id":3,"sLen":0,"time":43483.1460674157,"type":0},{"id":3,"sLen":0,"time":43820.2247191011,"type":0},{"id":0,"sLen":0,"time":43988.7640449438,"type":0},{"id":3,"sLen":0,"time":44157.3033707865,"type":0},{"id":0,"sLen":0,"time":44494.3820224719,"type":0},{"id":3,"sLen":0,"time":44831.4606741573,"type":0},{"id":0,"sLen":0,"time":45168.5393258427,"type":0},{"id":0,"sLen":0,"time":45337.0786516854,"type":0},{"id":3,"sLen":0,"time":45505.6179775281,"type":0},{"id":0,"sLen":0,"time":45842.6966292135,"type":0},{"id":3,"sLen":0,"time":46179.7752808989,"type":0},{"id":3,"sLen":0,"time":46516.8539325843,"type":0},{"id":0,"sLen":0,"time":46685.393258427,"type":0},{"id":3,"sLen":0,"time":46853.9325842697,"type":0},{"id":0,"sLen":0,"time":47191.0112359551,"type":0},{"id":3,"sLen":0,"time":47528.0898876404,"type":0},{"id":3,"sLen":0,"time":47865.1685393258,"type":0},{"id":0,"sLen":0,"time":48033.7078651685,"type":0},{"id":3,"sLen":0,"time":48202.2471910112,"type":0},{"id":0,"sLen":0,"time":48539.3258426966,"type":0},{"id":3,"sLen":0,"time":48876.404494382,"type":0},{"id":3,"sLen":0,"time":49213.4831460674,"type":0},{"id":0,"sLen":0,"time":49382.0224719101,"type":0},{"id":3,"sLen":0,"time":49550.5617977528,"type":0},{"id":0,"sLen":0,"time":49887.6404494382,"type":0},{"id":3,"sLen":0,"time":50224.7191011236,"type":0},{"id":0,"sLen":0,"time":50561.797752809,"type":0},{"id":0,"sLen":0,"time":50730.3370786517,"type":0},{"id":3,"sLen":0,"time":50898.8764044944,"type":0},{"id":0,"sLen":0,"time":51235.9550561798,"type":0},{"id":3,"sLen":0,"time":51573.0337078652,"type":0},{"id":3,"sLen":0,"time":51910.1123595506,"type":0},{"id":0,"sLen":0,"time":52078.6516853933,"type":0},{"id":3,"sLen":0,"time":52247.191011236,"type":0},{"id":0,"sLen":0,"time":52584.2696629213,"type":0},{"id":0,"sLen":0,"time":52921.3483146067,"type":0},{"id":0,"sLen":0,"time":53258.4269662921,"type":0},{"id":0,"sLen":0,"time":53426.9662921348,"type":0},{"id":3,"sLen":0,"time":53595.5056179775,"type":0},{"id":3,"sLen":0,"time":53679.7752808989,"type":0},{"id":3,"sLen":0,"time":53764.0449438202,"type":0},{"id":0,"sLen":0,"time":53932.5842696629,"type":0},{"id":3,"sLen":0,"time":54101.1235955056,"type":0},{"id":3,"sLen":0,"time":54185.393258427,"type":0},{"id":3,"sLen":0,"time":54353.9325842697,"type":0},{"id":0,"sLen":0,"time":54522.4719101124,"type":0},{"id":3,"sLen":0,"time":54691.0112359551,"type":0},{"id":3,"sLen":0,"time":54775.2808988764,"type":0},{"id":0,"sLen":0,"time":54943.8202247191,"type":0},{"id":0,"sLen":0,"time":55112.3595505618,"type":0},{"id":0,"sLen":0,"time":55196.6292134831,"type":0},{"id":3,"sLen":0,"time":55280.8988764045,"type":0},{"id":0,"sLen":0,"time":55449.4382022472,"type":0},{"id":0,"sLen":0,"time":55533.7078651685,"type":0},{"id":3,"sLen":0,"time":55786.5168539326,"type":0},{"id":0,"sLen":0,"time":55955.0561797753,"type":0},{"id":3,"sLen":0,"time":56039.3258426966,"type":0},{"id":3,"sLen":0,"time":56123.595505618,"type":0},{"id":0,"sLen":0,"time":56292.1348314607,"type":0},{"id":0,"sLen":0,"time":56629.2134831461,"type":0},{"id":3,"sLen":0,"time":56797.7528089888,"type":0},{"id":3,"sLen":0,"time":56882.0224719101,"type":0},{"id":3,"sLen":0,"time":57050.5617977528,"type":0},{"id":0,"sLen":0,"time":57219.1011235955,"type":0},{"id":3,"sLen":0,"time":57387.6404494382,"type":0},{"id":3,"sLen":0,"time":57471.9101123595,"type":0},{"id":0,"sLen":0,"time":57640.4494382022,"type":0},{"id":0,"sLen":0,"time":57808.9887640449,"type":0},{"id":0,"sLen":0,"time":57893.2584269663,"type":0},{"id":3,"sLen":0,"time":57977.5280898876,"type":0},{"id":0,"sLen":0,"time":58146.0674157303,"type":0},{"id":0,"sLen":0,"time":58230.3370786517,"type":0},{"id":3,"sLen":0,"time":58483.1460674157,"type":0},{"id":0,"sLen":0,"time":58651.6853932584,"type":0},{"id":3,"sLen":0,"time":58735.9550561798,"type":0},{"id":3,"sLen":0,"time":58820.2247191011,"type":0},{"id":0,"sLen":0,"time":58988.7640449438,"type":0},{"id":0,"sLen":0,"time":59073.0337078652,"type":0},{"id":0,"sLen":0,"time":59157.3033707865,"type":0},{"id":0,"sLen":0,"time":59325.8426966292,"type":0},{"id":3,"sLen":0,"time":59494.3820224719,"type":0},{"id":3,"sLen":0,"time":59578.6516853933,"type":0},{"id":3,"sLen":0,"time":59747.191011236,"type":0},{"id":0,"sLen":0,"time":59915.7303370786,"type":0},{"id":3,"sLen":0,"time":60084.2696629213,"type":0},{"id":3,"sLen":0,"time":60168.5393258427,"type":0},{"id":0,"sLen":0,"time":60337.0786516854,"type":0},{"id":0,"sLen":0,"time":60505.6179775281,"type":0},{"id":0,"sLen":0,"time":60589.8876404494,"type":0},{"id":3,"sLen":0,"time":60674.1573033708,"type":0},{"id":0,"sLen":0,"time":60842.6966292135,"type":0},{"id":0,"sLen":0,"time":60926.9662921348,"type":0},{"id":3,"sLen":0,"time":61179.7752808989,"type":0},{"id":0,"sLen":0,"time":61348.3146067416,"type":0},{"id":3,"sLen":0,"time":61432.5842696629,"type":0},{"id":3,"sLen":0,"time":61516.8539325843,"type":0},{"id":0,"sLen":0,"time":61685.393258427,"type":0},{"id":0,"sLen":0,"time":62022.4719101124,"type":0},{"id":3,"sLen":0,"time":62191.0112359551,"type":0},{"id":3,"sLen":0,"time":62275.2808988764,"type":0},{"id":3,"sLen":0,"time":62443.8202247191,"type":0},{"id":0,"sLen":0,"time":62612.3595505618,"type":0},{"id":3,"sLen":0,"time":62780.8988764045,"type":0},{"id":3,"sLen":0,"time":62865.1685393258,"type":0},{"id":0,"sLen":0,"time":63033.7078651685,"type":0},{"id":0,"sLen":0,"time":63202.2471910112,"type":0},{"id":0,"sLen":0,"time":63286.5168539326,"type":0},{"id":3,"sLen":0,"time":63370.7865168539,"type":0},{"id":0,"sLen":0,"time":63539.3258426966,"type":0},{"id":0,"sLen":0,"time":63623.595505618,"type":0},{"id":3,"sLen":0,"time":63876.404494382,"type":0},{"id":0,"sLen":0,"time":64044.9438202247,"type":0},{"id":3,"sLen":0,"time":64129.2134831461,"type":0},{"id":3,"sLen":0,"time":64213.4831460674,"type":0},{"id":0,"sLen":0,"time":64382.0224719101,"type":0},{"id":3,"sLen":0,"time":64719.1011235955,"type":0},{"id":0,"sLen":0,"time":65056.1797752809,"type":0},{"id":0,"sLen":0,"time":65393.2584269663,"type":0},{"id":0,"sLen":0,"time":65730.3370786517,"type":0},{"id":0,"sLen":0,"time":66067.4157303371,"type":0},{"id":3,"sLen":0,"time":66404.4943820225,"type":0},{"id":3,"sLen":0,"time":66741.5730337079,"type":0},{"id":3,"sLen":0,"time":67078.6516853933,"type":0},{"id":3,"sLen":0,"time":67415.7303370787,"type":0},{"id":0,"sLen":0,"time":67752.808988764,"type":0},{"id":0,"sLen":0,"time":68089.8876404494,"type":0},{"id":0,"sLen":0,"time":68426.9662921348,"type":0},{"id":0,"sLen":0,"time":68764.0449438202,"type":0},{"id":3,"sLen":0,"time":69101.1235955056,"type":0},{"id":3,"sLen":0,"time":69438.202247191,"type":0},{"id":3,"sLen":0,"time":69775.2808988764,"type":0},{"id":3,"sLen":0,"time":69859.5505617978,"type":0},{"id":3,"sLen":0,"time":69943.8202247191,"type":0},{"id":3,"sLen":0,"time":70112.3595505618,"type":0},{"id":0,"sLen":0,"time":70449.4382022472,"type":0},{"id":0,"sLen":0,"time":70786.5168539326,"type":0},{"id":0,"sLen":0,"time":71123.595505618,"type":0},{"id":0,"sLen":0,"time":71460.6741573034,"type":0},{"id":3,"sLen":0,"time":71797.7528089888,"type":0},{"id":3,"sLen":0,"time":72134.8314606741,"type":0},{"id":3,"sLen":0,"time":72471.9101123595,"type":0},{"id":3,"sLen":0,"time":72556.1797752809,"type":0},{"id":3,"sLen":0,"time":72640.4494382023,"type":0},{"id":3,"sLen":0,"time":72808.9887640449,"type":0},{"id":3,"sLen":0,"time":72893.2584269663,"type":0},{"id":3,"sLen":0,"time":72977.5280898877,"type":0},{"id":0,"sLen":0,"time":73146.0674157303,"type":0},{"id":0,"sLen":0,"time":73230.3370786517,"type":0},{"id":0,"sLen":0,"time":73314.6067415731,"type":0},{"id":0,"sLen":0,"time":73483.1460674157,"type":0},{"id":0,"sLen":0,"time":73567.4157303371,"type":0},{"id":0,"sLen":0,"time":73651.6853932585,"type":0},{"id":0,"sLen":0,"time":73820.2247191011,"type":0},{"id":0,"sLen":0,"time":73904.4943820225,"type":0},{"id":0,"sLen":0,"time":73988.7640449438,"type":0},{"id":0,"sLen":0,"time":74157.3033707866,"type":0},{"id":0,"sLen":0,"time":74241.5730337079,"type":0},{"id":0,"sLen":0,"time":74325.8426966292,"type":0},{"id":0,"sLen":0,"time":74410.1123595506,"type":0},{"id":3,"sLen":0,"time":74494.382022472,"type":0},{"id":3,"sLen":0,"time":74578.6516853933,"type":0},{"id":3,"sLen":0,"time":74662.9213483146,"type":0},{"id":3,"sLen":0,"time":74747.191011236,"type":0},{"id":0,"sLen":0,"time":74831.4606741573,"type":0},{"id":0,"sLen":0,"time":74915.7303370787,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":0,"sLen":0,"time":75084.2696629214,"type":0},{"id":3,"sLen":0,"time":75168.5393258427,"type":0},{"id":0,"sLen":0,"time":75505.6179775281,"type":0},{"id":3,"sLen":0,"time":75842.6966292135,"type":0},{"id":0,"sLen":0,"time":76179.7752808989,"type":0},{"id":0,"sLen":0,"time":76516.8539325843,"type":0},{"id":3,"sLen":0,"time":76853.9325842697,"type":0},{"id":3,"sLen":0,"time":77191.0112359551,"type":0},{"id":0,"sLen":0,"time":77528.0898876405,"type":0},{"id":3,"sLen":0,"time":77865.1685393259,"type":0},{"id":3,"sLen":0,"time":78033.7078651686,"type":0},{"id":0,"sLen":0,"time":78202.2471910113,"type":0},{"id":0,"sLen":0,"time":78539.3258426967,"type":0},{"id":3,"sLen":0,"time":78876.4044943821,"type":0},{"id":0,"sLen":0,"time":79213.4831460675,"type":0},{"id":0,"sLen":0,"time":79550.5617977529,"type":0},{"id":3,"sLen":0,"time":79887.6404494383,"type":0},{"id":0,"sLen":0,"time":80140.4494382023,"type":0},{"id":0,"sLen":0,"time":80224.7191011237,"type":0},{"id":0,"sLen":0,"time":80308.988764045,"type":0},{"id":0,"sLen":0,"time":80477.5280898877,"type":0},{"id":3,"sLen":84.269662921346,"time":80730.3370786517,"type":0},{"id":0,"sLen":0,"time":80898.8764044945,"type":0},{"id":3,"sLen":0,"time":81067.4157303371,"type":0},{"id":0,"sLen":0,"time":81235.9550561799,"type":0},{"id":3,"sLen":0,"time":81404.4943820225,"type":0},{"id":0,"sLen":0,"time":81573.0337078652,"type":0},{"id":3,"sLen":0,"time":81741.5730337079,"type":0},{"id":0,"sLen":0,"time":81910.1123595506,"type":0},{"id":3,"sLen":0,"time":82078.6516853933,"type":0},{"id":3,"sLen":0,"time":82247.191011236,"type":0},{"id":0,"sLen":0,"time":82415.7303370787,"type":0},{"id":3,"sLen":0,"time":82584.2696629214,"type":0},{"id":0,"sLen":0,"time":82752.8089887641,"type":0},{"id":3,"sLen":0,"time":82921.3483146068,"type":0},{"id":0,"sLen":0,"time":83089.8876404495,"type":0},{"id":3,"sLen":0,"time":83258.4269662922,"type":0},{"id":3,"sLen":0,"time":83426.9662921349,"type":0},{"id":0,"sLen":0,"time":83595.5056179776,"type":0},{"id":0,"sLen":0,"time":83764.0449438203,"type":0},{"id":0,"sLen":0,"time":83932.584269663,"type":0},{"id":3,"sLen":0,"time":84269.6629213484,"type":0},{"id":3,"sLen":0,"time":84606.7415730338,"type":0},{"id":3,"sLen":0,"time":84775.2808988765,"type":0},{"id":3,"sLen":0,"time":84943.8202247192,"type":0},{"id":3,"sLen":0,"time":85280.8988764046,"type":0},{"id":0,"sLen":0,"time":85617.97752809,"type":0},{"id":0,"sLen":0,"time":85702.2471910113,"type":0},{"id":3,"sLen":0,"time":85870.786516854,"type":0},{"id":0,"sLen":0,"time":86039.3258426967,"type":0},{"id":0,"sLen":0,"time":86292.1348314607,"type":0},{"id":3,"sLen":0,"time":86629.2134831461,"type":0},{"id":0,"sLen":0,"time":87134.8314606741,"type":0},{"id":3,"sLen":0,"time":87303.3707865169,"type":0},{"id":0,"sLen":0,"time":87640.4494382023,"type":0},{"id":3,"sLen":0,"time":87977.5280898877,"type":0},{"id":0,"sLen":0,"time":88483.1460674157,"type":0},{"id":3,"sLen":0,"time":88651.6853932584,"type":0},{"id":0,"sLen":0,"time":88988.7640449438,"type":0},{"id":3,"sLen":0,"time":89325.8426966292,"type":0},{"id":0,"sLen":0,"time":89831.4606741573,"type":0},{"id":3,"sLen":0,"time":90000,"type":0},{"id":0,"sLen":0,"time":90337.0786516854,"type":0},{"id":3,"sLen":0,"time":90674.1573033708,"type":0},{"id":0,"sLen":0,"time":91179.7752808989,"type":0},{"id":3,"sLen":0,"time":91348.3146067416,"type":0},{"id":0,"sLen":0,"time":91685.393258427,"type":0},{"id":3,"sLen":0,"time":92022.4719101124,"type":0},{"id":0,"sLen":0,"time":92528.0898876405,"type":0},{"id":3,"sLen":0,"time":92696.6292134832,"type":0},{"id":0,"sLen":0,"time":93033.7078651686,"type":0},{"id":3,"sLen":0,"time":93370.786516854,"type":0},{"id":0,"sLen":0,"time":93876.404494382,"type":0},{"id":3,"sLen":0,"time":94044.9438202248,"type":0},{"id":0,"sLen":0,"time":94382.0224719102,"type":0},{"id":3,"sLen":0,"time":94719.1011235956,"type":0},{"id":0,"sLen":0,"time":95224.7191011236,"type":0},{"id":3,"sLen":0,"time":95393.2584269663,"type":0},{"id":0,"sLen":0,"time":95730.3370786517,"type":0},{"id":0,"sLen":0,"time":96067.4157303371,"type":0},{"id":0,"sLen":0,"time":96404.4943820225,"type":0},{"id":0,"sLen":0,"time":96573.0337078652,"type":0},{"id":3,"sLen":0,"time":96741.5730337079,"type":0},{"id":3,"sLen":0,"time":96825.8426966292,"type":0},{"id":3,"sLen":0,"time":96910.1123595505,"type":0},{"id":0,"sLen":0,"time":97078.6516853933,"type":0},{"id":3,"sLen":0,"time":97415.7303370787,"type":0},{"id":3,"sLen":0,"time":97752.808988764,"type":0},{"id":0,"sLen":0,"time":97921.3483146067,"type":0},{"id":3,"sLen":0,"time":98089.8876404494,"type":0},{"id":0,"sLen":0,"time":98426.9662921348,"type":0},{"id":3,"sLen":0,"time":98764.0449438202,"type":0},{"id":0,"sLen":0,"time":99101.1235955056,"type":0},{"id":0,"sLen":0,"time":99269.6629213483,"type":0},{"id":3,"sLen":0,"time":99438.202247191,"type":0},{"id":0,"sLen":0,"time":99775.2808988764,"type":0},{"id":3,"sLen":0,"time":100112.359550562,"type":0},{"id":3,"sLen":0,"time":100449.438202247,"type":0},{"id":0,"sLen":0,"time":100617.97752809,"type":0},{"id":3,"sLen":0,"time":100786.516853933,"type":0},{"id":0,"sLen":0,"time":101123.595505618,"type":0},{"id":3,"sLen":0,"time":101460.674157303,"type":0},{"id":3,"sLen":0,"time":101797.752808989,"type":0},{"id":0,"sLen":0,"time":101966.292134831,"type":0},{"id":3,"sLen":0,"time":102134.831460674,"type":0},{"id":0,"sLen":0,"time":102471.91011236,"type":0},{"id":3,"sLen":0,"time":102808.988764045,"type":0},{"id":3,"sLen":0,"time":103146.06741573,"type":0},{"id":0,"sLen":0,"time":103314.606741573,"type":0},{"id":3,"sLen":0,"time":103483.146067416,"type":0},{"id":0,"sLen":0,"time":103820.224719101,"type":0},{"id":3,"sLen":0,"time":104157.303370787,"type":0},{"id":0,"sLen":0,"time":104494.382022472,"type":0},{"id":0,"sLen":0,"time":104662.921348315,"type":0},{"id":3,"sLen":0,"time":104831.460674157,"type":0},{"id":0,"sLen":0,"time":105168.539325843,"type":0},{"id":3,"sLen":0,"time":105505.617977528,"type":0},{"id":3,"sLen":0,"time":105842.696629213,"type":0},{"id":0,"sLen":0,"time":106011.235955056,"type":0},{"id":3,"sLen":0,"time":106179.775280899,"type":0},{"id":0,"sLen":0,"time":106516.853932584,"type":0},{"id":0,"sLen":0,"time":106853.93258427,"type":0},{"id":0,"sLen":0,"time":107191.011235955,"type":0},{"id":3,"sLen":0,"time":107528.08988764,"type":0},{"id":0,"sLen":0,"time":107865.168539326,"type":0},{"id":0,"sLen":0,"time":108202.247191011,"type":0},{"id":0,"sLen":0,"time":108539.325842697,"type":0},{"id":0,"sLen":0,"time":108876.404494382,"type":0},{"id":3,"sLen":0,"time":109213.483146068,"type":0},{"id":3,"sLen":0,"time":109550.561797753,"type":0},{"id":3,"sLen":0,"time":109887.640449438,"type":0},{"id":3,"sLen":0,"time":110224.719101124,"type":0},{"id":0,"sLen":0,"time":110561.797752809,"type":0},{"id":0,"sLen":0,"time":110730.337078652,"type":0},{"id":0,"sLen":0,"time":110898.876404495,"type":0},{"id":3,"sLen":0,"time":111235.95505618,"type":0},{"id":3,"sLen":0,"time":111573.033707865,"type":0},{"id":3,"sLen":0,"time":111910.112359551,"type":0},{"id":3,"sLen":0,"time":112247.191011236,"type":0},{"id":0,"sLen":0,"time":112584.269662922,"type":0},{"id":3,"sLen":0,"time":112921.348314607,"type":0},{"id":0,"sLen":0,"time":113258.426966292,"type":0},{"id":3,"sLen":0,"time":113932.584269663,"type":0},{"id":0,"sLen":0,"time":114269.662921349,"type":0},{"id":3,"sLen":0,"time":114438.202247191,"type":0},{"id":0,"sLen":0,"time":114606.741573034,"type":0},{"id":3,"sLen":0,"time":114943.820224719,"type":0},{"id":0,"sLen":0,"time":115280.898876405,"type":0},{"id":3,"sLen":0,"time":115617.97752809,"type":0},{"id":3,"sLen":0,"time":115786.516853933,"type":0},{"id":0,"sLen":0,"time":115955.056179776,"type":0},{"id":3,"sLen":0,"time":116123.595505618,"type":0},{"id":0,"sLen":0,"time":116292.134831461,"type":0},{"id":0,"sLen":0,"time":116629.213483146,"type":0},{"id":3,"sLen":0,"time":116966.292134832,"type":0},{"id":3,"sLen":0,"time":117303.370786517,"type":0},{"id":0,"sLen":0,"time":117640.449438203,"type":0},{"id":3,"sLen":0,"time":117977.528089888,"type":0},{"id":3,"sLen":0,"time":118061.797752809,"type":0},{"id":0,"sLen":0,"time":118314.606741573,"type":0},{"id":3,"sLen":0,"time":118398.876404495,"type":0},{"id":0,"sLen":0,"time":118651.685393258,"type":0},{"id":3,"sLen":0,"time":118820.224719101,"type":0},{"id":3,"sLen":0,"time":118904.494382022,"type":0},{"id":3,"sLen":0,"time":119073.033707865,"type":0},{"id":0,"sLen":0,"time":119241.573033708,"type":0},{"id":3,"sLen":0,"time":119410.112359551,"type":0},{"id":3,"sLen":0,"time":119494.382022472,"type":0},{"id":0,"sLen":0,"time":119662.921348315,"type":0},{"id":0,"sLen":0,"time":119831.460674157,"type":0},{"id":0,"sLen":0,"time":119915.730337079,"type":0},{"id":3,"sLen":0,"time":120000,"type":0},{"id":0,"sLen":0,"time":120168.539325843,"type":0},{"id":0,"sLen":0,"time":120252.808988764,"type":0},{"id":3,"sLen":0,"time":120505.617977528,"type":0},{"id":0,"sLen":0,"time":120674.157303371,"type":0},{"id":3,"sLen":0,"time":120758.426966292,"type":0},{"id":3,"sLen":0,"time":120842.696629213,"type":0},{"id":0,"sLen":0,"time":121011.235955056,"type":0},{"id":0,"sLen":0,"time":121095.505617978,"type":0},{"id":0,"sLen":0,"time":121179.775280899,"type":0}],"position":"girlfriend","type":2,"characters":["pico-speakers"],"visible":false}],"codenameChart":true,"stage":"tank","noteTypes":["No Anim Note"]} \ No newline at end of file diff --git a/assets/songs/stress/events.json b/assets/songs/stress/events.json new file mode 100644 index 00000000..1561a533 --- /dev/null +++ b/assets/songs/stress/events.json @@ -0,0 +1 @@ +{"events":[{"params":[0,"good!",true],"time":62022.4719101124,"name":"Play Animation"}]} \ No newline at end of file diff --git a/assets/songs/thorns/charts/easy.json b/assets/songs/thorns/charts/easy.json index b4e7b644..61cc8c95 100644 --- a/assets/songs/thorns/charts/easy.json +++ b/assets/songs/thorns/charts/easy.json @@ -1 +1 @@ -{"codenameChart":true,"strumLines":[{"position":"dad","notes":[{"id":3,"sLen":157.89473,"time":0,"type":0},{"id":1,"sLen":157.89473,"time":631.5789,"type":0},{"id":0,"sLen":0,"time":947.3684,"type":0},{"id":3,"sLen":0,"time":1263.15784,"type":0},{"id":3,"sLen":0,"time":1736.842,"type":0},{"id":3,"sLen":0,"time":2210.52612,"type":0},{"id":3,"sLen":0,"time":2526.31567,"type":0},{"id":3,"sLen":0,"time":2842.10522,"type":0},{"id":1,"sLen":0,"time":3157.89453,"type":0},{"id":0,"sLen":0,"time":3473.684,"type":0},{"id":3,"sLen":0,"time":3789.47363,"type":0},{"id":3,"sLen":0,"time":4263.15771,"type":0},{"id":3,"sLen":0,"time":4736.842,"type":0},{"id":2,"sLen":157.894729999998,"time":10105.2627,"type":0},{"id":2,"sLen":0,"time":10736.8418,"type":0},{"id":3,"sLen":0,"time":10894.7363,"type":0},{"id":2,"sLen":157.89473,"time":11368.4209,"type":0},{"id":2,"sLen":157.89473,"time":11842.1045,"type":0},{"id":2,"sLen":157.89473,"time":12315.7891,"type":0},{"id":2,"sLen":157.89473,"time":12631.5781,"type":0},{"id":2,"sLen":0,"time":13263.1572,"type":0},{"id":3,"sLen":0,"time":13421.0518,"type":0},{"id":2,"sLen":157.89473,"time":13894.7363,"type":0},{"id":2,"sLen":157.89473,"time":14368.42,"type":0},{"id":0,"sLen":157.89473,"time":14842.1045,"type":0},{"id":1,"sLen":0,"time":20210.5254,"type":0},{"id":3,"sLen":0,"time":20526.3145,"type":0},{"id":3,"sLen":0,"time":21000,"type":0},{"id":2,"sLen":0,"time":21473.6836,"type":0},{"id":2,"sLen":0,"time":21947.3672,"type":0},{"id":3,"sLen":0,"time":22421.05,"type":0},{"id":1,"sLen":0,"time":22736.8418,"type":0},{"id":3,"sLen":0,"time":23052.63,"type":0},{"id":3,"sLen":0,"time":23526.3145,"type":0},{"id":2,"sLen":0,"time":23999.998,"type":0},{"id":2,"sLen":0,"time":24473.6836,"type":0},{"id":3,"sLen":0,"time":24947.3672,"type":0},{"id":2,"sLen":0,"time":30315.79,"type":0},{"id":3,"sLen":0,"time":30631.5781,"type":0},{"id":3,"sLen":0,"time":30947.3672,"type":0},{"id":0,"sLen":0,"time":31263.1563,"type":0},{"id":0,"sLen":0,"time":31578.9453,"type":0},{"id":0,"sLen":0,"time":31894.7363,"type":0},{"id":3,"sLen":0,"time":32210.5254,"type":0},{"id":1,"sLen":0,"time":32526.3145,"type":0},{"id":0,"sLen":0,"time":32842.1055,"type":0},{"id":0,"sLen":0,"time":33157.8945,"type":0},{"id":0,"sLen":0,"time":33473.6836,"type":0},{"id":3,"sLen":0,"time":33789.4727,"type":0},{"id":1,"sLen":0,"time":34105.26,"type":0},{"id":1,"sLen":0,"time":34421.05,"type":0},{"id":1,"sLen":0,"time":34736.84,"type":0},{"id":1,"sLen":236.842100000002,"time":35052.63,"type":0},{"id":0,"sLen":0,"time":40421.05,"type":0},{"id":0,"sLen":0,"time":40736.84,"type":0},{"id":3,"sLen":0,"time":41052.63,"type":0},{"id":0,"sLen":0,"time":41368.418,"type":0},{"id":2,"sLen":0,"time":41684.207,"type":0},{"id":2,"sLen":0,"time":42315.79,"type":0},{"id":0,"sLen":0,"time":42631.58,"type":0},{"id":0,"sLen":0,"time":42947.3672,"type":0},{"id":0,"sLen":0,"time":43263.1563,"type":0},{"id":3,"sLen":0,"time":43578.9453,"type":0},{"id":0,"sLen":0,"time":43894.7344,"type":0},{"id":2,"sLen":0,"time":44210.5234,"type":0},{"id":2,"sLen":0,"time":44842.1,"type":0},{"id":3,"sLen":0,"time":45157.8945,"type":0},{"id":3,"sLen":157.89473,"time":50526.3125,"type":0},{"id":1,"sLen":157.89473,"time":51157.89,"type":0},{"id":0,"sLen":0,"time":51473.6836,"type":0},{"id":3,"sLen":0,"time":51789.4727,"type":0},{"id":3,"sLen":0,"time":52263.1563,"type":0},{"id":3,"sLen":0,"time":52736.84,"type":0},{"id":3,"sLen":0,"time":53052.63,"type":0},{"id":3,"sLen":0,"time":53368.418,"type":0},{"id":1,"sLen":0,"time":53684.207,"type":0},{"id":0,"sLen":0,"time":53999.9961,"type":0},{"id":3,"sLen":0,"time":54315.7852,"type":0},{"id":3,"sLen":0,"time":54789.4727,"type":0},{"id":3,"sLen":0,"time":55263.1563,"type":0},{"id":2,"sLen":0,"time":60631.58,"type":0},{"id":3,"sLen":0,"time":60947.3672,"type":0},{"id":3,"sLen":0,"time":61263.1563,"type":0},{"id":0,"sLen":0,"time":61578.9453,"type":0},{"id":0,"sLen":0,"time":61894.7344,"type":0},{"id":0,"sLen":0,"time":62210.5234,"type":0},{"id":3,"sLen":0,"time":62526.3125,"type":0},{"id":1,"sLen":0,"time":62842.1,"type":0},{"id":0,"sLen":0,"time":63157.89,"type":0},{"id":0,"sLen":0,"time":63473.68,"type":0},{"id":0,"sLen":0,"time":63789.4727,"type":0},{"id":3,"sLen":0,"time":64105.26,"type":0},{"id":1,"sLen":0,"time":64421.05,"type":0},{"id":1,"sLen":0,"time":64736.84,"type":0},{"id":1,"sLen":0,"time":65052.63,"type":0},{"id":1,"sLen":236.842100000002,"time":65368.418,"type":0},{"id":0,"sLen":0,"time":70736.8359,"type":0},{"id":0,"sLen":0,"time":71052.625,"type":0},{"id":3,"sLen":0,"time":71368.4141,"type":0},{"id":0,"sLen":0,"time":71684.21,"type":0},{"id":2,"sLen":0,"time":72000,"type":0},{"id":2,"sLen":0,"time":72631.58,"type":0},{"id":0,"sLen":0,"time":72947.37,"type":0},{"id":0,"sLen":0,"time":73263.16,"type":0},{"id":0,"sLen":0,"time":73578.9453,"type":0},{"id":3,"sLen":0,"time":73894.7344,"type":0},{"id":0,"sLen":0,"time":74210.52,"type":0},{"id":2,"sLen":0,"time":74526.31,"type":0},{"id":2,"sLen":0,"time":75157.89,"type":0},{"id":3,"sLen":0,"time":75473.68,"type":0},{"id":2,"sLen":0,"time":80842.1,"type":0},{"id":0,"sLen":0,"time":81473.68,"type":0},{"id":3,"sLen":0,"time":81631.58,"type":0},{"id":1,"sLen":0,"time":81789.47,"type":0},{"id":2,"sLen":0,"time":82105.26,"type":0},{"id":0,"sLen":0,"time":82736.8359,"type":0},{"id":3,"sLen":0,"time":82894.7344,"type":0},{"id":1,"sLen":0,"time":83052.625,"type":0},{"id":2,"sLen":0,"time":83368.4141,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84157.89,"type":0},{"id":1,"sLen":0,"time":84315.79,"type":0},{"id":2,"sLen":0,"time":84631.58,"type":0},{"id":0,"sLen":0,"time":85263.16,"type":0},{"id":3,"sLen":0,"time":85421.05,"type":0},{"id":1,"sLen":0,"time":85578.9453,"type":0},{"id":3,"sLen":157.89473,"time":90947.37,"type":0},{"id":1,"sLen":157.89473,"time":91578.9453,"type":0},{"id":0,"sLen":0,"time":91894.7344,"type":0},{"id":3,"sLen":0,"time":92210.52,"type":0},{"id":3,"sLen":0,"time":92684.2,"type":0},{"id":3,"sLen":0,"time":93157.89,"type":0},{"id":3,"sLen":0,"time":93473.68,"type":0},{"id":3,"sLen":0,"time":93789.47,"type":0},{"id":1,"sLen":0,"time":94105.26,"type":0},{"id":0,"sLen":0,"time":94421.05,"type":0},{"id":3,"sLen":0,"time":94736.8359,"type":0},{"id":3,"sLen":0,"time":95210.52,"type":0},{"id":3,"sLen":0,"time":95684.2,"type":0}],"characters":["spirit"],"visible":true,"type":0},{"position":"boyfriend","notes":[{"id":3,"sLen":157.89473,"time":5052.63135,"type":0},{"id":1,"sLen":157.89473,"time":5684.21045,"type":0},{"id":0,"sLen":0,"time":5999.99951,"type":0},{"id":3,"sLen":0,"time":6315.789,"type":0},{"id":3,"sLen":0,"time":6789.473,"type":0},{"id":3,"sLen":0,"time":7263.15771,"type":0},{"id":3,"sLen":0,"time":7578.94727,"type":0},{"id":3,"sLen":0,"time":7894.73633,"type":0},{"id":1,"sLen":0,"time":8210.526,"type":0},{"id":0,"sLen":0,"time":8526.315,"type":0},{"id":3,"sLen":0,"time":8842.10449,"type":0},{"id":3,"sLen":0,"time":9315.789,"type":0},{"id":3,"sLen":0,"time":9789.474,"type":0},{"id":2,"sLen":157.89473,"time":15157.8945,"type":0},{"id":2,"sLen":0,"time":15789.4727,"type":0},{"id":3,"sLen":0,"time":15947.3682,"type":0},{"id":2,"sLen":157.89473,"time":16421.0527,"type":0},{"id":2,"sLen":157.89473,"time":16894.7363,"type":0},{"id":2,"sLen":157.89473,"time":17368.42,"type":0},{"id":2,"sLen":157.89473,"time":17684.209,"type":0},{"id":2,"sLen":0,"time":18315.79,"type":0},{"id":3,"sLen":0,"time":18473.6836,"type":0},{"id":2,"sLen":157.89473,"time":18947.3672,"type":0},{"id":2,"sLen":157.89473,"time":19421.05,"type":0},{"id":0,"sLen":157.89473,"time":19894.7363,"type":0},{"id":0,"sLen":0,"time":24789.4727,"type":0},{"id":1,"sLen":0,"time":25263.1563,"type":0},{"id":3,"sLen":0,"time":25578.9453,"type":0},{"id":3,"sLen":0,"time":26052.63,"type":0},{"id":2,"sLen":0,"time":26526.3145,"type":0},{"id":2,"sLen":0,"time":26999.998,"type":0},{"id":3,"sLen":0,"time":27473.6836,"type":0},{"id":1,"sLen":0,"time":27789.4727,"type":0},{"id":3,"sLen":0,"time":28105.2617,"type":0},{"id":3,"sLen":0,"time":28578.9453,"type":0},{"id":2,"sLen":0,"time":29052.63,"type":0},{"id":2,"sLen":0,"time":29526.3145,"type":0},{"id":3,"sLen":0,"time":29999.998,"type":0},{"id":2,"sLen":0,"time":35368.418,"type":0},{"id":3,"sLen":0,"time":35684.207,"type":0},{"id":3,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36315.79,"type":0},{"id":0,"sLen":0,"time":36631.58,"type":0},{"id":0,"sLen":0,"time":36947.3672,"type":0},{"id":3,"sLen":0,"time":37263.1563,"type":0},{"id":1,"sLen":0,"time":37578.9453,"type":0},{"id":0,"sLen":0,"time":37894.7344,"type":0},{"id":0,"sLen":0,"time":38210.5234,"type":0},{"id":0,"sLen":0,"time":38526.3125,"type":0},{"id":3,"sLen":0,"time":38842.1,"type":0},{"id":1,"sLen":0,"time":39157.8945,"type":0},{"id":1,"sLen":0,"time":39473.6836,"type":0},{"id":1,"sLen":0,"time":39789.4727,"type":0},{"id":1,"sLen":236.842100000002,"time":40105.26,"type":0},{"id":0,"sLen":0,"time":45473.6836,"type":0},{"id":0,"sLen":0,"time":45789.4727,"type":0},{"id":3,"sLen":0,"time":46105.26,"type":0},{"id":0,"sLen":0,"time":46421.05,"type":0},{"id":2,"sLen":0,"time":46736.84,"type":0},{"id":2,"sLen":0,"time":47368.418,"type":0},{"id":0,"sLen":0,"time":47684.207,"type":0},{"id":0,"sLen":0,"time":47999.9961,"type":0},{"id":0,"sLen":0,"time":48315.79,"type":0},{"id":3,"sLen":0,"time":48631.58,"type":0},{"id":0,"sLen":0,"time":48947.3672,"type":0},{"id":2,"sLen":0,"time":49263.1563,"type":0},{"id":2,"sLen":0,"time":49894.7344,"type":0},{"id":3,"sLen":0,"time":50210.5234,"type":0},{"id":3,"sLen":157.89473,"time":55578.9453,"type":0},{"id":1,"sLen":157.89473,"time":56210.5234,"type":0},{"id":0,"sLen":0,"time":56526.3125,"type":0},{"id":3,"sLen":0,"time":56842.1,"type":0},{"id":3,"sLen":0,"time":57315.7852,"type":0},{"id":3,"sLen":0,"time":57789.4727,"type":0},{"id":3,"sLen":0,"time":58105.26,"type":0},{"id":3,"sLen":0,"time":58421.05,"type":0},{"id":1,"sLen":0,"time":58736.84,"type":0},{"id":0,"sLen":0,"time":59052.63,"type":0},{"id":3,"sLen":0,"time":59368.418,"type":0},{"id":3,"sLen":0,"time":59842.1,"type":0},{"id":3,"sLen":0,"time":60315.7852,"type":0},{"id":2,"sLen":0,"time":65684.21,"type":0},{"id":3,"sLen":0,"time":66000,"type":0},{"id":3,"sLen":0,"time":66315.79,"type":0},{"id":0,"sLen":0,"time":66631.58,"type":0},{"id":0,"sLen":0,"time":66947.37,"type":0},{"id":0,"sLen":0,"time":67263.16,"type":0},{"id":3,"sLen":0,"time":67578.9453,"type":0},{"id":1,"sLen":0,"time":67894.7344,"type":0},{"id":0,"sLen":0,"time":68210.52,"type":0},{"id":0,"sLen":0,"time":68526.31,"type":0},{"id":3,"sLen":0,"time":68684.21,"type":0},{"id":0,"sLen":0,"time":68842.1,"type":0},{"id":3,"sLen":0,"time":69157.89,"type":0},{"id":1,"sLen":0,"time":69315.79,"type":0},{"id":1,"sLen":0,"time":69473.68,"type":0},{"id":1,"sLen":0,"time":69789.47,"type":0},{"id":1,"sLen":0,"time":70105.26,"type":0},{"id":1,"sLen":236.842099999994,"time":70421.05,"type":0},{"id":0,"sLen":0,"time":75789.47,"type":0},{"id":0,"sLen":0,"time":76105.26,"type":0},{"id":3,"sLen":0,"time":76421.05,"type":0},{"id":0,"sLen":0,"time":76736.8359,"type":0},{"id":2,"sLen":0,"time":77052.625,"type":0},{"id":2,"sLen":0,"time":77684.2,"type":0},{"id":0,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78315.79,"type":0},{"id":0,"sLen":0,"time":78631.58,"type":0},{"id":3,"sLen":0,"time":78947.37,"type":0},{"id":0,"sLen":0,"time":79263.16,"type":0},{"id":2,"sLen":0,"time":79578.9453,"type":0},{"id":2,"sLen":0,"time":80210.52,"type":0},{"id":3,"sLen":0,"time":80526.31,"type":0},{"id":2,"sLen":0,"time":85894.7344,"type":0},{"id":0,"sLen":0,"time":86526.31,"type":0},{"id":3,"sLen":0,"time":86684.2,"type":0},{"id":1,"sLen":0,"time":86842.1,"type":0},{"id":2,"sLen":0,"time":87157.89,"type":0},{"id":0,"sLen":0,"time":87789.47,"type":0},{"id":3,"sLen":0,"time":87947.37,"type":0},{"id":1,"sLen":0,"time":88105.26,"type":0},{"id":2,"sLen":0,"time":88421.05,"type":0},{"id":0,"sLen":0,"time":89052.625,"type":0},{"id":3,"sLen":0,"time":89210.52,"type":0},{"id":1,"sLen":0,"time":89368.4141,"type":0},{"id":2,"sLen":0,"time":89684.2,"type":0},{"id":0,"sLen":0,"time":90315.79,"type":0},{"id":3,"sLen":0,"time":90473.68,"type":0},{"id":1,"sLen":0,"time":90631.58,"type":0},{"id":3,"sLen":157.89473,"time":95999.99,"type":0},{"id":1,"sLen":157.89473,"time":96631.58,"type":0},{"id":0,"sLen":0,"time":96947.37,"type":0},{"id":3,"sLen":0,"time":97263.16,"type":0},{"id":3,"sLen":0,"time":97736.8359,"type":0},{"id":3,"sLen":0,"time":98210.52,"type":0},{"id":3,"sLen":0,"time":98526.31,"type":0},{"id":3,"sLen":0,"time":98842.1,"type":0},{"id":1,"sLen":0,"time":99157.89,"type":0},{"id":0,"sLen":0,"time":99473.68,"type":0},{"id":3,"sLen":0,"time":99789.47,"type":0},{"id":3,"sLen":0,"time":100263.156,"type":0},{"id":3,"sLen":0,"time":100736.836,"type":0}],"characters":["bf-pixel"],"visible":true,"type":1},{"position":"girlfriend","notes":[],"characters":["gf-pixel"],"visible":false,"type":2}],"stage":"school-evil","scrollSpeed":1.1,"noteTypes":[]} \ No newline at end of file +{"events":[{"params":[1],"time":5052.63157894737,"name":"Camera Movement"},{"params":[0],"time":10105.2631578947,"name":"Camera Movement"},{"params":[1],"time":15157.8947368421,"name":"Camera Movement"},{"params":[0],"time":20210.5263157895,"name":"Camera Movement"},{"params":[1],"time":25263.1578947368,"name":"Camera Movement"},{"params":[0],"time":30315.7894736842,"name":"Camera Movement"},{"params":[1],"time":35368.4210526316,"name":"Camera Movement"},{"params":[0],"time":40421.0526315789,"name":"Camera Movement"},{"params":[1],"time":45473.6842105263,"name":"Camera Movement"},{"params":[0],"time":50526.3157894737,"name":"Camera Movement"},{"params":[1],"time":55578.9473684211,"name":"Camera Movement"},{"params":[0],"time":60631.5789473684,"name":"Camera Movement"},{"params":[1],"time":65684.2105263158,"name":"Camera Movement"},{"params":[0],"time":70736.8421052632,"name":"Camera Movement"},{"params":[1],"time":75789.4736842105,"name":"Camera Movement"},{"params":[0],"time":80842.1052631579,"name":"Camera Movement"},{"params":[1],"time":85894.7368421053,"name":"Camera Movement"},{"params":[0],"time":90947.3684210526,"name":"Camera Movement"},{"params":[1],"time":96000,"name":"Camera Movement"}],"codenameChart":true,"strumLines":[{"position":"dad","notes":[{"id":3,"sLen":157.89473,"time":0,"type":0},{"id":1,"sLen":157.89473,"time":631.5789,"type":0},{"id":0,"sLen":0,"time":947.3684,"type":0},{"id":3,"sLen":0,"time":1263.15784,"type":0},{"id":3,"sLen":0,"time":1736.842,"type":0},{"id":3,"sLen":0,"time":2210.52612,"type":0},{"id":3,"sLen":0,"time":2526.31567,"type":0},{"id":3,"sLen":0,"time":2842.10522,"type":0},{"id":1,"sLen":0,"time":3157.89453,"type":0},{"id":0,"sLen":0,"time":3473.684,"type":0},{"id":3,"sLen":0,"time":3789.47363,"type":0},{"id":3,"sLen":0,"time":4263.15771,"type":0},{"id":3,"sLen":0,"time":4736.842,"type":0},{"id":2,"sLen":157.894729999998,"time":10105.2627,"type":0},{"id":2,"sLen":0,"time":10736.8418,"type":0},{"id":3,"sLen":0,"time":10894.7363,"type":0},{"id":2,"sLen":157.89473,"time":11368.4209,"type":0},{"id":2,"sLen":157.89473,"time":11842.1045,"type":0},{"id":2,"sLen":157.89473,"time":12315.7891,"type":0},{"id":2,"sLen":157.89473,"time":12631.5781,"type":0},{"id":2,"sLen":0,"time":13263.1572,"type":0},{"id":3,"sLen":0,"time":13421.0518,"type":0},{"id":2,"sLen":157.89473,"time":13894.7363,"type":0},{"id":2,"sLen":157.89473,"time":14368.42,"type":0},{"id":0,"sLen":157.89473,"time":14842.1045,"type":0},{"id":1,"sLen":0,"time":20210.5254,"type":0},{"id":3,"sLen":0,"time":20526.3145,"type":0},{"id":3,"sLen":0,"time":21000,"type":0},{"id":2,"sLen":0,"time":21473.6836,"type":0},{"id":2,"sLen":0,"time":21947.3672,"type":0},{"id":3,"sLen":0,"time":22421.05,"type":0},{"id":1,"sLen":0,"time":22736.8418,"type":0},{"id":3,"sLen":0,"time":23052.63,"type":0},{"id":3,"sLen":0,"time":23526.3145,"type":0},{"id":2,"sLen":0,"time":23999.998,"type":0},{"id":2,"sLen":0,"time":24473.6836,"type":0},{"id":3,"sLen":0,"time":24947.3672,"type":0},{"id":2,"sLen":0,"time":30315.79,"type":0},{"id":3,"sLen":0,"time":30631.5781,"type":0},{"id":3,"sLen":0,"time":30947.3672,"type":0},{"id":0,"sLen":0,"time":31263.1563,"type":0},{"id":0,"sLen":0,"time":31578.9453,"type":0},{"id":0,"sLen":0,"time":31894.7363,"type":0},{"id":3,"sLen":0,"time":32210.5254,"type":0},{"id":1,"sLen":0,"time":32526.3145,"type":0},{"id":0,"sLen":0,"time":32842.1055,"type":0},{"id":0,"sLen":0,"time":33157.8945,"type":0},{"id":0,"sLen":0,"time":33473.6836,"type":0},{"id":3,"sLen":0,"time":33789.4727,"type":0},{"id":1,"sLen":0,"time":34105.26,"type":0},{"id":1,"sLen":0,"time":34421.05,"type":0},{"id":1,"sLen":0,"time":34736.84,"type":0},{"id":1,"sLen":236.842100000002,"time":35052.63,"type":0},{"id":0,"sLen":0,"time":40421.05,"type":0},{"id":0,"sLen":0,"time":40736.84,"type":0},{"id":3,"sLen":0,"time":41052.63,"type":0},{"id":0,"sLen":0,"time":41368.418,"type":0},{"id":2,"sLen":0,"time":41684.207,"type":0},{"id":2,"sLen":0,"time":42315.79,"type":0},{"id":0,"sLen":0,"time":42631.58,"type":0},{"id":0,"sLen":0,"time":42947.3672,"type":0},{"id":0,"sLen":0,"time":43263.1563,"type":0},{"id":3,"sLen":0,"time":43578.9453,"type":0},{"id":0,"sLen":0,"time":43894.7344,"type":0},{"id":2,"sLen":0,"time":44210.5234,"type":0},{"id":2,"sLen":0,"time":44842.1,"type":0},{"id":3,"sLen":0,"time":45157.8945,"type":0},{"id":3,"sLen":157.89473,"time":50526.3125,"type":0},{"id":1,"sLen":157.89473,"time":51157.89,"type":0},{"id":0,"sLen":0,"time":51473.6836,"type":0},{"id":3,"sLen":0,"time":51789.4727,"type":0},{"id":3,"sLen":0,"time":52263.1563,"type":0},{"id":3,"sLen":0,"time":52736.84,"type":0},{"id":3,"sLen":0,"time":53052.63,"type":0},{"id":3,"sLen":0,"time":53368.418,"type":0},{"id":1,"sLen":0,"time":53684.207,"type":0},{"id":0,"sLen":0,"time":53999.9961,"type":0},{"id":3,"sLen":0,"time":54315.7852,"type":0},{"id":3,"sLen":0,"time":54789.4727,"type":0},{"id":3,"sLen":0,"time":55263.1563,"type":0},{"id":2,"sLen":0,"time":60631.58,"type":0},{"id":3,"sLen":0,"time":60947.3672,"type":0},{"id":3,"sLen":0,"time":61263.1563,"type":0},{"id":0,"sLen":0,"time":61578.9453,"type":0},{"id":0,"sLen":0,"time":61894.7344,"type":0},{"id":0,"sLen":0,"time":62210.5234,"type":0},{"id":3,"sLen":0,"time":62526.3125,"type":0},{"id":1,"sLen":0,"time":62842.1,"type":0},{"id":0,"sLen":0,"time":63157.89,"type":0},{"id":0,"sLen":0,"time":63473.68,"type":0},{"id":0,"sLen":0,"time":63789.4727,"type":0},{"id":3,"sLen":0,"time":64105.26,"type":0},{"id":1,"sLen":0,"time":64421.05,"type":0},{"id":1,"sLen":0,"time":64736.84,"type":0},{"id":1,"sLen":0,"time":65052.63,"type":0},{"id":1,"sLen":236.842100000002,"time":65368.418,"type":0},{"id":0,"sLen":0,"time":70736.8359,"type":0},{"id":0,"sLen":0,"time":71052.625,"type":0},{"id":3,"sLen":0,"time":71368.4141,"type":0},{"id":0,"sLen":0,"time":71684.21,"type":0},{"id":2,"sLen":0,"time":72000,"type":0},{"id":2,"sLen":0,"time":72631.58,"type":0},{"id":0,"sLen":0,"time":72947.37,"type":0},{"id":0,"sLen":0,"time":73263.16,"type":0},{"id":0,"sLen":0,"time":73578.9453,"type":0},{"id":3,"sLen":0,"time":73894.7344,"type":0},{"id":0,"sLen":0,"time":74210.52,"type":0},{"id":2,"sLen":0,"time":74526.31,"type":0},{"id":2,"sLen":0,"time":75157.89,"type":0},{"id":3,"sLen":0,"time":75473.68,"type":0},{"id":2,"sLen":0,"time":80842.1,"type":0},{"id":0,"sLen":0,"time":81473.68,"type":0},{"id":3,"sLen":0,"time":81631.58,"type":0},{"id":1,"sLen":0,"time":81789.47,"type":0},{"id":2,"sLen":0,"time":82105.26,"type":0},{"id":0,"sLen":0,"time":82736.8359,"type":0},{"id":3,"sLen":0,"time":82894.7344,"type":0},{"id":1,"sLen":0,"time":83052.625,"type":0},{"id":2,"sLen":0,"time":83368.4141,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84157.89,"type":0},{"id":1,"sLen":0,"time":84315.79,"type":0},{"id":2,"sLen":0,"time":84631.58,"type":0},{"id":0,"sLen":0,"time":85263.16,"type":0},{"id":3,"sLen":0,"time":85421.05,"type":0},{"id":1,"sLen":0,"time":85578.9453,"type":0},{"id":3,"sLen":157.89473,"time":90947.37,"type":0},{"id":1,"sLen":157.89473,"time":91578.9453,"type":0},{"id":0,"sLen":0,"time":91894.7344,"type":0},{"id":3,"sLen":0,"time":92210.52,"type":0},{"id":3,"sLen":0,"time":92684.2,"type":0},{"id":3,"sLen":0,"time":93157.89,"type":0},{"id":3,"sLen":0,"time":93473.68,"type":0},{"id":3,"sLen":0,"time":93789.47,"type":0},{"id":1,"sLen":0,"time":94105.26,"type":0},{"id":0,"sLen":0,"time":94421.05,"type":0},{"id":3,"sLen":0,"time":94736.8359,"type":0},{"id":3,"sLen":0,"time":95210.52,"type":0},{"id":3,"sLen":0,"time":95684.2,"type":0}],"characters":["spirit"],"visible":true,"type":0},{"position":"boyfriend","notes":[{"id":3,"sLen":157.89473,"time":5052.63135,"type":0},{"id":1,"sLen":157.89473,"time":5684.21045,"type":0},{"id":0,"sLen":0,"time":5999.99951,"type":0},{"id":3,"sLen":0,"time":6315.789,"type":0},{"id":3,"sLen":0,"time":6789.473,"type":0},{"id":3,"sLen":0,"time":7263.15771,"type":0},{"id":3,"sLen":0,"time":7578.94727,"type":0},{"id":3,"sLen":0,"time":7894.73633,"type":0},{"id":1,"sLen":0,"time":8210.526,"type":0},{"id":0,"sLen":0,"time":8526.315,"type":0},{"id":3,"sLen":0,"time":8842.10449,"type":0},{"id":3,"sLen":0,"time":9315.789,"type":0},{"id":3,"sLen":0,"time":9789.474,"type":0},{"id":2,"sLen":157.89473,"time":15157.8945,"type":0},{"id":2,"sLen":0,"time":15789.4727,"type":0},{"id":3,"sLen":0,"time":15947.3682,"type":0},{"id":2,"sLen":157.89473,"time":16421.0527,"type":0},{"id":2,"sLen":157.89473,"time":16894.7363,"type":0},{"id":2,"sLen":157.89473,"time":17368.42,"type":0},{"id":2,"sLen":157.89473,"time":17684.209,"type":0},{"id":2,"sLen":0,"time":18315.79,"type":0},{"id":3,"sLen":0,"time":18473.6836,"type":0},{"id":2,"sLen":157.89473,"time":18947.3672,"type":0},{"id":2,"sLen":157.89473,"time":19421.05,"type":0},{"id":0,"sLen":157.89473,"time":19894.7363,"type":0},{"id":0,"sLen":0,"time":24789.4727,"type":0},{"id":1,"sLen":0,"time":25263.1563,"type":0},{"id":3,"sLen":0,"time":25578.9453,"type":0},{"id":3,"sLen":0,"time":26052.63,"type":0},{"id":2,"sLen":0,"time":26526.3145,"type":0},{"id":2,"sLen":0,"time":26999.998,"type":0},{"id":3,"sLen":0,"time":27473.6836,"type":0},{"id":1,"sLen":0,"time":27789.4727,"type":0},{"id":3,"sLen":0,"time":28105.2617,"type":0},{"id":3,"sLen":0,"time":28578.9453,"type":0},{"id":2,"sLen":0,"time":29052.63,"type":0},{"id":2,"sLen":0,"time":29526.3145,"type":0},{"id":3,"sLen":0,"time":29999.998,"type":0},{"id":2,"sLen":0,"time":35368.418,"type":0},{"id":3,"sLen":0,"time":35684.207,"type":0},{"id":3,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36315.79,"type":0},{"id":0,"sLen":0,"time":36631.58,"type":0},{"id":0,"sLen":0,"time":36947.3672,"type":0},{"id":3,"sLen":0,"time":37263.1563,"type":0},{"id":1,"sLen":0,"time":37578.9453,"type":0},{"id":0,"sLen":0,"time":37894.7344,"type":0},{"id":0,"sLen":0,"time":38210.5234,"type":0},{"id":0,"sLen":0,"time":38526.3125,"type":0},{"id":3,"sLen":0,"time":38842.1,"type":0},{"id":1,"sLen":0,"time":39157.8945,"type":0},{"id":1,"sLen":0,"time":39473.6836,"type":0},{"id":1,"sLen":0,"time":39789.4727,"type":0},{"id":1,"sLen":236.842100000002,"time":40105.26,"type":0},{"id":0,"sLen":0,"time":45473.6836,"type":0},{"id":0,"sLen":0,"time":45789.4727,"type":0},{"id":3,"sLen":0,"time":46105.26,"type":0},{"id":0,"sLen":0,"time":46421.05,"type":0},{"id":2,"sLen":0,"time":46736.84,"type":0},{"id":2,"sLen":0,"time":47368.418,"type":0},{"id":0,"sLen":0,"time":47684.207,"type":0},{"id":0,"sLen":0,"time":47999.9961,"type":0},{"id":0,"sLen":0,"time":48315.79,"type":0},{"id":3,"sLen":0,"time":48631.58,"type":0},{"id":0,"sLen":0,"time":48947.3672,"type":0},{"id":2,"sLen":0,"time":49263.1563,"type":0},{"id":2,"sLen":0,"time":49894.7344,"type":0},{"id":3,"sLen":0,"time":50210.5234,"type":0},{"id":3,"sLen":157.89473,"time":55578.9453,"type":0},{"id":1,"sLen":157.89473,"time":56210.5234,"type":0},{"id":0,"sLen":0,"time":56526.3125,"type":0},{"id":3,"sLen":0,"time":56842.1,"type":0},{"id":3,"sLen":0,"time":57315.7852,"type":0},{"id":3,"sLen":0,"time":57789.4727,"type":0},{"id":3,"sLen":0,"time":58105.26,"type":0},{"id":3,"sLen":0,"time":58421.05,"type":0},{"id":1,"sLen":0,"time":58736.84,"type":0},{"id":0,"sLen":0,"time":59052.63,"type":0},{"id":3,"sLen":0,"time":59368.418,"type":0},{"id":3,"sLen":0,"time":59842.1,"type":0},{"id":3,"sLen":0,"time":60315.7852,"type":0},{"id":2,"sLen":0,"time":65684.21,"type":0},{"id":3,"sLen":0,"time":66000,"type":0},{"id":3,"sLen":0,"time":66315.79,"type":0},{"id":0,"sLen":0,"time":66631.58,"type":0},{"id":0,"sLen":0,"time":66947.37,"type":0},{"id":0,"sLen":0,"time":67263.16,"type":0},{"id":3,"sLen":0,"time":67578.9453,"type":0},{"id":1,"sLen":0,"time":67894.7344,"type":0},{"id":0,"sLen":0,"time":68210.52,"type":0},{"id":0,"sLen":0,"time":68526.31,"type":0},{"id":3,"sLen":0,"time":68684.21,"type":0},{"id":0,"sLen":0,"time":68842.1,"type":0},{"id":3,"sLen":0,"time":69157.89,"type":0},{"id":1,"sLen":0,"time":69315.79,"type":0},{"id":1,"sLen":0,"time":69473.68,"type":0},{"id":1,"sLen":0,"time":69789.47,"type":0},{"id":1,"sLen":0,"time":70105.26,"type":0},{"id":1,"sLen":236.842099999994,"time":70421.05,"type":0},{"id":0,"sLen":0,"time":75789.47,"type":0},{"id":0,"sLen":0,"time":76105.26,"type":0},{"id":3,"sLen":0,"time":76421.05,"type":0},{"id":0,"sLen":0,"time":76736.8359,"type":0},{"id":2,"sLen":0,"time":77052.625,"type":0},{"id":2,"sLen":0,"time":77684.2,"type":0},{"id":0,"sLen":0,"time":78000,"type":0},{"id":0,"sLen":0,"time":78315.79,"type":0},{"id":0,"sLen":0,"time":78631.58,"type":0},{"id":3,"sLen":0,"time":78947.37,"type":0},{"id":0,"sLen":0,"time":79263.16,"type":0},{"id":2,"sLen":0,"time":79578.9453,"type":0},{"id":2,"sLen":0,"time":80210.52,"type":0},{"id":3,"sLen":0,"time":80526.31,"type":0},{"id":2,"sLen":0,"time":85894.7344,"type":0},{"id":0,"sLen":0,"time":86526.31,"type":0},{"id":3,"sLen":0,"time":86684.2,"type":0},{"id":1,"sLen":0,"time":86842.1,"type":0},{"id":2,"sLen":0,"time":87157.89,"type":0},{"id":0,"sLen":0,"time":87789.47,"type":0},{"id":3,"sLen":0,"time":87947.37,"type":0},{"id":1,"sLen":0,"time":88105.26,"type":0},{"id":2,"sLen":0,"time":88421.05,"type":0},{"id":0,"sLen":0,"time":89052.625,"type":0},{"id":3,"sLen":0,"time":89210.52,"type":0},{"id":1,"sLen":0,"time":89368.4141,"type":0},{"id":2,"sLen":0,"time":89684.2,"type":0},{"id":0,"sLen":0,"time":90315.79,"type":0},{"id":3,"sLen":0,"time":90473.68,"type":0},{"id":1,"sLen":0,"time":90631.58,"type":0},{"id":3,"sLen":157.89473,"time":95999.99,"type":0},{"id":1,"sLen":157.89473,"time":96631.58,"type":0},{"id":0,"sLen":0,"time":96947.37,"type":0},{"id":3,"sLen":0,"time":97263.16,"type":0},{"id":3,"sLen":0,"time":97736.8359,"type":0},{"id":3,"sLen":0,"time":98210.52,"type":0},{"id":3,"sLen":0,"time":98526.31,"type":0},{"id":3,"sLen":0,"time":98842.1,"type":0},{"id":1,"sLen":0,"time":99157.89,"type":0},{"id":0,"sLen":0,"time":99473.68,"type":0},{"id":3,"sLen":0,"time":99789.47,"type":0},{"id":3,"sLen":0,"time":100263.156,"type":0},{"id":3,"sLen":0,"time":100736.836,"type":0}],"characters":["bf-pixel"],"visible":true,"type":1},{"position":"girlfriend","notes":[],"characters":["gf-pixel"],"visible":false,"type":2}],"stage":"school-evil","scrollSpeed":1.1,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/thorns/charts/hard.json b/assets/songs/thorns/charts/hard.json index f7de248f..0a554666 100644 --- a/assets/songs/thorns/charts/hard.json +++ b/assets/songs/thorns/charts/hard.json @@ -1 +1 @@ -{"codenameChart":true,"strumLines":[{"position":"dad","notes":[{"id":3,"sLen":157.89473,"time":0,"type":0},{"id":3,"sLen":0,"time":315.789459,"type":0},{"id":3,"sLen":0,"time":473.6842,"type":0},{"id":1,"sLen":157.89473,"time":631.5789,"type":0},{"id":0,"sLen":0,"time":947.3684,"type":0},{"id":3,"sLen":0,"time":1263.15784,"type":0},{"id":1,"sLen":0,"time":1578.94727,"type":0},{"id":3,"sLen":0,"time":1736.842,"type":0},{"id":1,"sLen":0,"time":2052.63159,"type":0},{"id":3,"sLen":0,"time":2210.52612,"type":0},{"id":3,"sLen":0,"time":2526.31567,"type":0},{"id":3,"sLen":0,"time":2684.21045,"type":0},{"id":3,"sLen":0,"time":2842.10522,"type":0},{"id":3,"sLen":0,"time":2999.99976,"type":0},{"id":1,"sLen":0,"time":3157.89453,"type":0},{"id":1,"sLen":0,"time":3315.78931,"type":0},{"id":0,"sLen":0,"time":3473.684,"type":0},{"id":0,"sLen":0,"time":3631.57886,"type":0},{"id":3,"sLen":0,"time":3789.47363,"type":0},{"id":1,"sLen":0,"time":4105.263,"type":0},{"id":3,"sLen":0,"time":4263.15771,"type":0},{"id":1,"sLen":0,"time":4578.94727,"type":0},{"id":3,"sLen":0,"time":4736.842,"type":0},{"id":2,"sLen":157.894729999998,"time":10105.2627,"type":0},{"id":1,"sLen":78.9473648000057,"time":10421.0518,"type":0},{"id":2,"sLen":0,"time":10736.8418,"type":0},{"id":3,"sLen":0,"time":10894.7363,"type":0},{"id":1,"sLen":78.9473647999985,"time":11052.6309,"type":0},{"id":2,"sLen":157.89473,"time":11368.4209,"type":0},{"id":1,"sLen":0,"time":11684.21,"type":0},{"id":2,"sLen":157.89473,"time":11842.1045,"type":0},{"id":1,"sLen":0,"time":12157.8945,"type":0},{"id":2,"sLen":157.89473,"time":12315.7891,"type":0},{"id":2,"sLen":157.89473,"time":12631.5781,"type":0},{"id":1,"sLen":78.9473648000003,"time":12947.3682,"type":0},{"id":2,"sLen":0,"time":13263.1572,"type":0},{"id":3,"sLen":0,"time":13421.0518,"type":0},{"id":1,"sLen":78.9473648000003,"time":13578.9463,"type":0},{"id":2,"sLen":157.89473,"time":13894.7363,"type":0},{"id":1,"sLen":0,"time":14210.5254,"type":0},{"id":2,"sLen":157.89473,"time":14368.42,"type":0},{"id":1,"sLen":0,"time":14684.21,"type":0},{"id":0,"sLen":157.89473,"time":14842.1045,"type":0},{"id":1,"sLen":0,"time":20210.5254,"type":0},{"id":0,"sLen":0,"time":20368.42,"type":0},{"id":3,"sLen":0,"time":20526.3145,"type":0},{"id":1,"sLen":0,"time":20684.209,"type":0},{"id":0,"sLen":0,"time":20842.1035,"type":0},{"id":3,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21157.8945,"type":0},{"id":0,"sLen":0,"time":21315.79,"type":0},{"id":2,"sLen":0,"time":21473.6836,"type":0},{"id":1,"sLen":0,"time":21631.5781,"type":0},{"id":0,"sLen":0,"time":21789.4727,"type":0},{"id":2,"sLen":0,"time":21947.3672,"type":0},{"id":1,"sLen":0,"time":22105.2617,"type":0},{"id":0,"sLen":0,"time":22263.1563,"type":0},{"id":3,"sLen":0,"time":22421.05,"type":0},{"id":1,"sLen":0,"time":22578.9473,"type":0},{"id":1,"sLen":0,"time":22736.8418,"type":0},{"id":0,"sLen":0,"time":22894.7363,"type":0},{"id":3,"sLen":0,"time":23052.63,"type":0},{"id":1,"sLen":0,"time":23210.5254,"type":0},{"id":0,"sLen":0,"time":23368.42,"type":0},{"id":3,"sLen":0,"time":23526.3145,"type":0},{"id":1,"sLen":0,"time":23684.209,"type":0},{"id":0,"sLen":0,"time":23842.1035,"type":0},{"id":2,"sLen":0,"time":23999.998,"type":0},{"id":1,"sLen":0,"time":24157.8945,"type":0},{"id":0,"sLen":0,"time":24315.79,"type":0},{"id":2,"sLen":0,"time":24473.6836,"type":0},{"id":1,"sLen":0,"time":24631.5781,"type":0},{"id":0,"sLen":0,"time":24789.4727,"type":0},{"id":3,"sLen":0,"time":24947.3672,"type":0},{"id":1,"sLen":0,"time":25105.2617,"type":0},{"id":2,"sLen":0,"time":30315.79,"type":0},{"id":0,"sLen":0,"time":30473.6836,"type":0},{"id":3,"sLen":0,"time":30631.5781,"type":0},{"id":1,"sLen":0,"time":30789.4727,"type":0},{"id":3,"sLen":236.842100000002,"time":30947.3672,"type":0},{"id":0,"sLen":0,"time":31263.1563,"type":0},{"id":3,"sLen":0,"time":31421.05,"type":0},{"id":0,"sLen":0,"time":31578.9453,"type":0},{"id":2,"sLen":0,"time":31736.84,"type":0},{"id":0,"sLen":0,"time":31894.7363,"type":0},{"id":1,"sLen":0,"time":32052.63,"type":0},{"id":3,"sLen":236.842100000002,"time":32210.5254,"type":0},{"id":1,"sLen":0,"time":32526.3145,"type":0},{"id":3,"sLen":0,"time":32684.209,"type":0},{"id":0,"sLen":0,"time":32842.1055,"type":0},{"id":2,"sLen":0,"time":33000,"type":0},{"id":0,"sLen":0,"time":33157.8945,"type":0},{"id":3,"sLen":0,"time":33315.79,"type":0},{"id":0,"sLen":0,"time":33473.6836,"type":0},{"id":2,"sLen":0,"time":33631.58,"type":0},{"id":3,"sLen":0,"time":33789.4727,"type":0},{"id":1,"sLen":0,"time":33947.3672,"type":0},{"id":1,"sLen":0,"time":34105.26,"type":0},{"id":0,"sLen":0,"time":34263.1563,"type":0},{"id":1,"sLen":0,"time":34421.05,"type":0},{"id":3,"sLen":0,"time":34578.9453,"type":0},{"id":1,"sLen":0,"time":34736.84,"type":0},{"id":0,"sLen":0,"time":34894.7344,"type":0},{"id":1,"sLen":236.842100000002,"time":35052.63,"type":0},{"id":0,"sLen":0,"time":40421.05,"type":0},{"id":1,"sLen":0,"time":40578.9453,"type":0},{"id":0,"sLen":0,"time":40736.84,"type":0},{"id":2,"sLen":0,"time":40894.7344,"type":0},{"id":3,"sLen":0,"time":41052.63,"type":0},{"id":1,"sLen":0,"time":41210.5234,"type":0},{"id":0,"sLen":0,"time":41368.418,"type":0},{"id":3,"sLen":0,"time":41526.3125,"type":0},{"id":2,"sLen":0,"time":41684.207,"type":0},{"id":0,"sLen":0,"time":41842.1,"type":0},{"id":1,"sLen":0,"time":42000,"type":0},{"id":0,"sLen":0,"time":42157.8945,"type":0},{"id":2,"sLen":0,"time":42315.79,"type":0},{"id":3,"sLen":0,"time":42473.6836,"type":0},{"id":0,"sLen":0,"time":42631.58,"type":0},{"id":3,"sLen":0,"time":42789.4727,"type":0},{"id":0,"sLen":0,"time":42947.3672,"type":0},{"id":1,"sLen":0,"time":43105.26,"type":0},{"id":0,"sLen":0,"time":43263.1563,"type":0},{"id":2,"sLen":0,"time":43421.05,"type":0},{"id":3,"sLen":0,"time":43578.9453,"type":0},{"id":1,"sLen":0,"time":43736.84,"type":0},{"id":0,"sLen":0,"time":43894.7344,"type":0},{"id":3,"sLen":0,"time":44052.63,"type":0},{"id":2,"sLen":0,"time":44210.5234,"type":0},{"id":0,"sLen":0,"time":44368.418,"type":0},{"id":1,"sLen":0,"time":44526.3125,"type":0},{"id":0,"sLen":0,"time":44684.207,"type":0},{"id":2,"sLen":0,"time":44842.1,"type":0},{"id":0,"sLen":0,"time":44999.9961,"type":0},{"id":3,"sLen":0,"time":45157.8945,"type":0},{"id":0,"sLen":0,"time":45315.79,"type":0},{"id":3,"sLen":157.89473,"time":50526.3125,"type":0},{"id":3,"sLen":0,"time":50842.1,"type":0},{"id":3,"sLen":0,"time":50999.9961,"type":0},{"id":1,"sLen":157.89473,"time":51157.89,"type":0},{"id":0,"sLen":0,"time":51473.6836,"type":0},{"id":3,"sLen":0,"time":51789.4727,"type":0},{"id":1,"sLen":0,"time":52105.26,"type":0},{"id":3,"sLen":0,"time":52263.1563,"type":0},{"id":1,"sLen":0,"time":52578.9453,"type":0},{"id":3,"sLen":0,"time":52736.84,"type":0},{"id":3,"sLen":0,"time":53052.63,"type":0},{"id":3,"sLen":0,"time":53210.5234,"type":0},{"id":3,"sLen":0,"time":53368.418,"type":0},{"id":3,"sLen":0,"time":53526.3125,"type":0},{"id":1,"sLen":0,"time":53684.207,"type":0},{"id":1,"sLen":0,"time":53842.1,"type":0},{"id":0,"sLen":0,"time":53999.9961,"type":0},{"id":0,"sLen":0,"time":54157.89,"type":0},{"id":3,"sLen":0,"time":54315.7852,"type":0},{"id":1,"sLen":0,"time":54631.58,"type":0},{"id":3,"sLen":0,"time":54789.4727,"type":0},{"id":1,"sLen":0,"time":55105.26,"type":0},{"id":3,"sLen":0,"time":55263.1563,"type":0},{"id":2,"sLen":0,"time":60631.58,"type":0},{"id":0,"sLen":0,"time":60789.4727,"type":0},{"id":3,"sLen":0,"time":60947.3672,"type":0},{"id":1,"sLen":0,"time":61105.26,"type":0},{"id":3,"sLen":0,"time":61263.1563,"type":0},{"id":3,"sLen":0,"time":61421.05,"type":0},{"id":0,"sLen":0,"time":61578.9453,"type":0},{"id":3,"sLen":0,"time":61736.84,"type":0},{"id":0,"sLen":0,"time":61894.7344,"type":0},{"id":2,"sLen":0,"time":62052.63,"type":0},{"id":0,"sLen":0,"time":62210.5234,"type":0},{"id":1,"sLen":0,"time":62368.418,"type":0},{"id":3,"sLen":0,"time":62526.3125,"type":0},{"id":3,"sLen":0,"time":62684.207,"type":0},{"id":1,"sLen":0,"time":62842.1,"type":0},{"id":3,"sLen":0,"time":62999.9961,"type":0},{"id":0,"sLen":0,"time":63157.89,"type":0},{"id":2,"sLen":0,"time":63315.7852,"type":0},{"id":0,"sLen":0,"time":63473.68,"type":0},{"id":3,"sLen":0,"time":63631.5742,"type":0},{"id":0,"sLen":0,"time":63789.4727,"type":0},{"id":2,"sLen":0,"time":63947.3672,"type":0},{"id":3,"sLen":0,"time":64105.26,"type":0},{"id":1,"sLen":0,"time":64263.1563,"type":0},{"id":1,"sLen":0,"time":64421.05,"type":0},{"id":0,"sLen":0,"time":64578.9453,"type":0},{"id":1,"sLen":0,"time":64736.84,"type":0},{"id":3,"sLen":0,"time":64894.7344,"type":0},{"id":1,"sLen":0,"time":65052.63,"type":0},{"id":0,"sLen":0,"time":65210.5234,"type":0},{"id":1,"sLen":236.842100000002,"time":65368.418,"type":0},{"id":0,"sLen":0,"time":70736.8359,"type":0},{"id":1,"sLen":0,"time":70894.7344,"type":0},{"id":0,"sLen":0,"time":71052.625,"type":0},{"id":2,"sLen":0,"time":71210.52,"type":0},{"id":3,"sLen":0,"time":71368.4141,"type":0},{"id":1,"sLen":0,"time":71526.31,"type":0},{"id":0,"sLen":0,"time":71684.21,"type":0},{"id":3,"sLen":0,"time":71842.1,"type":0},{"id":2,"sLen":0,"time":72000,"type":0},{"id":0,"sLen":0,"time":72157.89,"type":0},{"id":1,"sLen":0,"time":72315.79,"type":0},{"id":0,"sLen":0,"time":72473.68,"type":0},{"id":2,"sLen":0,"time":72631.58,"type":0},{"id":3,"sLen":0,"time":72789.47,"type":0},{"id":0,"sLen":0,"time":72947.37,"type":0},{"id":3,"sLen":0,"time":73105.26,"type":0},{"id":0,"sLen":0,"time":73263.16,"type":0},{"id":1,"sLen":0,"time":73421.05,"type":0},{"id":0,"sLen":0,"time":73578.9453,"type":0},{"id":2,"sLen":0,"time":73736.8359,"type":0},{"id":3,"sLen":0,"time":73894.7344,"type":0},{"id":1,"sLen":0,"time":74052.625,"type":0},{"id":0,"sLen":0,"time":74210.52,"type":0},{"id":3,"sLen":0,"time":74368.4141,"type":0},{"id":2,"sLen":0,"time":74526.31,"type":0},{"id":0,"sLen":0,"time":74684.21,"type":0},{"id":1,"sLen":0,"time":74842.1,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":2,"sLen":0,"time":75157.89,"type":0},{"id":0,"sLen":0,"time":75315.79,"type":0},{"id":3,"sLen":0,"time":75473.68,"type":0},{"id":0,"sLen":0,"time":75631.58,"type":0},{"id":2,"sLen":0,"time":80842.1,"type":0},{"id":3,"sLen":0,"time":81000,"type":0},{"id":1,"sLen":0,"time":81157.89,"type":0},{"id":3,"sLen":0,"time":81315.79,"type":0},{"id":0,"sLen":0,"time":81473.68,"type":0},{"id":3,"sLen":0,"time":81631.58,"type":0},{"id":1,"sLen":0,"time":81789.47,"type":0},{"id":1,"sLen":0,"time":81947.37,"type":0},{"id":2,"sLen":0,"time":82105.26,"type":0},{"id":3,"sLen":0,"time":82263.16,"type":0},{"id":1,"sLen":0,"time":82421.05,"type":0},{"id":3,"sLen":0,"time":82578.9453,"type":0},{"id":0,"sLen":0,"time":82736.8359,"type":0},{"id":3,"sLen":0,"time":82894.7344,"type":0},{"id":1,"sLen":0,"time":83052.625,"type":0},{"id":1,"sLen":0,"time":83210.52,"type":0},{"id":2,"sLen":0,"time":83368.4141,"type":0},{"id":3,"sLen":0,"time":83526.31,"type":0},{"id":1,"sLen":0,"time":83684.2,"type":0},{"id":3,"sLen":0,"time":83842.1,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84157.89,"type":0},{"id":1,"sLen":0,"time":84315.79,"type":0},{"id":1,"sLen":0,"time":84473.68,"type":0},{"id":2,"sLen":0,"time":84631.58,"type":0},{"id":3,"sLen":0,"time":84789.47,"type":0},{"id":1,"sLen":0,"time":84947.37,"type":0},{"id":3,"sLen":0,"time":85105.26,"type":0},{"id":0,"sLen":0,"time":85263.16,"type":0},{"id":3,"sLen":0,"time":85421.05,"type":0},{"id":1,"sLen":0,"time":85578.9453,"type":0},{"id":1,"sLen":0,"time":85736.8359,"type":0},{"id":3,"sLen":157.89473,"time":90947.37,"type":0},{"id":3,"sLen":0,"time":91263.16,"type":0},{"id":3,"sLen":0,"time":91421.05,"type":0},{"id":1,"sLen":157.89473,"time":91578.9453,"type":0},{"id":0,"sLen":0,"time":91894.7344,"type":0},{"id":3,"sLen":0,"time":92210.52,"type":0},{"id":1,"sLen":0,"time":92526.31,"type":0},{"id":3,"sLen":0,"time":92684.2,"type":0},{"id":1,"sLen":0,"time":92999.99,"type":0},{"id":3,"sLen":0,"time":93157.89,"type":0},{"id":3,"sLen":0,"time":93473.68,"type":0},{"id":3,"sLen":0,"time":93631.58,"type":0},{"id":3,"sLen":0,"time":93789.47,"type":0},{"id":3,"sLen":0,"time":93947.37,"type":0},{"id":1,"sLen":0,"time":94105.26,"type":0},{"id":1,"sLen":0,"time":94263.16,"type":0},{"id":0,"sLen":0,"time":94421.05,"type":0},{"id":0,"sLen":0,"time":94578.9453,"type":0},{"id":3,"sLen":0,"time":94736.8359,"type":0},{"id":1,"sLen":0,"time":95052.625,"type":0},{"id":3,"sLen":0,"time":95210.52,"type":0},{"id":1,"sLen":0,"time":95526.31,"type":0},{"id":3,"sLen":0,"time":95684.2,"type":0}],"characters":["spirit"],"visible":true,"type":0},{"position":"boyfriend","notes":[{"id":3,"sLen":157.89473,"time":5052.63135,"type":0},{"id":3,"sLen":0,"time":5368.421,"type":0},{"id":3,"sLen":0,"time":5526.31543,"type":0},{"id":1,"sLen":157.89473,"time":5684.21045,"type":0},{"id":0,"sLen":0,"time":5999.99951,"type":0},{"id":3,"sLen":0,"time":6315.789,"type":0},{"id":1,"sLen":0,"time":6631.57861,"type":0},{"id":3,"sLen":0,"time":6789.473,"type":0},{"id":1,"sLen":0,"time":7105.2627,"type":0},{"id":3,"sLen":0,"time":7263.15771,"type":0},{"id":3,"sLen":0,"time":7578.94727,"type":0},{"id":3,"sLen":0,"time":7736.842,"type":0},{"id":3,"sLen":0,"time":7894.73633,"type":0},{"id":3,"sLen":0,"time":8052.63135,"type":0},{"id":1,"sLen":0,"time":8210.526,"type":0},{"id":1,"sLen":0,"time":8368.421,"type":0},{"id":0,"sLen":0,"time":8526.315,"type":0},{"id":0,"sLen":0,"time":8684.21,"type":0},{"id":3,"sLen":0,"time":8842.10449,"type":0},{"id":1,"sLen":0,"time":9157.895,"type":0},{"id":3,"sLen":0,"time":9315.789,"type":0},{"id":1,"sLen":0,"time":9631.578,"type":0},{"id":3,"sLen":0,"time":9789.474,"type":0},{"id":2,"sLen":157.89473,"time":15157.8945,"type":0},{"id":1,"sLen":78.9473648000003,"time":15473.6836,"type":0},{"id":2,"sLen":0,"time":15789.4727,"type":0},{"id":3,"sLen":0,"time":15947.3682,"type":0},{"id":1,"sLen":78.9473648000003,"time":16105.2627,"type":0},{"id":2,"sLen":157.89473,"time":16421.0527,"type":0},{"id":1,"sLen":0,"time":16736.8418,"type":0},{"id":2,"sLen":157.89473,"time":16894.7363,"type":0},{"id":1,"sLen":0,"time":17210.5254,"type":0},{"id":2,"sLen":157.89473,"time":17368.42,"type":0},{"id":2,"sLen":157.89473,"time":17684.209,"type":0},{"id":1,"sLen":78.9473647999985,"time":18000,"type":0},{"id":2,"sLen":0,"time":18315.79,"type":0},{"id":3,"sLen":0,"time":18473.6836,"type":0},{"id":1,"sLen":78.9473647999985,"time":18631.5781,"type":0},{"id":2,"sLen":157.89473,"time":18947.3672,"type":0},{"id":1,"sLen":0,"time":19263.1563,"type":0},{"id":2,"sLen":157.89473,"time":19421.05,"type":0},{"id":1,"sLen":0,"time":19736.8418,"type":0},{"id":0,"sLen":157.89473,"time":19894.7363,"type":0},{"id":3,"sLen":0,"time":24789.4727,"type":0},{"id":2,"sLen":0,"time":24947.3672,"type":0},{"id":0,"sLen":0,"time":25105.2617,"type":0},{"id":1,"sLen":0,"time":25263.1563,"type":0},{"id":0,"sLen":0,"time":25421.05,"type":0},{"id":3,"sLen":0,"time":25578.9453,"type":0},{"id":1,"sLen":0,"time":25736.8418,"type":0},{"id":0,"sLen":0,"time":25894.7363,"type":0},{"id":3,"sLen":0,"time":26052.63,"type":0},{"id":1,"sLen":0,"time":26210.5254,"type":0},{"id":0,"sLen":0,"time":26368.42,"type":0},{"id":2,"sLen":0,"time":26526.3145,"type":0},{"id":1,"sLen":0,"time":26684.209,"type":0},{"id":0,"sLen":0,"time":26842.1035,"type":0},{"id":2,"sLen":0,"time":26999.998,"type":0},{"id":1,"sLen":0,"time":27157.8926,"type":0},{"id":0,"sLen":0,"time":27315.79,"type":0},{"id":3,"sLen":0,"time":27473.6836,"type":0},{"id":1,"sLen":0,"time":27631.5781,"type":0},{"id":1,"sLen":0,"time":27789.4727,"type":0},{"id":0,"sLen":0,"time":27947.3672,"type":0},{"id":3,"sLen":0,"time":28105.2617,"type":0},{"id":1,"sLen":0,"time":28263.1563,"type":0},{"id":0,"sLen":0,"time":28421.05,"type":0},{"id":3,"sLen":0,"time":28578.9453,"type":0},{"id":1,"sLen":0,"time":28736.84,"type":0},{"id":0,"sLen":0,"time":28894.7363,"type":0},{"id":2,"sLen":0,"time":29052.63,"type":0},{"id":1,"sLen":0,"time":29210.5254,"type":0},{"id":0,"sLen":0,"time":29368.42,"type":0},{"id":2,"sLen":0,"time":29526.3145,"type":0},{"id":1,"sLen":0,"time":29684.209,"type":0},{"id":0,"sLen":0,"time":29842.1035,"type":0},{"id":3,"sLen":0,"time":29999.998,"type":0},{"id":1,"sLen":0,"time":30157.8926,"type":0},{"id":2,"sLen":0,"time":35368.418,"type":0},{"id":0,"sLen":0,"time":35526.3125,"type":0},{"id":3,"sLen":0,"time":35684.207,"type":0},{"id":1,"sLen":0,"time":35842.1055,"type":0},{"id":3,"sLen":236.842100000002,"time":36000,"type":0},{"id":0,"sLen":0,"time":36315.79,"type":0},{"id":3,"sLen":0,"time":36473.6836,"type":0},{"id":0,"sLen":0,"time":36631.58,"type":0},{"id":2,"sLen":0,"time":36789.4727,"type":0},{"id":0,"sLen":0,"time":36947.3672,"type":0},{"id":1,"sLen":0,"time":37105.26,"type":0},{"id":3,"sLen":236.842100000002,"time":37263.1563,"type":0},{"id":1,"sLen":0,"time":37578.9453,"type":0},{"id":3,"sLen":0,"time":37736.84,"type":0},{"id":0,"sLen":0,"time":37894.7344,"type":0},{"id":2,"sLen":0,"time":38052.63,"type":0},{"id":0,"sLen":0,"time":38210.5234,"type":0},{"id":3,"sLen":0,"time":38368.418,"type":0},{"id":0,"sLen":0,"time":38526.3125,"type":0},{"id":2,"sLen":0,"time":38684.207,"type":0},{"id":3,"sLen":0,"time":38842.1,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":1,"sLen":0,"time":39157.8945,"type":0},{"id":0,"sLen":0,"time":39315.79,"type":0},{"id":1,"sLen":0,"time":39473.6836,"type":0},{"id":3,"sLen":0,"time":39631.58,"type":0},{"id":1,"sLen":0,"time":39789.4727,"type":0},{"id":0,"sLen":0,"time":39947.3672,"type":0},{"id":1,"sLen":236.842100000002,"time":40105.26,"type":0},{"id":0,"sLen":0,"time":45473.6836,"type":0},{"id":1,"sLen":0,"time":45631.58,"type":0},{"id":0,"sLen":0,"time":45789.4727,"type":0},{"id":2,"sLen":0,"time":45947.3672,"type":0},{"id":3,"sLen":0,"time":46105.26,"type":0},{"id":1,"sLen":0,"time":46263.1563,"type":0},{"id":0,"sLen":0,"time":46421.05,"type":0},{"id":3,"sLen":0,"time":46578.9453,"type":0},{"id":2,"sLen":0,"time":46736.84,"type":0},{"id":0,"sLen":0,"time":46894.7344,"type":0},{"id":1,"sLen":0,"time":47052.63,"type":0},{"id":0,"sLen":0,"time":47210.5234,"type":0},{"id":2,"sLen":0,"time":47368.418,"type":0},{"id":3,"sLen":0,"time":47526.3125,"type":0},{"id":0,"sLen":0,"time":47684.207,"type":0},{"id":3,"sLen":0,"time":47842.1,"type":0},{"id":0,"sLen":0,"time":47999.9961,"type":0},{"id":1,"sLen":0,"time":48157.89,"type":0},{"id":0,"sLen":0,"time":48315.79,"type":0},{"id":2,"sLen":0,"time":48473.6836,"type":0},{"id":3,"sLen":0,"time":48631.58,"type":0},{"id":1,"sLen":0,"time":48789.4727,"type":0},{"id":0,"sLen":0,"time":48947.3672,"type":0},{"id":3,"sLen":0,"time":49105.26,"type":0},{"id":2,"sLen":0,"time":49263.1563,"type":0},{"id":0,"sLen":0,"time":49421.05,"type":0},{"id":1,"sLen":0,"time":49578.9453,"type":0},{"id":0,"sLen":0,"time":49736.84,"type":0},{"id":2,"sLen":0,"time":49894.7344,"type":0},{"id":0,"sLen":0,"time":50052.63,"type":0},{"id":3,"sLen":0,"time":50210.5234,"type":0},{"id":0,"sLen":0,"time":50368.418,"type":0},{"id":3,"sLen":157.89473,"time":55578.9453,"type":0},{"id":3,"sLen":0,"time":55894.7344,"type":0},{"id":3,"sLen":0,"time":56052.63,"type":0},{"id":1,"sLen":157.89473,"time":56210.5234,"type":0},{"id":0,"sLen":0,"time":56526.3125,"type":0},{"id":3,"sLen":0,"time":56842.1,"type":0},{"id":1,"sLen":0,"time":57157.89,"type":0},{"id":3,"sLen":0,"time":57315.7852,"type":0},{"id":1,"sLen":0,"time":57631.58,"type":0},{"id":3,"sLen":0,"time":57789.4727,"type":0},{"id":3,"sLen":0,"time":58105.26,"type":0},{"id":3,"sLen":0,"time":58263.1563,"type":0},{"id":3,"sLen":0,"time":58421.05,"type":0},{"id":3,"sLen":0,"time":58578.9453,"type":0},{"id":1,"sLen":0,"time":58736.84,"type":0},{"id":1,"sLen":0,"time":58894.7344,"type":0},{"id":0,"sLen":0,"time":59052.63,"type":0},{"id":0,"sLen":0,"time":59210.5234,"type":0},{"id":3,"sLen":0,"time":59368.418,"type":0},{"id":1,"sLen":0,"time":59684.207,"type":0},{"id":3,"sLen":0,"time":59842.1,"type":0},{"id":1,"sLen":0,"time":60157.89,"type":0},{"id":3,"sLen":0,"time":60315.7852,"type":0},{"id":2,"sLen":0,"time":65684.21,"type":0},{"id":0,"sLen":0,"time":65842.1,"type":0},{"id":3,"sLen":0,"time":66000,"type":0},{"id":1,"sLen":0,"time":66157.89,"type":0},{"id":3,"sLen":0,"time":66315.79,"type":0},{"id":3,"sLen":0,"time":66473.68,"type":0},{"id":0,"sLen":0,"time":66631.58,"type":0},{"id":3,"sLen":0,"time":66789.47,"type":0},{"id":0,"sLen":0,"time":66947.37,"type":0},{"id":2,"sLen":0,"time":67105.26,"type":0},{"id":0,"sLen":0,"time":67263.16,"type":0},{"id":1,"sLen":0,"time":67421.05,"type":0},{"id":3,"sLen":0,"time":67578.9453,"type":0},{"id":3,"sLen":0,"time":67736.8359,"type":0},{"id":1,"sLen":0,"time":67894.7344,"type":0},{"id":3,"sLen":0,"time":68052.625,"type":0},{"id":0,"sLen":0,"time":68210.52,"type":0},{"id":2,"sLen":0,"time":68368.4141,"type":0},{"id":0,"sLen":0,"time":68526.31,"type":0},{"id":3,"sLen":0,"time":68684.21,"type":0},{"id":0,"sLen":0,"time":68842.1,"type":0},{"id":2,"sLen":0,"time":69000,"type":0},{"id":3,"sLen":0,"time":69157.89,"type":0},{"id":1,"sLen":0,"time":69315.79,"type":0},{"id":1,"sLen":0,"time":69473.68,"type":0},{"id":0,"sLen":0,"time":69631.58,"type":0},{"id":1,"sLen":0,"time":69789.47,"type":0},{"id":3,"sLen":0,"time":69947.37,"type":0},{"id":1,"sLen":0,"time":70105.26,"type":0},{"id":0,"sLen":0,"time":70263.16,"type":0},{"id":1,"sLen":236.842099999994,"time":70421.05,"type":0},{"id":0,"sLen":0,"time":75789.47,"type":0},{"id":1,"sLen":0,"time":75947.37,"type":0},{"id":0,"sLen":0,"time":76105.26,"type":0},{"id":2,"sLen":0,"time":76263.16,"type":0},{"id":3,"sLen":0,"time":76421.05,"type":0},{"id":1,"sLen":0,"time":76578.9453,"type":0},{"id":0,"sLen":0,"time":76736.8359,"type":0},{"id":3,"sLen":0,"time":76894.7344,"type":0},{"id":2,"sLen":0,"time":77052.625,"type":0},{"id":0,"sLen":0,"time":77210.52,"type":0},{"id":1,"sLen":0,"time":77368.4141,"type":0},{"id":0,"sLen":0,"time":77526.31,"type":0},{"id":2,"sLen":0,"time":77684.2,"type":0},{"id":3,"sLen":0,"time":77842.1,"type":0},{"id":0,"sLen":0,"time":78000,"type":0},{"id":3,"sLen":0,"time":78157.89,"type":0},{"id":0,"sLen":0,"time":78315.79,"type":0},{"id":1,"sLen":0,"time":78473.68,"type":0},{"id":0,"sLen":0,"time":78631.58,"type":0},{"id":2,"sLen":0,"time":78789.47,"type":0},{"id":3,"sLen":0,"time":78947.37,"type":0},{"id":1,"sLen":0,"time":79105.26,"type":0},{"id":0,"sLen":0,"time":79263.16,"type":0},{"id":3,"sLen":0,"time":79421.05,"type":0},{"id":2,"sLen":0,"time":79578.9453,"type":0},{"id":0,"sLen":0,"time":79736.8359,"type":0},{"id":1,"sLen":0,"time":79894.7344,"type":0},{"id":0,"sLen":0,"time":80052.625,"type":0},{"id":2,"sLen":0,"time":80210.52,"type":0},{"id":0,"sLen":0,"time":80368.4141,"type":0},{"id":3,"sLen":0,"time":80526.31,"type":0},{"id":0,"sLen":0,"time":80684.2,"type":0},{"id":2,"sLen":0,"time":85894.7344,"type":0},{"id":3,"sLen":0,"time":86052.625,"type":0},{"id":1,"sLen":0,"time":86210.52,"type":0},{"id":3,"sLen":0,"time":86368.4141,"type":0},{"id":0,"sLen":0,"time":86526.31,"type":0},{"id":3,"sLen":0,"time":86684.2,"type":0},{"id":1,"sLen":0,"time":86842.1,"type":0},{"id":1,"sLen":0,"time":86999.99,"type":0},{"id":2,"sLen":0,"time":87157.89,"type":0},{"id":3,"sLen":0,"time":87315.79,"type":0},{"id":1,"sLen":0,"time":87473.68,"type":0},{"id":3,"sLen":0,"time":87631.58,"type":0},{"id":0,"sLen":0,"time":87789.47,"type":0},{"id":3,"sLen":0,"time":87947.37,"type":0},{"id":1,"sLen":0,"time":88105.26,"type":0},{"id":1,"sLen":0,"time":88263.16,"type":0},{"id":2,"sLen":0,"time":88421.05,"type":0},{"id":3,"sLen":0,"time":88578.9453,"type":0},{"id":1,"sLen":0,"time":88736.8359,"type":0},{"id":3,"sLen":0,"time":88894.7344,"type":0},{"id":0,"sLen":0,"time":89052.625,"type":0},{"id":3,"sLen":0,"time":89210.52,"type":0},{"id":1,"sLen":0,"time":89368.4141,"type":0},{"id":1,"sLen":0,"time":89526.31,"type":0},{"id":2,"sLen":0,"time":89684.2,"type":0},{"id":3,"sLen":0,"time":89842.1,"type":0},{"id":1,"sLen":0,"time":89999.99,"type":0},{"id":3,"sLen":0,"time":90157.89,"type":0},{"id":0,"sLen":0,"time":90315.79,"type":0},{"id":3,"sLen":0,"time":90473.68,"type":0},{"id":1,"sLen":0,"time":90631.58,"type":0},{"id":1,"sLen":0,"time":90789.47,"type":0},{"id":3,"sLen":157.89473,"time":95999.99,"type":0},{"id":3,"sLen":0,"time":96315.78,"type":0},{"id":3,"sLen":0,"time":96473.68,"type":0},{"id":1,"sLen":157.89473,"time":96631.58,"type":0},{"id":0,"sLen":0,"time":96947.37,"type":0},{"id":3,"sLen":0,"time":97263.16,"type":0},{"id":1,"sLen":0,"time":97578.9453,"type":0},{"id":3,"sLen":0,"time":97736.8359,"type":0},{"id":1,"sLen":0,"time":98052.625,"type":0},{"id":3,"sLen":0,"time":98210.52,"type":0},{"id":3,"sLen":0,"time":98526.31,"type":0},{"id":3,"sLen":0,"time":98684.2,"type":0},{"id":3,"sLen":0,"time":98842.1,"type":0},{"id":3,"sLen":0,"time":98999.99,"type":0},{"id":1,"sLen":0,"time":99157.89,"type":0},{"id":1,"sLen":0,"time":99315.78,"type":0},{"id":0,"sLen":0,"time":99473.68,"type":0},{"id":0,"sLen":0,"time":99631.58,"type":0},{"id":3,"sLen":0,"time":99789.47,"type":0},{"id":1,"sLen":0,"time":100105.258,"type":0},{"id":3,"sLen":0,"time":100263.156,"type":0},{"id":1,"sLen":0,"time":100578.945,"type":0},{"id":3,"sLen":0,"time":100736.836,"type":0}],"characters":["bf-pixel"],"visible":true,"type":1},{"position":"girlfriend","notes":[],"characters":["gf-pixel"],"visible":false,"type":2}],"stage":"school-evil","scrollSpeed":1.5,"noteTypes":[]} \ No newline at end of file +{"events":[{"params":[1],"time":5052.63157894737,"name":"Camera Movement"},{"params":[0],"time":10105.2631578947,"name":"Camera Movement"},{"params":[1],"time":15157.8947368421,"name":"Camera Movement"},{"params":[0],"time":20210.5263157895,"name":"Camera Movement"},{"params":[1],"time":25263.1578947368,"name":"Camera Movement"},{"params":[0],"time":30315.7894736842,"name":"Camera Movement"},{"params":[1],"time":35368.4210526316,"name":"Camera Movement"},{"params":[0],"time":40421.0526315789,"name":"Camera Movement"},{"params":[1],"time":45473.6842105263,"name":"Camera Movement"},{"params":[0],"time":50526.3157894737,"name":"Camera Movement"},{"params":[1],"time":55578.9473684211,"name":"Camera Movement"},{"params":[0],"time":60631.5789473684,"name":"Camera Movement"},{"params":[1],"time":65684.2105263158,"name":"Camera Movement"},{"params":[0],"time":70736.8421052632,"name":"Camera Movement"},{"params":[1],"time":75789.4736842105,"name":"Camera Movement"},{"params":[0],"time":80842.1052631579,"name":"Camera Movement"},{"params":[1],"time":85894.7368421053,"name":"Camera Movement"},{"params":[0],"time":90947.3684210526,"name":"Camera Movement"},{"params":[1],"time":96000,"name":"Camera Movement"}],"stage":"school-evil","strumLines":[{"keyCount":4,"notes":[{"id":3,"sLen":157.89473,"time":0,"type":0},{"id":3,"sLen":0,"time":315.789459,"type":0},{"id":3,"sLen":0,"time":473.6842,"type":0},{"id":1,"sLen":157.89473,"time":631.5789,"type":0},{"id":0,"sLen":0,"time":947.3684,"type":0},{"id":3,"sLen":0,"time":1263.15784,"type":0},{"id":1,"sLen":0,"time":1578.94727,"type":0},{"id":3,"sLen":0,"time":1736.842,"type":0},{"id":1,"sLen":0,"time":2052.63159,"type":0},{"id":3,"sLen":0,"time":2210.52612,"type":0},{"id":3,"sLen":0,"time":2526.31567,"type":0},{"id":3,"sLen":0,"time":2684.21045,"type":0},{"id":3,"sLen":0,"time":2842.10522,"type":0},{"id":3,"sLen":0,"time":2999.99976,"type":0},{"id":1,"sLen":0,"time":3157.89453,"type":0},{"id":1,"sLen":0,"time":3315.78931,"type":0},{"id":0,"sLen":0,"time":3473.684,"type":0},{"id":0,"sLen":0,"time":3631.57886,"type":0},{"id":3,"sLen":0,"time":3789.47363,"type":0},{"id":1,"sLen":0,"time":4105.263,"type":0},{"id":3,"sLen":0,"time":4263.15771,"type":0},{"id":1,"sLen":0,"time":4578.94727,"type":0},{"id":3,"sLen":0,"time":4736.842,"type":0},{"id":2,"sLen":157.894729999998,"time":10105.2627,"type":0},{"id":1,"sLen":78.9473648000076,"time":10421.0518,"type":0},{"id":2,"sLen":0,"time":10736.8418,"type":0},{"id":3,"sLen":0,"time":10894.7363,"type":0},{"id":1,"sLen":78.9473647999985,"time":11052.6309,"type":0},{"id":2,"sLen":157.89473,"time":11368.4209,"type":0},{"id":1,"sLen":0,"time":11684.21,"type":0},{"id":2,"sLen":157.89473,"time":11842.1045,"type":0},{"id":1,"sLen":0,"time":12157.8945,"type":0},{"id":2,"sLen":157.89473,"time":12315.7891,"type":0},{"id":2,"sLen":157.89473,"time":12631.5781,"type":0},{"id":1,"sLen":78.9473648000003,"time":12947.3682,"type":0},{"id":2,"sLen":0,"time":13263.1572,"type":0},{"id":3,"sLen":0,"time":13421.0518,"type":0},{"id":1,"sLen":78.9473648000003,"time":13578.9463,"type":0},{"id":2,"sLen":157.89473,"time":13894.7363,"type":0},{"id":1,"sLen":0,"time":14210.5254,"type":0},{"id":2,"sLen":157.89473,"time":14368.42,"type":0},{"id":1,"sLen":0,"time":14684.21,"type":0},{"id":0,"sLen":157.89473,"time":14842.1045,"type":0},{"id":1,"sLen":0,"time":20210.5254,"type":0},{"id":0,"sLen":0,"time":20368.42,"type":0},{"id":3,"sLen":0,"time":20526.3145,"type":0},{"id":1,"sLen":0,"time":20684.209,"type":0},{"id":0,"sLen":0,"time":20842.1035,"type":0},{"id":3,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21157.8945,"type":0},{"id":0,"sLen":0,"time":21315.79,"type":0},{"id":2,"sLen":0,"time":21473.6836,"type":0},{"id":1,"sLen":0,"time":21631.5781,"type":0},{"id":0,"sLen":0,"time":21789.4727,"type":0},{"id":2,"sLen":0,"time":21947.3672,"type":0},{"id":1,"sLen":0,"time":22105.2617,"type":0},{"id":0,"sLen":0,"time":22263.1563,"type":0},{"id":3,"sLen":0,"time":22421.05,"type":0},{"id":1,"sLen":0,"time":22578.9473,"type":0},{"id":1,"sLen":0,"time":22736.8418,"type":0},{"id":0,"sLen":0,"time":22894.7363,"type":0},{"id":3,"sLen":0,"time":23052.63,"type":0},{"id":1,"sLen":0,"time":23210.5254,"type":0},{"id":0,"sLen":0,"time":23368.42,"type":0},{"id":3,"sLen":0,"time":23526.3145,"type":0},{"id":1,"sLen":0,"time":23684.209,"type":0},{"id":0,"sLen":0,"time":23842.1035,"type":0},{"id":2,"sLen":0,"time":23999.998,"type":0},{"id":1,"sLen":0,"time":24157.8945,"type":0},{"id":0,"sLen":0,"time":24315.79,"type":0},{"id":2,"sLen":0,"time":24473.6836,"type":0},{"id":1,"sLen":0,"time":24631.5781,"type":0},{"id":0,"sLen":0,"time":24789.4727,"type":0},{"id":3,"sLen":0,"time":24947.3672,"type":0},{"id":1,"sLen":0,"time":25105.2617,"type":0},{"id":2,"sLen":0,"time":30315.79,"type":0},{"id":0,"sLen":0,"time":30473.6836,"type":0},{"id":3,"sLen":0,"time":30631.5781,"type":0},{"id":1,"sLen":0,"time":30789.4727,"type":0},{"id":3,"sLen":236.842100000002,"time":30947.3672,"type":0},{"id":0,"sLen":0,"time":31263.1563,"type":0},{"id":3,"sLen":0,"time":31421.05,"type":0},{"id":0,"sLen":0,"time":31578.9453,"type":0},{"id":2,"sLen":0,"time":31736.84,"type":0},{"id":0,"sLen":0,"time":31894.7363,"type":0},{"id":1,"sLen":0,"time":32052.63,"type":0},{"id":3,"sLen":236.842100000002,"time":32210.5254,"type":0},{"id":1,"sLen":0,"time":32526.3145,"type":0},{"id":3,"sLen":0,"time":32684.209,"type":0},{"id":0,"sLen":0,"time":32842.1055,"type":0},{"id":2,"sLen":0,"time":33000,"type":0},{"id":0,"sLen":0,"time":33157.8945,"type":0},{"id":3,"sLen":0,"time":33315.79,"type":0},{"id":0,"sLen":0,"time":33473.6836,"type":0},{"id":2,"sLen":0,"time":33631.58,"type":0},{"id":3,"sLen":0,"time":33789.4727,"type":0},{"id":1,"sLen":0,"time":33947.3672,"type":0},{"id":1,"sLen":0,"time":34105.26,"type":0},{"id":0,"sLen":0,"time":34263.1563,"type":0},{"id":1,"sLen":0,"time":34421.05,"type":0},{"id":3,"sLen":0,"time":34578.9453,"type":0},{"id":1,"sLen":0,"time":34736.84,"type":0},{"id":0,"sLen":0,"time":34894.7344,"type":0},{"id":1,"sLen":236.842100000002,"time":35052.63,"type":0},{"id":0,"sLen":0,"time":40421.05,"type":0},{"id":1,"sLen":0,"time":40578.9453,"type":0},{"id":0,"sLen":0,"time":40736.84,"type":0},{"id":2,"sLen":0,"time":40894.7344,"type":0},{"id":3,"sLen":0,"time":41052.63,"type":0},{"id":1,"sLen":0,"time":41210.5234,"type":0},{"id":0,"sLen":0,"time":41368.418,"type":0},{"id":3,"sLen":0,"time":41526.3125,"type":0},{"id":2,"sLen":0,"time":41684.207,"type":0},{"id":0,"sLen":0,"time":41842.1,"type":0},{"id":1,"sLen":0,"time":42000,"type":0},{"id":0,"sLen":0,"time":42157.8945,"type":0},{"id":2,"sLen":0,"time":42315.79,"type":0},{"id":3,"sLen":0,"time":42473.6836,"type":0},{"id":0,"sLen":0,"time":42631.58,"type":0},{"id":3,"sLen":0,"time":42789.4727,"type":0},{"id":0,"sLen":0,"time":42947.3672,"type":0},{"id":1,"sLen":0,"time":43105.26,"type":0},{"id":0,"sLen":0,"time":43263.1563,"type":0},{"id":2,"sLen":0,"time":43421.05,"type":0},{"id":3,"sLen":0,"time":43578.9453,"type":0},{"id":1,"sLen":0,"time":43736.84,"type":0},{"id":0,"sLen":0,"time":43894.7344,"type":0},{"id":3,"sLen":0,"time":44052.63,"type":0},{"id":2,"sLen":0,"time":44210.5234,"type":0},{"id":0,"sLen":0,"time":44368.418,"type":0},{"id":1,"sLen":0,"time":44526.3125,"type":0},{"id":0,"sLen":0,"time":44684.207,"type":0},{"id":2,"sLen":0,"time":44842.1,"type":0},{"id":0,"sLen":0,"time":44999.9961,"type":0},{"id":3,"sLen":0,"time":45157.8945,"type":0},{"id":0,"sLen":0,"time":45315.79,"type":0},{"id":3,"sLen":157.89473,"time":50526.3125,"type":0},{"id":3,"sLen":0,"time":50842.1,"type":0},{"id":3,"sLen":0,"time":50999.9961,"type":0},{"id":1,"sLen":157.89473,"time":51157.89,"type":0},{"id":0,"sLen":0,"time":51473.6836,"type":0},{"id":3,"sLen":0,"time":51789.4727,"type":0},{"id":1,"sLen":0,"time":52105.26,"type":0},{"id":3,"sLen":0,"time":52263.1563,"type":0},{"id":1,"sLen":0,"time":52578.9453,"type":0},{"id":3,"sLen":0,"time":52736.84,"type":0},{"id":3,"sLen":0,"time":53052.63,"type":0},{"id":3,"sLen":0,"time":53210.5234,"type":0},{"id":3,"sLen":0,"time":53368.418,"type":0},{"id":3,"sLen":0,"time":53526.3125,"type":0},{"id":1,"sLen":0,"time":53684.207,"type":0},{"id":1,"sLen":0,"time":53842.1,"type":0},{"id":0,"sLen":0,"time":53999.9961,"type":0},{"id":0,"sLen":0,"time":54157.89,"type":0},{"id":3,"sLen":0,"time":54315.7852,"type":0},{"id":1,"sLen":0,"time":54631.58,"type":0},{"id":3,"sLen":0,"time":54789.4727,"type":0},{"id":1,"sLen":0,"time":55105.26,"type":0},{"id":3,"sLen":0,"time":55263.1563,"type":0},{"id":2,"sLen":0,"time":60631.58,"type":0},{"id":0,"sLen":0,"time":60789.4727,"type":0},{"id":3,"sLen":0,"time":60947.3672,"type":0},{"id":1,"sLen":0,"time":61105.26,"type":0},{"id":3,"sLen":0,"time":61263.1563,"type":0},{"id":3,"sLen":0,"time":61421.05,"type":0},{"id":0,"sLen":0,"time":61578.9453,"type":0},{"id":3,"sLen":0,"time":61736.84,"type":0},{"id":0,"sLen":0,"time":61894.7344,"type":0},{"id":2,"sLen":0,"time":62052.63,"type":0},{"id":0,"sLen":0,"time":62210.5234,"type":0},{"id":1,"sLen":0,"time":62368.418,"type":0},{"id":3,"sLen":0,"time":62526.3125,"type":0},{"id":3,"sLen":0,"time":62684.207,"type":0},{"id":1,"sLen":0,"time":62842.1,"type":0},{"id":3,"sLen":0,"time":62999.9961,"type":0},{"id":0,"sLen":0,"time":63157.89,"type":0},{"id":2,"sLen":0,"time":63315.7852,"type":0},{"id":0,"sLen":0,"time":63473.68,"type":0},{"id":3,"sLen":0,"time":63631.5742,"type":0},{"id":0,"sLen":0,"time":63789.4727,"type":0},{"id":2,"sLen":0,"time":63947.3672,"type":0},{"id":3,"sLen":0,"time":64105.26,"type":0},{"id":1,"sLen":0,"time":64263.1563,"type":0},{"id":1,"sLen":0,"time":64421.05,"type":0},{"id":0,"sLen":0,"time":64578.9453,"type":0},{"id":1,"sLen":0,"time":64736.84,"type":0},{"id":3,"sLen":0,"time":64894.7344,"type":0},{"id":1,"sLen":0,"time":65052.63,"type":0},{"id":0,"sLen":0,"time":65210.5234,"type":0},{"id":1,"sLen":236.842100000002,"time":65368.418,"type":0},{"id":0,"sLen":0,"time":70736.8359,"type":0},{"id":1,"sLen":0,"time":70894.7344,"type":0},{"id":0,"sLen":0,"time":71052.625,"type":0},{"id":2,"sLen":0,"time":71210.52,"type":0},{"id":3,"sLen":0,"time":71368.4141,"type":0},{"id":1,"sLen":0,"time":71526.31,"type":0},{"id":0,"sLen":0,"time":71684.21,"type":0},{"id":3,"sLen":0,"time":71842.1,"type":0},{"id":2,"sLen":0,"time":72000,"type":0},{"id":0,"sLen":0,"time":72157.89,"type":0},{"id":1,"sLen":0,"time":72315.79,"type":0},{"id":0,"sLen":0,"time":72473.68,"type":0},{"id":2,"sLen":0,"time":72631.58,"type":0},{"id":3,"sLen":0,"time":72789.47,"type":0},{"id":0,"sLen":0,"time":72947.37,"type":0},{"id":3,"sLen":0,"time":73105.26,"type":0},{"id":0,"sLen":0,"time":73263.16,"type":0},{"id":1,"sLen":0,"time":73421.05,"type":0},{"id":0,"sLen":0,"time":73578.9453,"type":0},{"id":2,"sLen":0,"time":73736.8359,"type":0},{"id":3,"sLen":0,"time":73894.7344,"type":0},{"id":1,"sLen":0,"time":74052.625,"type":0},{"id":0,"sLen":0,"time":74210.52,"type":0},{"id":3,"sLen":0,"time":74368.4141,"type":0},{"id":2,"sLen":0,"time":74526.31,"type":0},{"id":0,"sLen":0,"time":74684.21,"type":0},{"id":1,"sLen":0,"time":74842.1,"type":0},{"id":0,"sLen":0,"time":75000,"type":0},{"id":2,"sLen":0,"time":75157.89,"type":0},{"id":0,"sLen":0,"time":75315.79,"type":0},{"id":3,"sLen":0,"time":75473.68,"type":0},{"id":0,"sLen":0,"time":75631.58,"type":0},{"id":2,"sLen":0,"time":80842.1,"type":0},{"id":3,"sLen":0,"time":81000,"type":0},{"id":1,"sLen":0,"time":81157.89,"type":0},{"id":3,"sLen":0,"time":81315.79,"type":0},{"id":0,"sLen":0,"time":81473.68,"type":0},{"id":3,"sLen":0,"time":81631.58,"type":0},{"id":1,"sLen":0,"time":81789.47,"type":0},{"id":1,"sLen":0,"time":81947.37,"type":0},{"id":2,"sLen":0,"time":82105.26,"type":0},{"id":3,"sLen":0,"time":82263.16,"type":0},{"id":1,"sLen":0,"time":82421.05,"type":0},{"id":3,"sLen":0,"time":82578.9453,"type":0},{"id":0,"sLen":0,"time":82736.8359,"type":0},{"id":3,"sLen":0,"time":82894.7344,"type":0},{"id":1,"sLen":0,"time":83052.625,"type":0},{"id":1,"sLen":0,"time":83210.52,"type":0},{"id":2,"sLen":0,"time":83368.4141,"type":0},{"id":3,"sLen":0,"time":83526.31,"type":0},{"id":1,"sLen":0,"time":83684.2,"type":0},{"id":3,"sLen":0,"time":83842.1,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84157.89,"type":0},{"id":1,"sLen":0,"time":84315.79,"type":0},{"id":1,"sLen":0,"time":84473.68,"type":0},{"id":2,"sLen":0,"time":84631.58,"type":0},{"id":3,"sLen":0,"time":84789.47,"type":0},{"id":1,"sLen":0,"time":84947.37,"type":0},{"id":3,"sLen":0,"time":85105.26,"type":0},{"id":0,"sLen":0,"time":85263.16,"type":0},{"id":3,"sLen":0,"time":85421.05,"type":0},{"id":1,"sLen":0,"time":85578.9453,"type":0},{"id":1,"sLen":0,"time":85736.8359,"type":0},{"id":3,"sLen":157.89473,"time":90947.37,"type":0},{"id":3,"sLen":0,"time":91263.16,"type":0},{"id":3,"sLen":0,"time":91421.05,"type":0},{"id":1,"sLen":157.89473,"time":91578.9453,"type":0},{"id":0,"sLen":0,"time":91894.7344,"type":0},{"id":3,"sLen":0,"time":92210.52,"type":0},{"id":1,"sLen":0,"time":92526.31,"type":0},{"id":3,"sLen":0,"time":92684.2,"type":0},{"id":1,"sLen":0,"time":92999.99,"type":0},{"id":3,"sLen":0,"time":93157.89,"type":0},{"id":3,"sLen":0,"time":93473.68,"type":0},{"id":3,"sLen":0,"time":93631.58,"type":0},{"id":3,"sLen":0,"time":93789.47,"type":0},{"id":3,"sLen":0,"time":93947.37,"type":0},{"id":1,"sLen":0,"time":94105.26,"type":0},{"id":1,"sLen":0,"time":94263.16,"type":0},{"id":0,"sLen":0,"time":94421.05,"type":0},{"id":0,"sLen":0,"time":94578.9453,"type":0},{"id":3,"sLen":0,"time":94736.8359,"type":0},{"id":1,"sLen":0,"time":95052.625,"type":0},{"id":3,"sLen":0,"time":95210.52,"type":0},{"id":1,"sLen":0,"time":95526.31,"type":0},{"id":3,"sLen":0,"time":95684.2,"type":0}],"position":"dad","type":0,"characters":["spirit"],"visible":true},{"keyCount":4,"notes":[{"id":3,"sLen":157.89473,"time":5052.63135,"type":0},{"id":3,"sLen":0,"time":5368.421,"type":0},{"id":3,"sLen":0,"time":5526.31543,"type":0},{"id":1,"sLen":157.89473,"time":5684.21045,"type":0},{"id":0,"sLen":0,"time":5999.99951,"type":0},{"id":3,"sLen":0,"time":6315.789,"type":0},{"id":1,"sLen":0,"time":6631.57861,"type":0},{"id":3,"sLen":0,"time":6789.473,"type":0},{"id":1,"sLen":0,"time":7105.2627,"type":0},{"id":3,"sLen":0,"time":7263.15771,"type":0},{"id":3,"sLen":0,"time":7578.94727,"type":0},{"id":3,"sLen":0,"time":7736.842,"type":0},{"id":3,"sLen":0,"time":7894.73633,"type":0},{"id":3,"sLen":0,"time":8052.63135,"type":0},{"id":1,"sLen":0,"time":8210.526,"type":0},{"id":1,"sLen":0,"time":8368.421,"type":0},{"id":0,"sLen":0,"time":8526.315,"type":0},{"id":0,"sLen":0,"time":8684.21,"type":0},{"id":3,"sLen":0,"time":8842.10449,"type":0},{"id":1,"sLen":0,"time":9157.895,"type":0},{"id":3,"sLen":0,"time":9315.789,"type":0},{"id":1,"sLen":0,"time":9631.578,"type":0},{"id":3,"sLen":0,"time":9789.474,"type":0},{"id":2,"sLen":157.89473,"time":15157.8945,"type":0},{"id":1,"sLen":78.9473648000003,"time":15473.6836,"type":0},{"id":2,"sLen":0,"time":15789.4727,"type":0},{"id":3,"sLen":0,"time":15947.3682,"type":0},{"id":1,"sLen":78.9473648000003,"time":16105.2627,"type":0},{"id":2,"sLen":157.89473,"time":16421.0527,"type":0},{"id":1,"sLen":0,"time":16736.8418,"type":0},{"id":2,"sLen":157.89473,"time":16894.7363,"type":0},{"id":1,"sLen":0,"time":17210.5254,"type":0},{"id":2,"sLen":157.89473,"time":17368.42,"type":0},{"id":2,"sLen":157.89473,"time":17684.209,"type":0},{"id":1,"sLen":78.9473647999985,"time":18000,"type":0},{"id":2,"sLen":0,"time":18315.79,"type":0},{"id":3,"sLen":0,"time":18473.6836,"type":0},{"id":1,"sLen":78.9473647999985,"time":18631.5781,"type":0},{"id":2,"sLen":157.89473,"time":18947.3672,"type":0},{"id":1,"sLen":0,"time":19263.1563,"type":0},{"id":2,"sLen":157.89473,"time":19421.05,"type":0},{"id":1,"sLen":0,"time":19736.8418,"type":0},{"id":0,"sLen":157.89473,"time":19894.7363,"type":0},{"id":3,"sLen":0,"time":24789.4727,"type":0},{"id":2,"sLen":0,"time":24947.3672,"type":0},{"id":0,"sLen":0,"time":25105.2617,"type":0},{"id":1,"sLen":0,"time":25263.1563,"type":0},{"id":0,"sLen":0,"time":25421.05,"type":0},{"id":3,"sLen":0,"time":25578.9453,"type":0},{"id":1,"sLen":0,"time":25736.8418,"type":0},{"id":0,"sLen":0,"time":25894.7363,"type":0},{"id":3,"sLen":0,"time":26052.63,"type":0},{"id":1,"sLen":0,"time":26210.5254,"type":0},{"id":0,"sLen":0,"time":26368.42,"type":0},{"id":2,"sLen":0,"time":26526.3145,"type":0},{"id":1,"sLen":0,"time":26684.209,"type":0},{"id":0,"sLen":0,"time":26842.1035,"type":0},{"id":2,"sLen":0,"time":26999.998,"type":0},{"id":1,"sLen":0,"time":27157.8926,"type":0},{"id":0,"sLen":0,"time":27315.79,"type":0},{"id":3,"sLen":0,"time":27473.6836,"type":0},{"id":1,"sLen":0,"time":27631.5781,"type":0},{"id":1,"sLen":0,"time":27789.4727,"type":0},{"id":0,"sLen":0,"time":27947.3672,"type":0},{"id":3,"sLen":0,"time":28105.2617,"type":0},{"id":1,"sLen":0,"time":28263.1563,"type":0},{"id":0,"sLen":0,"time":28421.05,"type":0},{"id":3,"sLen":0,"time":28578.9453,"type":0},{"id":1,"sLen":0,"time":28736.84,"type":0},{"id":0,"sLen":0,"time":28894.7363,"type":0},{"id":2,"sLen":0,"time":29052.63,"type":0},{"id":1,"sLen":0,"time":29210.5254,"type":0},{"id":0,"sLen":0,"time":29368.42,"type":0},{"id":2,"sLen":0,"time":29526.3145,"type":0},{"id":1,"sLen":0,"time":29684.209,"type":0},{"id":0,"sLen":0,"time":29842.1035,"type":0},{"id":3,"sLen":0,"time":29999.998,"type":0},{"id":1,"sLen":0,"time":30157.8926,"type":0},{"id":2,"sLen":0,"time":35368.418,"type":0},{"id":0,"sLen":0,"time":35526.3125,"type":0},{"id":3,"sLen":0,"time":35684.207,"type":0},{"id":1,"sLen":0,"time":35842.1055,"type":0},{"id":3,"sLen":236.842100000002,"time":36000,"type":0},{"id":0,"sLen":0,"time":36315.79,"type":0},{"id":3,"sLen":0,"time":36473.6836,"type":0},{"id":0,"sLen":0,"time":36631.58,"type":0},{"id":2,"sLen":0,"time":36789.4727,"type":0},{"id":0,"sLen":0,"time":36947.3672,"type":0},{"id":1,"sLen":0,"time":37105.26,"type":0},{"id":3,"sLen":236.842100000002,"time":37263.1563,"type":0},{"id":1,"sLen":0,"time":37578.9453,"type":0},{"id":3,"sLen":0,"time":37736.84,"type":0},{"id":0,"sLen":0,"time":37894.7344,"type":0},{"id":2,"sLen":0,"time":38052.63,"type":0},{"id":0,"sLen":0,"time":38210.5234,"type":0},{"id":3,"sLen":0,"time":38368.418,"type":0},{"id":0,"sLen":0,"time":38526.3125,"type":0},{"id":2,"sLen":0,"time":38684.207,"type":0},{"id":3,"sLen":0,"time":38842.1,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":1,"sLen":0,"time":39157.8945,"type":0},{"id":0,"sLen":0,"time":39315.79,"type":0},{"id":1,"sLen":0,"time":39473.6836,"type":0},{"id":3,"sLen":0,"time":39631.58,"type":0},{"id":1,"sLen":0,"time":39789.4727,"type":0},{"id":0,"sLen":0,"time":39947.3672,"type":0},{"id":1,"sLen":236.842100000002,"time":40105.26,"type":0},{"id":0,"sLen":0,"time":45473.6836,"type":0},{"id":1,"sLen":0,"time":45631.58,"type":0},{"id":0,"sLen":0,"time":45789.4727,"type":0},{"id":2,"sLen":0,"time":45947.3672,"type":0},{"id":3,"sLen":0,"time":46105.26,"type":0},{"id":1,"sLen":0,"time":46263.1563,"type":0},{"id":0,"sLen":0,"time":46421.05,"type":0},{"id":3,"sLen":0,"time":46578.9453,"type":0},{"id":2,"sLen":0,"time":46736.84,"type":0},{"id":0,"sLen":0,"time":46894.7344,"type":0},{"id":1,"sLen":0,"time":47052.63,"type":0},{"id":0,"sLen":0,"time":47210.5234,"type":0},{"id":2,"sLen":0,"time":47368.418,"type":0},{"id":3,"sLen":0,"time":47526.3125,"type":0},{"id":0,"sLen":0,"time":47684.207,"type":0},{"id":3,"sLen":0,"time":47842.1,"type":0},{"id":0,"sLen":0,"time":47999.9961,"type":0},{"id":1,"sLen":0,"time":48157.89,"type":0},{"id":0,"sLen":0,"time":48315.79,"type":0},{"id":2,"sLen":0,"time":48473.6836,"type":0},{"id":3,"sLen":0,"time":48631.58,"type":0},{"id":1,"sLen":0,"time":48789.4727,"type":0},{"id":0,"sLen":0,"time":48947.3672,"type":0},{"id":3,"sLen":0,"time":49105.26,"type":0},{"id":2,"sLen":0,"time":49263.1563,"type":0},{"id":0,"sLen":0,"time":49421.05,"type":0},{"id":1,"sLen":0,"time":49578.9453,"type":0},{"id":0,"sLen":0,"time":49736.84,"type":0},{"id":2,"sLen":0,"time":49894.7344,"type":0},{"id":0,"sLen":0,"time":50052.63,"type":0},{"id":3,"sLen":0,"time":50210.5234,"type":0},{"id":0,"sLen":0,"time":50368.418,"type":0},{"id":3,"sLen":157.89473,"time":55578.9453,"type":0},{"id":3,"sLen":0,"time":55894.7344,"type":0},{"id":3,"sLen":0,"time":56052.63,"type":0},{"id":1,"sLen":157.89473,"time":56210.5234,"type":0},{"id":0,"sLen":0,"time":56526.3125,"type":0},{"id":3,"sLen":0,"time":56842.1,"type":0},{"id":1,"sLen":0,"time":57157.89,"type":0},{"id":3,"sLen":0,"time":57315.7852,"type":0},{"id":1,"sLen":0,"time":57631.58,"type":0},{"id":3,"sLen":0,"time":57789.4727,"type":0},{"id":3,"sLen":0,"time":58105.26,"type":0},{"id":3,"sLen":0,"time":58263.1563,"type":0},{"id":3,"sLen":0,"time":58421.05,"type":0},{"id":3,"sLen":0,"time":58578.9453,"type":0},{"id":1,"sLen":0,"time":58736.84,"type":0},{"id":1,"sLen":0,"time":58894.7344,"type":0},{"id":0,"sLen":0,"time":59052.63,"type":0},{"id":0,"sLen":0,"time":59210.5234,"type":0},{"id":3,"sLen":0,"time":59368.418,"type":0},{"id":1,"sLen":0,"time":59684.207,"type":0},{"id":3,"sLen":0,"time":59842.1,"type":0},{"id":1,"sLen":0,"time":60157.89,"type":0},{"id":3,"sLen":0,"time":60315.7852,"type":0},{"id":2,"sLen":0,"time":65684.21,"type":0},{"id":0,"sLen":0,"time":65842.1,"type":0},{"id":3,"sLen":0,"time":66000,"type":0},{"id":1,"sLen":0,"time":66157.89,"type":0},{"id":3,"sLen":0,"time":66315.79,"type":0},{"id":3,"sLen":0,"time":66473.68,"type":0},{"id":0,"sLen":0,"time":66631.58,"type":0},{"id":3,"sLen":0,"time":66789.47,"type":0},{"id":0,"sLen":0,"time":66947.37,"type":0},{"id":2,"sLen":0,"time":67105.26,"type":0},{"id":0,"sLen":0,"time":67263.16,"type":0},{"id":1,"sLen":0,"time":67421.05,"type":0},{"id":3,"sLen":0,"time":67578.9453,"type":0},{"id":3,"sLen":0,"time":67736.8359,"type":0},{"id":1,"sLen":0,"time":67894.7344,"type":0},{"id":3,"sLen":0,"time":68052.625,"type":0},{"id":0,"sLen":0,"time":68210.52,"type":0},{"id":2,"sLen":0,"time":68368.4141,"type":0},{"id":0,"sLen":0,"time":68526.31,"type":0},{"id":3,"sLen":0,"time":68684.21,"type":0},{"id":0,"sLen":0,"time":68842.1,"type":0},{"id":2,"sLen":0,"time":69000,"type":0},{"id":3,"sLen":0,"time":69157.89,"type":0},{"id":1,"sLen":0,"time":69315.79,"type":0},{"id":1,"sLen":0,"time":69473.68,"type":0},{"id":0,"sLen":0,"time":69631.58,"type":0},{"id":1,"sLen":0,"time":69789.47,"type":0},{"id":3,"sLen":0,"time":69947.37,"type":0},{"id":1,"sLen":0,"time":70105.26,"type":0},{"id":0,"sLen":0,"time":70263.16,"type":0},{"id":1,"sLen":236.842099999994,"time":70421.05,"type":0},{"id":0,"sLen":0,"time":75789.47,"type":0},{"id":1,"sLen":0,"time":75947.37,"type":0},{"id":0,"sLen":0,"time":76105.26,"type":0},{"id":2,"sLen":0,"time":76263.16,"type":0},{"id":3,"sLen":0,"time":76421.05,"type":0},{"id":1,"sLen":0,"time":76578.9453,"type":0},{"id":0,"sLen":0,"time":76736.8359,"type":0},{"id":3,"sLen":0,"time":76894.7344,"type":0},{"id":2,"sLen":0,"time":77052.625,"type":0},{"id":0,"sLen":0,"time":77210.52,"type":0},{"id":1,"sLen":0,"time":77368.4141,"type":0},{"id":0,"sLen":0,"time":77526.31,"type":0},{"id":2,"sLen":0,"time":77684.2,"type":0},{"id":3,"sLen":0,"time":77842.1,"type":0},{"id":0,"sLen":0,"time":78000,"type":0},{"id":3,"sLen":0,"time":78157.89,"type":0},{"id":0,"sLen":0,"time":78315.79,"type":0},{"id":1,"sLen":0,"time":78473.68,"type":0},{"id":0,"sLen":0,"time":78631.58,"type":0},{"id":2,"sLen":0,"time":78789.47,"type":0},{"id":3,"sLen":0,"time":78947.37,"type":0},{"id":1,"sLen":0,"time":79105.26,"type":0},{"id":0,"sLen":0,"time":79263.16,"type":0},{"id":3,"sLen":0,"time":79421.05,"type":0},{"id":2,"sLen":0,"time":79578.9453,"type":0},{"id":0,"sLen":0,"time":79736.8359,"type":0},{"id":1,"sLen":0,"time":79894.7344,"type":0},{"id":0,"sLen":0,"time":80052.625,"type":0},{"id":2,"sLen":0,"time":80210.52,"type":0},{"id":0,"sLen":0,"time":80368.4141,"type":0},{"id":3,"sLen":0,"time":80526.31,"type":0},{"id":0,"sLen":0,"time":80684.2,"type":0},{"id":2,"sLen":0,"time":85894.7344,"type":0},{"id":3,"sLen":0,"time":86052.625,"type":0},{"id":1,"sLen":0,"time":86210.52,"type":0},{"id":3,"sLen":0,"time":86368.4141,"type":0},{"id":0,"sLen":0,"time":86526.31,"type":0},{"id":3,"sLen":0,"time":86684.2,"type":0},{"id":1,"sLen":0,"time":86842.1,"type":0},{"id":1,"sLen":0,"time":86999.99,"type":0},{"id":2,"sLen":0,"time":87157.89,"type":0},{"id":3,"sLen":0,"time":87315.79,"type":0},{"id":1,"sLen":0,"time":87473.68,"type":0},{"id":3,"sLen":0,"time":87631.58,"type":0},{"id":0,"sLen":0,"time":87789.47,"type":0},{"id":3,"sLen":0,"time":87947.37,"type":0},{"id":1,"sLen":0,"time":88105.26,"type":0},{"id":1,"sLen":0,"time":88263.16,"type":0},{"id":2,"sLen":0,"time":88421.05,"type":0},{"id":3,"sLen":0,"time":88578.9453,"type":0},{"id":1,"sLen":0,"time":88736.8359,"type":0},{"id":3,"sLen":0,"time":88894.7344,"type":0},{"id":0,"sLen":0,"time":89052.625,"type":0},{"id":3,"sLen":0,"time":89210.52,"type":0},{"id":1,"sLen":0,"time":89368.4141,"type":0},{"id":1,"sLen":0,"time":89526.31,"type":0},{"id":2,"sLen":0,"time":89684.2,"type":0},{"id":3,"sLen":0,"time":89842.1,"type":0},{"id":1,"sLen":0,"time":89999.99,"type":0},{"id":3,"sLen":0,"time":90157.89,"type":0},{"id":0,"sLen":0,"time":90315.79,"type":0},{"id":3,"sLen":0,"time":90473.68,"type":0},{"id":1,"sLen":0,"time":90631.58,"type":0},{"id":1,"sLen":0,"time":90789.47,"type":0},{"id":3,"sLen":157.89473,"time":95999.99,"type":0},{"id":3,"sLen":0,"time":96315.78,"type":0},{"id":3,"sLen":0,"time":96473.68,"type":0},{"id":1,"sLen":157.89473,"time":96631.58,"type":0},{"id":0,"sLen":0,"time":96947.37,"type":0},{"id":3,"sLen":0,"time":97263.16,"type":0},{"id":1,"sLen":0,"time":97578.9453,"type":0},{"id":3,"sLen":0,"time":97736.8359,"type":0},{"id":1,"sLen":0,"time":98052.625,"type":0},{"id":3,"sLen":0,"time":98210.52,"type":0},{"id":3,"sLen":0,"time":98526.31,"type":0},{"id":3,"sLen":0,"time":98684.2,"type":0},{"id":3,"sLen":0,"time":98842.1,"type":0},{"id":3,"sLen":0,"time":98999.99,"type":0},{"id":1,"sLen":0,"time":99157.89,"type":0},{"id":1,"sLen":0,"time":99315.78,"type":0},{"id":0,"sLen":0,"time":99473.68,"type":0},{"id":0,"sLen":0,"time":99631.58,"type":0},{"id":3,"sLen":0,"time":99789.47,"type":0},{"id":1,"sLen":0,"time":100105.258,"type":0},{"id":3,"sLen":0,"time":100263.156,"type":0},{"id":1,"sLen":0,"time":100578.945,"type":0},{"id":3,"sLen":0,"time":100736.836,"type":0}],"position":"boyfriend","type":1,"characters":["bf-pixel"],"visible":true},{"keyCount":4,"notes":[],"position":"girlfriend","type":2,"characters":["gf-pixel"],"visible":false}],"scrollSpeed":1.5,"chartVersion":"1.6.0","codenameChart":true,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/thorns/charts/normal.json b/assets/songs/thorns/charts/normal.json index f8c2234d..08e32b1c 100644 --- a/assets/songs/thorns/charts/normal.json +++ b/assets/songs/thorns/charts/normal.json @@ -1 +1 @@ -{"codenameChart":true,"strumLines":[{"position":"dad","notes":[{"id":3,"sLen":157.89473,"time":0,"type":0},{"id":1,"sLen":157.89473,"time":631.5789,"type":0},{"id":0,"sLen":0,"time":947.3684,"type":0},{"id":3,"sLen":0,"time":1263.15784,"type":0},{"id":1,"sLen":0,"time":1578.94727,"type":0},{"id":3,"sLen":0,"time":1736.842,"type":0},{"id":1,"sLen":0,"time":2052.63159,"type":0},{"id":3,"sLen":0,"time":2210.52612,"type":0},{"id":3,"sLen":0,"time":2526.31567,"type":0},{"id":3,"sLen":0,"time":2842.10522,"type":0},{"id":1,"sLen":0,"time":3157.89453,"type":0},{"id":0,"sLen":0,"time":3473.684,"type":0},{"id":3,"sLen":0,"time":3789.47363,"type":0},{"id":1,"sLen":0,"time":4105.263,"type":0},{"id":3,"sLen":0,"time":4263.15771,"type":0},{"id":1,"sLen":0,"time":4578.94727,"type":0},{"id":3,"sLen":0,"time":4736.842,"type":0},{"id":2,"sLen":157.894729999998,"time":10105.2627,"type":0},{"id":2,"sLen":0,"time":10736.8418,"type":0},{"id":3,"sLen":0,"time":10894.7363,"type":0},{"id":1,"sLen":78.9473647999985,"time":11052.6309,"type":0},{"id":2,"sLen":157.89473,"time":11368.4209,"type":0},{"id":2,"sLen":157.89473,"time":11842.1045,"type":0},{"id":2,"sLen":157.89473,"time":12315.7891,"type":0},{"id":2,"sLen":157.89473,"time":12631.5781,"type":0},{"id":2,"sLen":0,"time":13263.1572,"type":0},{"id":3,"sLen":0,"time":13421.0518,"type":0},{"id":1,"sLen":78.9473648000003,"time":13578.9463,"type":0},{"id":2,"sLen":157.89473,"time":13894.7363,"type":0},{"id":2,"sLen":157.89473,"time":14368.42,"type":0},{"id":0,"sLen":157.89473,"time":14842.1045,"type":0},{"id":1,"sLen":0,"time":20210.5254,"type":0},{"id":3,"sLen":0,"time":20526.3145,"type":0},{"id":1,"sLen":0,"time":20684.209,"type":0},{"id":3,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21157.8945,"type":0},{"id":2,"sLen":0,"time":21473.6836,"type":0},{"id":1,"sLen":0,"time":21631.5781,"type":0},{"id":2,"sLen":0,"time":21947.3672,"type":0},{"id":1,"sLen":0,"time":22105.2617,"type":0},{"id":3,"sLen":0,"time":22421.05,"type":0},{"id":1,"sLen":0,"time":22578.9473,"type":0},{"id":1,"sLen":0,"time":22736.8418,"type":0},{"id":3,"sLen":0,"time":23052.63,"type":0},{"id":1,"sLen":0,"time":23210.5254,"type":0},{"id":3,"sLen":0,"time":23526.3145,"type":0},{"id":1,"sLen":0,"time":23684.209,"type":0},{"id":2,"sLen":0,"time":23999.998,"type":0},{"id":1,"sLen":0,"time":24157.8945,"type":0},{"id":2,"sLen":0,"time":24473.6836,"type":0},{"id":1,"sLen":0,"time":24631.5781,"type":0},{"id":3,"sLen":0,"time":24947.3672,"type":0},{"id":1,"sLen":0,"time":25105.2617,"type":0},{"id":2,"sLen":0,"time":30315.79,"type":0},{"id":3,"sLen":0,"time":30631.5781,"type":0},{"id":1,"sLen":0,"time":30789.4727,"type":0},{"id":3,"sLen":0,"time":30947.3672,"type":0},{"id":0,"sLen":0,"time":31263.1563,"type":0},{"id":3,"sLen":0,"time":31421.05,"type":0},{"id":0,"sLen":0,"time":31578.9453,"type":0},{"id":0,"sLen":0,"time":31894.7363,"type":0},{"id":1,"sLen":0,"time":32052.63,"type":0},{"id":3,"sLen":0,"time":32210.5254,"type":0},{"id":1,"sLen":0,"time":32526.3145,"type":0},{"id":3,"sLen":0,"time":32684.209,"type":0},{"id":0,"sLen":0,"time":32842.1055,"type":0},{"id":0,"sLen":0,"time":33157.8945,"type":0},{"id":3,"sLen":0,"time":33315.79,"type":0},{"id":0,"sLen":0,"time":33473.6836,"type":0},{"id":3,"sLen":0,"time":33789.4727,"type":0},{"id":1,"sLen":0,"time":33947.3672,"type":0},{"id":1,"sLen":0,"time":34105.26,"type":0},{"id":1,"sLen":0,"time":34421.05,"type":0},{"id":3,"sLen":0,"time":34578.9453,"type":0},{"id":1,"sLen":0,"time":34736.84,"type":0},{"id":1,"sLen":236.842100000002,"time":35052.63,"type":0},{"id":0,"sLen":0,"time":40421.05,"type":0},{"id":0,"sLen":0,"time":40736.84,"type":0},{"id":3,"sLen":0,"time":41052.63,"type":0},{"id":1,"sLen":0,"time":41210.5234,"type":0},{"id":0,"sLen":0,"time":41368.418,"type":0},{"id":3,"sLen":0,"time":41526.3125,"type":0},{"id":2,"sLen":0,"time":41684.207,"type":0},{"id":1,"sLen":0,"time":42000,"type":0},{"id":2,"sLen":0,"time":42315.79,"type":0},{"id":3,"sLen":0,"time":42473.6836,"type":0},{"id":0,"sLen":0,"time":42631.58,"type":0},{"id":3,"sLen":0,"time":42789.4727,"type":0},{"id":0,"sLen":0,"time":42947.3672,"type":0},{"id":0,"sLen":0,"time":43263.1563,"type":0},{"id":3,"sLen":0,"time":43578.9453,"type":0},{"id":1,"sLen":0,"time":43736.84,"type":0},{"id":0,"sLen":0,"time":43894.7344,"type":0},{"id":3,"sLen":0,"time":44052.63,"type":0},{"id":2,"sLen":0,"time":44210.5234,"type":0},{"id":1,"sLen":0,"time":44526.3125,"type":0},{"id":2,"sLen":0,"time":44842.1,"type":0},{"id":0,"sLen":0,"time":44999.9961,"type":0},{"id":3,"sLen":0,"time":45157.8945,"type":0},{"id":0,"sLen":0,"time":45315.79,"type":0},{"id":3,"sLen":157.89473,"time":50526.3125,"type":0},{"id":1,"sLen":157.89473,"time":51157.89,"type":0},{"id":0,"sLen":0,"time":51473.6836,"type":0},{"id":3,"sLen":0,"time":51789.4727,"type":0},{"id":1,"sLen":0,"time":52105.26,"type":0},{"id":3,"sLen":0,"time":52263.1563,"type":0},{"id":1,"sLen":0,"time":52578.9453,"type":0},{"id":3,"sLen":0,"time":52736.84,"type":0},{"id":3,"sLen":0,"time":53052.63,"type":0},{"id":3,"sLen":0,"time":53368.418,"type":0},{"id":1,"sLen":0,"time":53684.207,"type":0},{"id":0,"sLen":0,"time":53999.9961,"type":0},{"id":3,"sLen":0,"time":54315.7852,"type":0},{"id":1,"sLen":0,"time":54631.58,"type":0},{"id":3,"sLen":0,"time":54789.4727,"type":0},{"id":1,"sLen":0,"time":55105.26,"type":0},{"id":3,"sLen":0,"time":55263.1563,"type":0},{"id":2,"sLen":0,"time":60631.58,"type":0},{"id":3,"sLen":0,"time":60947.3672,"type":0},{"id":1,"sLen":0,"time":61105.26,"type":0},{"id":3,"sLen":0,"time":61263.1563,"type":0},{"id":0,"sLen":0,"time":61578.9453,"type":0},{"id":3,"sLen":0,"time":61736.84,"type":0},{"id":0,"sLen":0,"time":61894.7344,"type":0},{"id":0,"sLen":0,"time":62210.5234,"type":0},{"id":1,"sLen":0,"time":62368.418,"type":0},{"id":3,"sLen":0,"time":62526.3125,"type":0},{"id":1,"sLen":0,"time":62842.1,"type":0},{"id":3,"sLen":0,"time":62999.9961,"type":0},{"id":0,"sLen":0,"time":63157.89,"type":0},{"id":0,"sLen":0,"time":63473.68,"type":0},{"id":3,"sLen":0,"time":63631.5742,"type":0},{"id":0,"sLen":0,"time":63789.4727,"type":0},{"id":3,"sLen":0,"time":64105.26,"type":0},{"id":1,"sLen":0,"time":64263.1563,"type":0},{"id":1,"sLen":0,"time":64421.05,"type":0},{"id":1,"sLen":0,"time":64736.84,"type":0},{"id":3,"sLen":0,"time":64894.7344,"type":0},{"id":1,"sLen":0,"time":65052.63,"type":0},{"id":1,"sLen":236.842100000002,"time":65368.418,"type":0},{"id":0,"sLen":0,"time":70736.8359,"type":0},{"id":0,"sLen":0,"time":71052.625,"type":0},{"id":3,"sLen":0,"time":71368.4141,"type":0},{"id":1,"sLen":0,"time":71526.31,"type":0},{"id":0,"sLen":0,"time":71684.21,"type":0},{"id":3,"sLen":0,"time":71842.1,"type":0},{"id":2,"sLen":0,"time":72000,"type":0},{"id":1,"sLen":0,"time":72315.79,"type":0},{"id":2,"sLen":0,"time":72631.58,"type":0},{"id":3,"sLen":0,"time":72789.47,"type":0},{"id":0,"sLen":0,"time":72947.37,"type":0},{"id":3,"sLen":0,"time":73105.26,"type":0},{"id":0,"sLen":0,"time":73263.16,"type":0},{"id":0,"sLen":0,"time":73578.9453,"type":0},{"id":3,"sLen":0,"time":73894.7344,"type":0},{"id":1,"sLen":0,"time":74052.625,"type":0},{"id":0,"sLen":0,"time":74210.52,"type":0},{"id":3,"sLen":0,"time":74368.4141,"type":0},{"id":2,"sLen":0,"time":74526.31,"type":0},{"id":1,"sLen":0,"time":74842.1,"type":0},{"id":2,"sLen":0,"time":75157.89,"type":0},{"id":0,"sLen":0,"time":75315.79,"type":0},{"id":3,"sLen":0,"time":75473.68,"type":0},{"id":0,"sLen":0,"time":75631.58,"type":0},{"id":2,"sLen":0,"time":80842.1,"type":0},{"id":1,"sLen":0,"time":81157.89,"type":0},{"id":0,"sLen":0,"time":81473.68,"type":0},{"id":3,"sLen":0,"time":81631.58,"type":0},{"id":1,"sLen":0,"time":81789.47,"type":0},{"id":2,"sLen":0,"time":82105.26,"type":0},{"id":1,"sLen":0,"time":82421.05,"type":0},{"id":0,"sLen":0,"time":82736.8359,"type":0},{"id":3,"sLen":0,"time":82894.7344,"type":0},{"id":1,"sLen":0,"time":83052.625,"type":0},{"id":2,"sLen":0,"time":83368.4141,"type":0},{"id":1,"sLen":0,"time":83684.2,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84157.89,"type":0},{"id":1,"sLen":0,"time":84315.79,"type":0},{"id":2,"sLen":0,"time":84631.58,"type":0},{"id":1,"sLen":0,"time":84947.37,"type":0},{"id":0,"sLen":0,"time":85263.16,"type":0},{"id":3,"sLen":0,"time":85421.05,"type":0},{"id":1,"sLen":0,"time":85578.9453,"type":0},{"id":3,"sLen":157.89473,"time":90947.37,"type":0},{"id":1,"sLen":157.89473,"time":91578.9453,"type":0},{"id":0,"sLen":0,"time":91894.7344,"type":0},{"id":3,"sLen":0,"time":92210.52,"type":0},{"id":1,"sLen":0,"time":92526.31,"type":0},{"id":3,"sLen":0,"time":92684.2,"type":0},{"id":1,"sLen":0,"time":92999.99,"type":0},{"id":3,"sLen":0,"time":93157.89,"type":0},{"id":3,"sLen":0,"time":93473.68,"type":0},{"id":3,"sLen":0,"time":93789.47,"type":0},{"id":1,"sLen":0,"time":94105.26,"type":0},{"id":0,"sLen":0,"time":94421.05,"type":0},{"id":3,"sLen":0,"time":94736.8359,"type":0},{"id":1,"sLen":0,"time":95052.625,"type":0},{"id":3,"sLen":0,"time":95210.52,"type":0},{"id":1,"sLen":0,"time":95526.31,"type":0},{"id":3,"sLen":0,"time":95684.2,"type":0}],"characters":["spirit"],"visible":true,"type":0},{"position":"boyfriend","notes":[{"id":3,"sLen":157.89473,"time":5052.63135,"type":0},{"id":1,"sLen":157.89473,"time":5684.21045,"type":0},{"id":0,"sLen":0,"time":5999.99951,"type":0},{"id":3,"sLen":0,"time":6315.789,"type":0},{"id":1,"sLen":0,"time":6631.57861,"type":0},{"id":3,"sLen":0,"time":6789.473,"type":0},{"id":1,"sLen":0,"time":7105.2627,"type":0},{"id":3,"sLen":0,"time":7263.15771,"type":0},{"id":3,"sLen":0,"time":7578.94727,"type":0},{"id":3,"sLen":0,"time":7894.73633,"type":0},{"id":1,"sLen":0,"time":8210.526,"type":0},{"id":0,"sLen":0,"time":8526.315,"type":0},{"id":3,"sLen":0,"time":8842.10449,"type":0},{"id":1,"sLen":0,"time":9157.895,"type":0},{"id":3,"sLen":0,"time":9315.789,"type":0},{"id":1,"sLen":0,"time":9631.578,"type":0},{"id":3,"sLen":0,"time":9789.474,"type":0},{"id":2,"sLen":157.89473,"time":15157.8945,"type":0},{"id":2,"sLen":0,"time":15789.4727,"type":0},{"id":3,"sLen":0,"time":15947.3682,"type":0},{"id":1,"sLen":78.9473648000003,"time":16105.2627,"type":0},{"id":2,"sLen":157.89473,"time":16421.0527,"type":0},{"id":2,"sLen":157.89473,"time":16894.7363,"type":0},{"id":2,"sLen":157.89473,"time":17368.42,"type":0},{"id":2,"sLen":157.89473,"time":17684.209,"type":0},{"id":2,"sLen":0,"time":18315.79,"type":0},{"id":3,"sLen":0,"time":18473.6836,"type":0},{"id":1,"sLen":78.9473647999985,"time":18631.5781,"type":0},{"id":2,"sLen":157.89473,"time":18947.3672,"type":0},{"id":2,"sLen":157.89473,"time":19421.05,"type":0},{"id":0,"sLen":157.89473,"time":19894.7363,"type":0},{"id":3,"sLen":0,"time":24789.4727,"type":0},{"id":2,"sLen":0,"time":24947.3672,"type":0},{"id":0,"sLen":0,"time":25105.2617,"type":0},{"id":1,"sLen":0,"time":25263.1563,"type":0},{"id":3,"sLen":0,"time":25578.9453,"type":0},{"id":1,"sLen":0,"time":25736.8418,"type":0},{"id":3,"sLen":0,"time":26052.63,"type":0},{"id":1,"sLen":0,"time":26210.5254,"type":0},{"id":2,"sLen":0,"time":26526.3145,"type":0},{"id":1,"sLen":0,"time":26684.209,"type":0},{"id":2,"sLen":0,"time":26999.998,"type":0},{"id":1,"sLen":0,"time":27157.8926,"type":0},{"id":3,"sLen":0,"time":27473.6836,"type":0},{"id":1,"sLen":0,"time":27631.5781,"type":0},{"id":1,"sLen":0,"time":27789.4727,"type":0},{"id":3,"sLen":0,"time":28105.2617,"type":0},{"id":1,"sLen":0,"time":28263.1563,"type":0},{"id":3,"sLen":0,"time":28578.9453,"type":0},{"id":1,"sLen":0,"time":28736.84,"type":0},{"id":2,"sLen":0,"time":29052.63,"type":0},{"id":1,"sLen":0,"time":29210.5254,"type":0},{"id":2,"sLen":0,"time":29526.3145,"type":0},{"id":1,"sLen":0,"time":29684.209,"type":0},{"id":3,"sLen":0,"time":29999.998,"type":0},{"id":1,"sLen":0,"time":30157.8926,"type":0},{"id":2,"sLen":0,"time":35368.418,"type":0},{"id":3,"sLen":0,"time":35684.207,"type":0},{"id":1,"sLen":0,"time":35842.1055,"type":0},{"id":3,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36315.79,"type":0},{"id":3,"sLen":0,"time":36473.6836,"type":0},{"id":0,"sLen":0,"time":36631.58,"type":0},{"id":0,"sLen":0,"time":36947.3672,"type":0},{"id":1,"sLen":0,"time":37105.26,"type":0},{"id":3,"sLen":0,"time":37263.1563,"type":0},{"id":1,"sLen":0,"time":37578.9453,"type":0},{"id":3,"sLen":0,"time":37736.84,"type":0},{"id":0,"sLen":0,"time":37894.7344,"type":0},{"id":0,"sLen":0,"time":38210.5234,"type":0},{"id":3,"sLen":0,"time":38368.418,"type":0},{"id":0,"sLen":0,"time":38526.3125,"type":0},{"id":3,"sLen":0,"time":38842.1,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":1,"sLen":0,"time":39157.8945,"type":0},{"id":1,"sLen":0,"time":39473.6836,"type":0},{"id":3,"sLen":0,"time":39631.58,"type":0},{"id":1,"sLen":0,"time":39789.4727,"type":0},{"id":1,"sLen":236.842100000002,"time":40105.26,"type":0},{"id":0,"sLen":0,"time":45473.6836,"type":0},{"id":0,"sLen":0,"time":45789.4727,"type":0},{"id":3,"sLen":0,"time":46105.26,"type":0},{"id":1,"sLen":0,"time":46263.1563,"type":0},{"id":0,"sLen":0,"time":46421.05,"type":0},{"id":3,"sLen":0,"time":46578.9453,"type":0},{"id":2,"sLen":0,"time":46736.84,"type":0},{"id":1,"sLen":0,"time":47052.63,"type":0},{"id":2,"sLen":0,"time":47368.418,"type":0},{"id":3,"sLen":0,"time":47526.3125,"type":0},{"id":0,"sLen":0,"time":47684.207,"type":0},{"id":3,"sLen":0,"time":47842.1,"type":0},{"id":0,"sLen":0,"time":47999.9961,"type":0},{"id":0,"sLen":0,"time":48315.79,"type":0},{"id":3,"sLen":0,"time":48631.58,"type":0},{"id":1,"sLen":0,"time":48789.4727,"type":0},{"id":0,"sLen":0,"time":48947.3672,"type":0},{"id":3,"sLen":0,"time":49105.26,"type":0},{"id":2,"sLen":0,"time":49263.1563,"type":0},{"id":1,"sLen":0,"time":49578.9453,"type":0},{"id":2,"sLen":0,"time":49894.7344,"type":0},{"id":0,"sLen":0,"time":50052.63,"type":0},{"id":3,"sLen":0,"time":50210.5234,"type":0},{"id":0,"sLen":0,"time":50368.418,"type":0},{"id":3,"sLen":157.89473,"time":55578.9453,"type":0},{"id":1,"sLen":157.89473,"time":56210.5234,"type":0},{"id":0,"sLen":0,"time":56526.3125,"type":0},{"id":3,"sLen":0,"time":56842.1,"type":0},{"id":1,"sLen":0,"time":57157.89,"type":0},{"id":3,"sLen":0,"time":57315.7852,"type":0},{"id":1,"sLen":0,"time":57631.58,"type":0},{"id":3,"sLen":0,"time":57789.4727,"type":0},{"id":3,"sLen":0,"time":58105.26,"type":0},{"id":3,"sLen":0,"time":58421.05,"type":0},{"id":1,"sLen":0,"time":58736.84,"type":0},{"id":0,"sLen":0,"time":59052.63,"type":0},{"id":3,"sLen":0,"time":59368.418,"type":0},{"id":1,"sLen":0,"time":59684.207,"type":0},{"id":3,"sLen":0,"time":59842.1,"type":0},{"id":1,"sLen":0,"time":60157.89,"type":0},{"id":3,"sLen":0,"time":60315.7852,"type":0},{"id":2,"sLen":0,"time":65684.21,"type":0},{"id":3,"sLen":0,"time":66000,"type":0},{"id":1,"sLen":0,"time":66157.89,"type":0},{"id":3,"sLen":0,"time":66315.79,"type":0},{"id":0,"sLen":0,"time":66631.58,"type":0},{"id":3,"sLen":0,"time":66789.47,"type":0},{"id":0,"sLen":0,"time":66947.37,"type":0},{"id":0,"sLen":0,"time":67263.16,"type":0},{"id":1,"sLen":0,"time":67421.05,"type":0},{"id":3,"sLen":0,"time":67578.9453,"type":0},{"id":1,"sLen":0,"time":67894.7344,"type":0},{"id":3,"sLen":0,"time":68052.625,"type":0},{"id":0,"sLen":0,"time":68210.52,"type":0},{"id":0,"sLen":0,"time":68526.31,"type":0},{"id":3,"sLen":0,"time":68684.21,"type":0},{"id":0,"sLen":0,"time":68842.1,"type":0},{"id":3,"sLen":0,"time":69157.89,"type":0},{"id":1,"sLen":0,"time":69315.79,"type":0},{"id":1,"sLen":0,"time":69473.68,"type":0},{"id":1,"sLen":0,"time":69789.47,"type":0},{"id":3,"sLen":0,"time":69947.37,"type":0},{"id":1,"sLen":0,"time":70105.26,"type":0},{"id":1,"sLen":236.842099999994,"time":70421.05,"type":0},{"id":0,"sLen":0,"time":75789.47,"type":0},{"id":0,"sLen":0,"time":76105.26,"type":0},{"id":3,"sLen":0,"time":76421.05,"type":0},{"id":1,"sLen":0,"time":76578.9453,"type":0},{"id":0,"sLen":0,"time":76736.8359,"type":0},{"id":3,"sLen":0,"time":76894.7344,"type":0},{"id":2,"sLen":0,"time":77052.625,"type":0},{"id":1,"sLen":0,"time":77368.4141,"type":0},{"id":2,"sLen":0,"time":77684.2,"type":0},{"id":3,"sLen":0,"time":77842.1,"type":0},{"id":0,"sLen":0,"time":78000,"type":0},{"id":3,"sLen":0,"time":78157.89,"type":0},{"id":0,"sLen":0,"time":78315.79,"type":0},{"id":0,"sLen":0,"time":78631.58,"type":0},{"id":3,"sLen":0,"time":78947.37,"type":0},{"id":1,"sLen":0,"time":79105.26,"type":0},{"id":0,"sLen":0,"time":79263.16,"type":0},{"id":3,"sLen":0,"time":79421.05,"type":0},{"id":2,"sLen":0,"time":79578.9453,"type":0},{"id":1,"sLen":0,"time":79894.7344,"type":0},{"id":2,"sLen":0,"time":80210.52,"type":0},{"id":0,"sLen":0,"time":80368.4141,"type":0},{"id":3,"sLen":0,"time":80526.31,"type":0},{"id":0,"sLen":0,"time":80684.2,"type":0},{"id":2,"sLen":0,"time":85894.7344,"type":0},{"id":1,"sLen":0,"time":86210.52,"type":0},{"id":0,"sLen":0,"time":86526.31,"type":0},{"id":3,"sLen":0,"time":86684.2,"type":0},{"id":1,"sLen":0,"time":86842.1,"type":0},{"id":2,"sLen":0,"time":87157.89,"type":0},{"id":1,"sLen":0,"time":87473.68,"type":0},{"id":0,"sLen":0,"time":87789.47,"type":0},{"id":3,"sLen":0,"time":87947.37,"type":0},{"id":1,"sLen":0,"time":88105.26,"type":0},{"id":2,"sLen":0,"time":88421.05,"type":0},{"id":1,"sLen":0,"time":88736.8359,"type":0},{"id":0,"sLen":0,"time":89052.625,"type":0},{"id":3,"sLen":0,"time":89210.52,"type":0},{"id":1,"sLen":0,"time":89368.4141,"type":0},{"id":2,"sLen":0,"time":89684.2,"type":0},{"id":1,"sLen":0,"time":89999.99,"type":0},{"id":0,"sLen":0,"time":90315.79,"type":0},{"id":3,"sLen":0,"time":90473.68,"type":0},{"id":1,"sLen":0,"time":90631.58,"type":0},{"id":3,"sLen":157.89473,"time":95999.99,"type":0},{"id":1,"sLen":157.89473,"time":96631.58,"type":0},{"id":0,"sLen":0,"time":96947.37,"type":0},{"id":3,"sLen":0,"time":97263.16,"type":0},{"id":1,"sLen":0,"time":97578.9453,"type":0},{"id":3,"sLen":0,"time":97736.8359,"type":0},{"id":1,"sLen":0,"time":98052.625,"type":0},{"id":3,"sLen":0,"time":98210.52,"type":0},{"id":3,"sLen":0,"time":98526.31,"type":0},{"id":3,"sLen":0,"time":98842.1,"type":0},{"id":1,"sLen":0,"time":99157.89,"type":0},{"id":0,"sLen":0,"time":99473.68,"type":0},{"id":3,"sLen":0,"time":99789.47,"type":0},{"id":1,"sLen":0,"time":100105.258,"type":0},{"id":3,"sLen":0,"time":100263.156,"type":0},{"id":1,"sLen":0,"time":100578.945,"type":0},{"id":3,"sLen":0,"time":100736.836,"type":0}],"characters":["bf-pixel"],"visible":true,"type":1},{"position":"girlfriend","notes":[],"characters":["gf-pixel"],"visible":false,"type":2}],"stage":"school-evil","scrollSpeed":1.3,"noteTypes":[]} \ No newline at end of file +{"events":[{"params":[1],"time":5052.63157894737,"name":"Camera Movement"},{"params":[0],"time":10105.2631578947,"name":"Camera Movement"},{"params":[1],"time":15157.8947368421,"name":"Camera Movement"},{"params":[0],"time":20210.5263157895,"name":"Camera Movement"},{"params":[1],"time":25263.1578947368,"name":"Camera Movement"},{"params":[0],"time":30315.7894736842,"name":"Camera Movement"},{"params":[1],"time":35368.4210526316,"name":"Camera Movement"},{"params":[0],"time":40421.0526315789,"name":"Camera Movement"},{"params":[1],"time":45473.6842105263,"name":"Camera Movement"},{"params":[0],"time":50526.3157894737,"name":"Camera Movement"},{"params":[1],"time":55578.9473684211,"name":"Camera Movement"},{"params":[0],"time":60631.5789473684,"name":"Camera Movement"},{"params":[1],"time":65684.2105263158,"name":"Camera Movement"},{"params":[0],"time":70736.8421052632,"name":"Camera Movement"},{"params":[1],"time":75789.4736842105,"name":"Camera Movement"},{"params":[0],"time":80842.1052631579,"name":"Camera Movement"},{"params":[1],"time":85894.7368421053,"name":"Camera Movement"},{"params":[0],"time":90947.3684210526,"name":"Camera Movement"},{"params":[1],"time":96000,"name":"Camera Movement"}],"codenameChart":true,"strumLines":[{"position":"dad","notes":[{"id":3,"sLen":157.89473,"time":0,"type":0},{"id":1,"sLen":157.89473,"time":631.5789,"type":0},{"id":0,"sLen":0,"time":947.3684,"type":0},{"id":3,"sLen":0,"time":1263.15784,"type":0},{"id":1,"sLen":0,"time":1578.94727,"type":0},{"id":3,"sLen":0,"time":1736.842,"type":0},{"id":1,"sLen":0,"time":2052.63159,"type":0},{"id":3,"sLen":0,"time":2210.52612,"type":0},{"id":3,"sLen":0,"time":2526.31567,"type":0},{"id":3,"sLen":0,"time":2842.10522,"type":0},{"id":1,"sLen":0,"time":3157.89453,"type":0},{"id":0,"sLen":0,"time":3473.684,"type":0},{"id":3,"sLen":0,"time":3789.47363,"type":0},{"id":1,"sLen":0,"time":4105.263,"type":0},{"id":3,"sLen":0,"time":4263.15771,"type":0},{"id":1,"sLen":0,"time":4578.94727,"type":0},{"id":3,"sLen":0,"time":4736.842,"type":0},{"id":2,"sLen":157.894729999998,"time":10105.2627,"type":0},{"id":2,"sLen":0,"time":10736.8418,"type":0},{"id":3,"sLen":0,"time":10894.7363,"type":0},{"id":1,"sLen":78.9473647999985,"time":11052.6309,"type":0},{"id":2,"sLen":157.89473,"time":11368.4209,"type":0},{"id":2,"sLen":157.89473,"time":11842.1045,"type":0},{"id":2,"sLen":157.89473,"time":12315.7891,"type":0},{"id":2,"sLen":157.89473,"time":12631.5781,"type":0},{"id":2,"sLen":0,"time":13263.1572,"type":0},{"id":3,"sLen":0,"time":13421.0518,"type":0},{"id":1,"sLen":78.9473648000003,"time":13578.9463,"type":0},{"id":2,"sLen":157.89473,"time":13894.7363,"type":0},{"id":2,"sLen":157.89473,"time":14368.42,"type":0},{"id":0,"sLen":157.89473,"time":14842.1045,"type":0},{"id":1,"sLen":0,"time":20210.5254,"type":0},{"id":3,"sLen":0,"time":20526.3145,"type":0},{"id":1,"sLen":0,"time":20684.209,"type":0},{"id":3,"sLen":0,"time":21000,"type":0},{"id":1,"sLen":0,"time":21157.8945,"type":0},{"id":2,"sLen":0,"time":21473.6836,"type":0},{"id":1,"sLen":0,"time":21631.5781,"type":0},{"id":2,"sLen":0,"time":21947.3672,"type":0},{"id":1,"sLen":0,"time":22105.2617,"type":0},{"id":3,"sLen":0,"time":22421.05,"type":0},{"id":1,"sLen":0,"time":22578.9473,"type":0},{"id":1,"sLen":0,"time":22736.8418,"type":0},{"id":3,"sLen":0,"time":23052.63,"type":0},{"id":1,"sLen":0,"time":23210.5254,"type":0},{"id":3,"sLen":0,"time":23526.3145,"type":0},{"id":1,"sLen":0,"time":23684.209,"type":0},{"id":2,"sLen":0,"time":23999.998,"type":0},{"id":1,"sLen":0,"time":24157.8945,"type":0},{"id":2,"sLen":0,"time":24473.6836,"type":0},{"id":1,"sLen":0,"time":24631.5781,"type":0},{"id":3,"sLen":0,"time":24947.3672,"type":0},{"id":1,"sLen":0,"time":25105.2617,"type":0},{"id":2,"sLen":0,"time":30315.79,"type":0},{"id":3,"sLen":0,"time":30631.5781,"type":0},{"id":1,"sLen":0,"time":30789.4727,"type":0},{"id":3,"sLen":0,"time":30947.3672,"type":0},{"id":0,"sLen":0,"time":31263.1563,"type":0},{"id":3,"sLen":0,"time":31421.05,"type":0},{"id":0,"sLen":0,"time":31578.9453,"type":0},{"id":0,"sLen":0,"time":31894.7363,"type":0},{"id":1,"sLen":0,"time":32052.63,"type":0},{"id":3,"sLen":0,"time":32210.5254,"type":0},{"id":1,"sLen":0,"time":32526.3145,"type":0},{"id":3,"sLen":0,"time":32684.209,"type":0},{"id":0,"sLen":0,"time":32842.1055,"type":0},{"id":0,"sLen":0,"time":33157.8945,"type":0},{"id":3,"sLen":0,"time":33315.79,"type":0},{"id":0,"sLen":0,"time":33473.6836,"type":0},{"id":3,"sLen":0,"time":33789.4727,"type":0},{"id":1,"sLen":0,"time":33947.3672,"type":0},{"id":1,"sLen":0,"time":34105.26,"type":0},{"id":1,"sLen":0,"time":34421.05,"type":0},{"id":3,"sLen":0,"time":34578.9453,"type":0},{"id":1,"sLen":0,"time":34736.84,"type":0},{"id":1,"sLen":236.842100000002,"time":35052.63,"type":0},{"id":0,"sLen":0,"time":40421.05,"type":0},{"id":0,"sLen":0,"time":40736.84,"type":0},{"id":3,"sLen":0,"time":41052.63,"type":0},{"id":1,"sLen":0,"time":41210.5234,"type":0},{"id":0,"sLen":0,"time":41368.418,"type":0},{"id":3,"sLen":0,"time":41526.3125,"type":0},{"id":2,"sLen":0,"time":41684.207,"type":0},{"id":1,"sLen":0,"time":42000,"type":0},{"id":2,"sLen":0,"time":42315.79,"type":0},{"id":3,"sLen":0,"time":42473.6836,"type":0},{"id":0,"sLen":0,"time":42631.58,"type":0},{"id":3,"sLen":0,"time":42789.4727,"type":0},{"id":0,"sLen":0,"time":42947.3672,"type":0},{"id":0,"sLen":0,"time":43263.1563,"type":0},{"id":3,"sLen":0,"time":43578.9453,"type":0},{"id":1,"sLen":0,"time":43736.84,"type":0},{"id":0,"sLen":0,"time":43894.7344,"type":0},{"id":3,"sLen":0,"time":44052.63,"type":0},{"id":2,"sLen":0,"time":44210.5234,"type":0},{"id":1,"sLen":0,"time":44526.3125,"type":0},{"id":2,"sLen":0,"time":44842.1,"type":0},{"id":0,"sLen":0,"time":44999.9961,"type":0},{"id":3,"sLen":0,"time":45157.8945,"type":0},{"id":0,"sLen":0,"time":45315.79,"type":0},{"id":3,"sLen":157.89473,"time":50526.3125,"type":0},{"id":1,"sLen":157.89473,"time":51157.89,"type":0},{"id":0,"sLen":0,"time":51473.6836,"type":0},{"id":3,"sLen":0,"time":51789.4727,"type":0},{"id":1,"sLen":0,"time":52105.26,"type":0},{"id":3,"sLen":0,"time":52263.1563,"type":0},{"id":1,"sLen":0,"time":52578.9453,"type":0},{"id":3,"sLen":0,"time":52736.84,"type":0},{"id":3,"sLen":0,"time":53052.63,"type":0},{"id":3,"sLen":0,"time":53368.418,"type":0},{"id":1,"sLen":0,"time":53684.207,"type":0},{"id":0,"sLen":0,"time":53999.9961,"type":0},{"id":3,"sLen":0,"time":54315.7852,"type":0},{"id":1,"sLen":0,"time":54631.58,"type":0},{"id":3,"sLen":0,"time":54789.4727,"type":0},{"id":1,"sLen":0,"time":55105.26,"type":0},{"id":3,"sLen":0,"time":55263.1563,"type":0},{"id":2,"sLen":0,"time":60631.58,"type":0},{"id":3,"sLen":0,"time":60947.3672,"type":0},{"id":1,"sLen":0,"time":61105.26,"type":0},{"id":3,"sLen":0,"time":61263.1563,"type":0},{"id":0,"sLen":0,"time":61578.9453,"type":0},{"id":3,"sLen":0,"time":61736.84,"type":0},{"id":0,"sLen":0,"time":61894.7344,"type":0},{"id":0,"sLen":0,"time":62210.5234,"type":0},{"id":1,"sLen":0,"time":62368.418,"type":0},{"id":3,"sLen":0,"time":62526.3125,"type":0},{"id":1,"sLen":0,"time":62842.1,"type":0},{"id":3,"sLen":0,"time":62999.9961,"type":0},{"id":0,"sLen":0,"time":63157.89,"type":0},{"id":0,"sLen":0,"time":63473.68,"type":0},{"id":3,"sLen":0,"time":63631.5742,"type":0},{"id":0,"sLen":0,"time":63789.4727,"type":0},{"id":3,"sLen":0,"time":64105.26,"type":0},{"id":1,"sLen":0,"time":64263.1563,"type":0},{"id":1,"sLen":0,"time":64421.05,"type":0},{"id":1,"sLen":0,"time":64736.84,"type":0},{"id":3,"sLen":0,"time":64894.7344,"type":0},{"id":1,"sLen":0,"time":65052.63,"type":0},{"id":1,"sLen":236.842100000002,"time":65368.418,"type":0},{"id":0,"sLen":0,"time":70736.8359,"type":0},{"id":0,"sLen":0,"time":71052.625,"type":0},{"id":3,"sLen":0,"time":71368.4141,"type":0},{"id":1,"sLen":0,"time":71526.31,"type":0},{"id":0,"sLen":0,"time":71684.21,"type":0},{"id":3,"sLen":0,"time":71842.1,"type":0},{"id":2,"sLen":0,"time":72000,"type":0},{"id":1,"sLen":0,"time":72315.79,"type":0},{"id":2,"sLen":0,"time":72631.58,"type":0},{"id":3,"sLen":0,"time":72789.47,"type":0},{"id":0,"sLen":0,"time":72947.37,"type":0},{"id":3,"sLen":0,"time":73105.26,"type":0},{"id":0,"sLen":0,"time":73263.16,"type":0},{"id":0,"sLen":0,"time":73578.9453,"type":0},{"id":3,"sLen":0,"time":73894.7344,"type":0},{"id":1,"sLen":0,"time":74052.625,"type":0},{"id":0,"sLen":0,"time":74210.52,"type":0},{"id":3,"sLen":0,"time":74368.4141,"type":0},{"id":2,"sLen":0,"time":74526.31,"type":0},{"id":1,"sLen":0,"time":74842.1,"type":0},{"id":2,"sLen":0,"time":75157.89,"type":0},{"id":0,"sLen":0,"time":75315.79,"type":0},{"id":3,"sLen":0,"time":75473.68,"type":0},{"id":0,"sLen":0,"time":75631.58,"type":0},{"id":2,"sLen":0,"time":80842.1,"type":0},{"id":1,"sLen":0,"time":81157.89,"type":0},{"id":0,"sLen":0,"time":81473.68,"type":0},{"id":3,"sLen":0,"time":81631.58,"type":0},{"id":1,"sLen":0,"time":81789.47,"type":0},{"id":2,"sLen":0,"time":82105.26,"type":0},{"id":1,"sLen":0,"time":82421.05,"type":0},{"id":0,"sLen":0,"time":82736.8359,"type":0},{"id":3,"sLen":0,"time":82894.7344,"type":0},{"id":1,"sLen":0,"time":83052.625,"type":0},{"id":2,"sLen":0,"time":83368.4141,"type":0},{"id":1,"sLen":0,"time":83684.2,"type":0},{"id":0,"sLen":0,"time":84000,"type":0},{"id":3,"sLen":0,"time":84157.89,"type":0},{"id":1,"sLen":0,"time":84315.79,"type":0},{"id":2,"sLen":0,"time":84631.58,"type":0},{"id":1,"sLen":0,"time":84947.37,"type":0},{"id":0,"sLen":0,"time":85263.16,"type":0},{"id":3,"sLen":0,"time":85421.05,"type":0},{"id":1,"sLen":0,"time":85578.9453,"type":0},{"id":3,"sLen":157.89473,"time":90947.37,"type":0},{"id":1,"sLen":157.89473,"time":91578.9453,"type":0},{"id":0,"sLen":0,"time":91894.7344,"type":0},{"id":3,"sLen":0,"time":92210.52,"type":0},{"id":1,"sLen":0,"time":92526.31,"type":0},{"id":3,"sLen":0,"time":92684.2,"type":0},{"id":1,"sLen":0,"time":92999.99,"type":0},{"id":3,"sLen":0,"time":93157.89,"type":0},{"id":3,"sLen":0,"time":93473.68,"type":0},{"id":3,"sLen":0,"time":93789.47,"type":0},{"id":1,"sLen":0,"time":94105.26,"type":0},{"id":0,"sLen":0,"time":94421.05,"type":0},{"id":3,"sLen":0,"time":94736.8359,"type":0},{"id":1,"sLen":0,"time":95052.625,"type":0},{"id":3,"sLen":0,"time":95210.52,"type":0},{"id":1,"sLen":0,"time":95526.31,"type":0},{"id":3,"sLen":0,"time":95684.2,"type":0}],"characters":["spirit"],"visible":true,"type":0},{"position":"boyfriend","notes":[{"id":3,"sLen":157.89473,"time":5052.63135,"type":0},{"id":1,"sLen":157.89473,"time":5684.21045,"type":0},{"id":0,"sLen":0,"time":5999.99951,"type":0},{"id":3,"sLen":0,"time":6315.789,"type":0},{"id":1,"sLen":0,"time":6631.57861,"type":0},{"id":3,"sLen":0,"time":6789.473,"type":0},{"id":1,"sLen":0,"time":7105.2627,"type":0},{"id":3,"sLen":0,"time":7263.15771,"type":0},{"id":3,"sLen":0,"time":7578.94727,"type":0},{"id":3,"sLen":0,"time":7894.73633,"type":0},{"id":1,"sLen":0,"time":8210.526,"type":0},{"id":0,"sLen":0,"time":8526.315,"type":0},{"id":3,"sLen":0,"time":8842.10449,"type":0},{"id":1,"sLen":0,"time":9157.895,"type":0},{"id":3,"sLen":0,"time":9315.789,"type":0},{"id":1,"sLen":0,"time":9631.578,"type":0},{"id":3,"sLen":0,"time":9789.474,"type":0},{"id":2,"sLen":157.89473,"time":15157.8945,"type":0},{"id":2,"sLen":0,"time":15789.4727,"type":0},{"id":3,"sLen":0,"time":15947.3682,"type":0},{"id":1,"sLen":78.9473648000003,"time":16105.2627,"type":0},{"id":2,"sLen":157.89473,"time":16421.0527,"type":0},{"id":2,"sLen":157.89473,"time":16894.7363,"type":0},{"id":2,"sLen":157.89473,"time":17368.42,"type":0},{"id":2,"sLen":157.89473,"time":17684.209,"type":0},{"id":2,"sLen":0,"time":18315.79,"type":0},{"id":3,"sLen":0,"time":18473.6836,"type":0},{"id":1,"sLen":78.9473647999985,"time":18631.5781,"type":0},{"id":2,"sLen":157.89473,"time":18947.3672,"type":0},{"id":2,"sLen":157.89473,"time":19421.05,"type":0},{"id":0,"sLen":157.89473,"time":19894.7363,"type":0},{"id":3,"sLen":0,"time":24789.4727,"type":0},{"id":2,"sLen":0,"time":24947.3672,"type":0},{"id":0,"sLen":0,"time":25105.2617,"type":0},{"id":1,"sLen":0,"time":25263.1563,"type":0},{"id":3,"sLen":0,"time":25578.9453,"type":0},{"id":1,"sLen":0,"time":25736.8418,"type":0},{"id":3,"sLen":0,"time":26052.63,"type":0},{"id":1,"sLen":0,"time":26210.5254,"type":0},{"id":2,"sLen":0,"time":26526.3145,"type":0},{"id":1,"sLen":0,"time":26684.209,"type":0},{"id":2,"sLen":0,"time":26999.998,"type":0},{"id":1,"sLen":0,"time":27157.8926,"type":0},{"id":3,"sLen":0,"time":27473.6836,"type":0},{"id":1,"sLen":0,"time":27631.5781,"type":0},{"id":1,"sLen":0,"time":27789.4727,"type":0},{"id":3,"sLen":0,"time":28105.2617,"type":0},{"id":1,"sLen":0,"time":28263.1563,"type":0},{"id":3,"sLen":0,"time":28578.9453,"type":0},{"id":1,"sLen":0,"time":28736.84,"type":0},{"id":2,"sLen":0,"time":29052.63,"type":0},{"id":1,"sLen":0,"time":29210.5254,"type":0},{"id":2,"sLen":0,"time":29526.3145,"type":0},{"id":1,"sLen":0,"time":29684.209,"type":0},{"id":3,"sLen":0,"time":29999.998,"type":0},{"id":1,"sLen":0,"time":30157.8926,"type":0},{"id":2,"sLen":0,"time":35368.418,"type":0},{"id":3,"sLen":0,"time":35684.207,"type":0},{"id":1,"sLen":0,"time":35842.1055,"type":0},{"id":3,"sLen":0,"time":36000,"type":0},{"id":0,"sLen":0,"time":36315.79,"type":0},{"id":3,"sLen":0,"time":36473.6836,"type":0},{"id":0,"sLen":0,"time":36631.58,"type":0},{"id":0,"sLen":0,"time":36947.3672,"type":0},{"id":1,"sLen":0,"time":37105.26,"type":0},{"id":3,"sLen":0,"time":37263.1563,"type":0},{"id":1,"sLen":0,"time":37578.9453,"type":0},{"id":3,"sLen":0,"time":37736.84,"type":0},{"id":0,"sLen":0,"time":37894.7344,"type":0},{"id":0,"sLen":0,"time":38210.5234,"type":0},{"id":3,"sLen":0,"time":38368.418,"type":0},{"id":0,"sLen":0,"time":38526.3125,"type":0},{"id":3,"sLen":0,"time":38842.1,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":1,"sLen":0,"time":39157.8945,"type":0},{"id":1,"sLen":0,"time":39473.6836,"type":0},{"id":3,"sLen":0,"time":39631.58,"type":0},{"id":1,"sLen":0,"time":39789.4727,"type":0},{"id":1,"sLen":236.842100000002,"time":40105.26,"type":0},{"id":0,"sLen":0,"time":45473.6836,"type":0},{"id":0,"sLen":0,"time":45789.4727,"type":0},{"id":3,"sLen":0,"time":46105.26,"type":0},{"id":1,"sLen":0,"time":46263.1563,"type":0},{"id":0,"sLen":0,"time":46421.05,"type":0},{"id":3,"sLen":0,"time":46578.9453,"type":0},{"id":2,"sLen":0,"time":46736.84,"type":0},{"id":1,"sLen":0,"time":47052.63,"type":0},{"id":2,"sLen":0,"time":47368.418,"type":0},{"id":3,"sLen":0,"time":47526.3125,"type":0},{"id":0,"sLen":0,"time":47684.207,"type":0},{"id":3,"sLen":0,"time":47842.1,"type":0},{"id":0,"sLen":0,"time":47999.9961,"type":0},{"id":0,"sLen":0,"time":48315.79,"type":0},{"id":3,"sLen":0,"time":48631.58,"type":0},{"id":1,"sLen":0,"time":48789.4727,"type":0},{"id":0,"sLen":0,"time":48947.3672,"type":0},{"id":3,"sLen":0,"time":49105.26,"type":0},{"id":2,"sLen":0,"time":49263.1563,"type":0},{"id":1,"sLen":0,"time":49578.9453,"type":0},{"id":2,"sLen":0,"time":49894.7344,"type":0},{"id":0,"sLen":0,"time":50052.63,"type":0},{"id":3,"sLen":0,"time":50210.5234,"type":0},{"id":0,"sLen":0,"time":50368.418,"type":0},{"id":3,"sLen":157.89473,"time":55578.9453,"type":0},{"id":1,"sLen":157.89473,"time":56210.5234,"type":0},{"id":0,"sLen":0,"time":56526.3125,"type":0},{"id":3,"sLen":0,"time":56842.1,"type":0},{"id":1,"sLen":0,"time":57157.89,"type":0},{"id":3,"sLen":0,"time":57315.7852,"type":0},{"id":1,"sLen":0,"time":57631.58,"type":0},{"id":3,"sLen":0,"time":57789.4727,"type":0},{"id":3,"sLen":0,"time":58105.26,"type":0},{"id":3,"sLen":0,"time":58421.05,"type":0},{"id":1,"sLen":0,"time":58736.84,"type":0},{"id":0,"sLen":0,"time":59052.63,"type":0},{"id":3,"sLen":0,"time":59368.418,"type":0},{"id":1,"sLen":0,"time":59684.207,"type":0},{"id":3,"sLen":0,"time":59842.1,"type":0},{"id":1,"sLen":0,"time":60157.89,"type":0},{"id":3,"sLen":0,"time":60315.7852,"type":0},{"id":2,"sLen":0,"time":65684.21,"type":0},{"id":3,"sLen":0,"time":66000,"type":0},{"id":1,"sLen":0,"time":66157.89,"type":0},{"id":3,"sLen":0,"time":66315.79,"type":0},{"id":0,"sLen":0,"time":66631.58,"type":0},{"id":3,"sLen":0,"time":66789.47,"type":0},{"id":0,"sLen":0,"time":66947.37,"type":0},{"id":0,"sLen":0,"time":67263.16,"type":0},{"id":1,"sLen":0,"time":67421.05,"type":0},{"id":3,"sLen":0,"time":67578.9453,"type":0},{"id":1,"sLen":0,"time":67894.7344,"type":0},{"id":3,"sLen":0,"time":68052.625,"type":0},{"id":0,"sLen":0,"time":68210.52,"type":0},{"id":0,"sLen":0,"time":68526.31,"type":0},{"id":3,"sLen":0,"time":68684.21,"type":0},{"id":0,"sLen":0,"time":68842.1,"type":0},{"id":3,"sLen":0,"time":69157.89,"type":0},{"id":1,"sLen":0,"time":69315.79,"type":0},{"id":1,"sLen":0,"time":69473.68,"type":0},{"id":1,"sLen":0,"time":69789.47,"type":0},{"id":3,"sLen":0,"time":69947.37,"type":0},{"id":1,"sLen":0,"time":70105.26,"type":0},{"id":1,"sLen":236.842099999994,"time":70421.05,"type":0},{"id":0,"sLen":0,"time":75789.47,"type":0},{"id":0,"sLen":0,"time":76105.26,"type":0},{"id":3,"sLen":0,"time":76421.05,"type":0},{"id":1,"sLen":0,"time":76578.9453,"type":0},{"id":0,"sLen":0,"time":76736.8359,"type":0},{"id":3,"sLen":0,"time":76894.7344,"type":0},{"id":2,"sLen":0,"time":77052.625,"type":0},{"id":1,"sLen":0,"time":77368.4141,"type":0},{"id":2,"sLen":0,"time":77684.2,"type":0},{"id":3,"sLen":0,"time":77842.1,"type":0},{"id":0,"sLen":0,"time":78000,"type":0},{"id":3,"sLen":0,"time":78157.89,"type":0},{"id":0,"sLen":0,"time":78315.79,"type":0},{"id":0,"sLen":0,"time":78631.58,"type":0},{"id":3,"sLen":0,"time":78947.37,"type":0},{"id":1,"sLen":0,"time":79105.26,"type":0},{"id":0,"sLen":0,"time":79263.16,"type":0},{"id":3,"sLen":0,"time":79421.05,"type":0},{"id":2,"sLen":0,"time":79578.9453,"type":0},{"id":1,"sLen":0,"time":79894.7344,"type":0},{"id":2,"sLen":0,"time":80210.52,"type":0},{"id":0,"sLen":0,"time":80368.4141,"type":0},{"id":3,"sLen":0,"time":80526.31,"type":0},{"id":0,"sLen":0,"time":80684.2,"type":0},{"id":2,"sLen":0,"time":85894.7344,"type":0},{"id":1,"sLen":0,"time":86210.52,"type":0},{"id":0,"sLen":0,"time":86526.31,"type":0},{"id":3,"sLen":0,"time":86684.2,"type":0},{"id":1,"sLen":0,"time":86842.1,"type":0},{"id":2,"sLen":0,"time":87157.89,"type":0},{"id":1,"sLen":0,"time":87473.68,"type":0},{"id":0,"sLen":0,"time":87789.47,"type":0},{"id":3,"sLen":0,"time":87947.37,"type":0},{"id":1,"sLen":0,"time":88105.26,"type":0},{"id":2,"sLen":0,"time":88421.05,"type":0},{"id":1,"sLen":0,"time":88736.8359,"type":0},{"id":0,"sLen":0,"time":89052.625,"type":0},{"id":3,"sLen":0,"time":89210.52,"type":0},{"id":1,"sLen":0,"time":89368.4141,"type":0},{"id":2,"sLen":0,"time":89684.2,"type":0},{"id":1,"sLen":0,"time":89999.99,"type":0},{"id":0,"sLen":0,"time":90315.79,"type":0},{"id":3,"sLen":0,"time":90473.68,"type":0},{"id":1,"sLen":0,"time":90631.58,"type":0},{"id":3,"sLen":157.89473,"time":95999.99,"type":0},{"id":1,"sLen":157.89473,"time":96631.58,"type":0},{"id":0,"sLen":0,"time":96947.37,"type":0},{"id":3,"sLen":0,"time":97263.16,"type":0},{"id":1,"sLen":0,"time":97578.9453,"type":0},{"id":3,"sLen":0,"time":97736.8359,"type":0},{"id":1,"sLen":0,"time":98052.625,"type":0},{"id":3,"sLen":0,"time":98210.52,"type":0},{"id":3,"sLen":0,"time":98526.31,"type":0},{"id":3,"sLen":0,"time":98842.1,"type":0},{"id":1,"sLen":0,"time":99157.89,"type":0},{"id":0,"sLen":0,"time":99473.68,"type":0},{"id":3,"sLen":0,"time":99789.47,"type":0},{"id":1,"sLen":0,"time":100105.258,"type":0},{"id":3,"sLen":0,"time":100263.156,"type":0},{"id":1,"sLen":0,"time":100578.945,"type":0},{"id":3,"sLen":0,"time":100736.836,"type":0}],"characters":["bf-pixel"],"visible":true,"type":1},{"position":"girlfriend","notes":[],"characters":["gf-pixel"],"visible":false,"type":2}],"stage":"school-evil","scrollSpeed":1.3,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/thorns/events.json b/assets/songs/thorns/events.json index 3fcdc848..6d9cb51f 100644 --- a/assets/songs/thorns/events.json +++ b/assets/songs/thorns/events.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":5052.63157894737,"name":"Camera Movement"},{"params":[0],"time":10105.2631578947,"name":"Camera Movement"},{"params":[1],"time":15157.8947368421,"name":"Camera Movement"},{"params":[0],"time":20210.5263157895,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":20210.5263157895,"name":"HScript Call"},{"params":[1],"time":25263.1578947368,"name":"Camera Movement"},{"params":[0],"time":30315.7894736842,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":30315.7894736842,"name":"HScript Call"},{"params":[1],"time":35368.4210526316,"name":"Camera Movement"},{"params":[0],"time":40421.0526315789,"name":"Camera Movement"},{"params":[1],"time":45473.6842105263,"name":"Camera Movement"},{"params":[0],"time":50526.3157894737,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":50526.3157894737,"name":"HScript Call"},{"params":[1],"time":55578.947368421,"name":"Camera Movement"},{"params":[0],"time":60631.5789473684,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":60631.5789473684,"name":"HScript Call"},{"params":[1],"time":65684.2105263157,"name":"Camera Movement"},{"params":[0],"time":70736.8421052631,"name":"Camera Movement"},{"params":[1],"time":75789.4736842105,"name":"Camera Movement"},{"params":[0],"time":80842.1052631578,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":80842.1052631578,"name":"HScript Call"},{"params":[1],"time":85894.7368421052,"name":"Camera Movement"},{"params":[0],"time":90947.3684210525,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":90947.3684210525,"name":"HScript Call"},{"params":[1],"time":95999.9999999999,"name":"Camera Movement"}]} \ No newline at end of file +{"events":[{"params":["aberrationCoolThing",""],"time":20210.5263157895,"name":"HScript Call"},{"params":["aberrationCoolThing",""],"name":"HScript Call","time":30315.7894736842},{"params":["aberrationCoolThing",""],"time":50526.3157894737,"name":"HScript Call"},{"params":["aberrationCoolThing",""],"time":60631.5789473684,"name":"HScript Call"},{"params":["aberrationCoolThing",""],"time":80842.1052631578,"name":"HScript Call"},{"params":["aberrationCoolThing",""],"time":90947.3684210525,"name":"HScript Call"}]} \ No newline at end of file diff --git a/assets/songs/tutorial/charts/easy.json b/assets/songs/tutorial/charts/easy.json index 49a8146f..6f8aad51 100644 --- a/assets/songs/tutorial/charts/easy.json +++ b/assets/songs/tutorial/charts/easy.json @@ -1 +1 @@ -{"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file +{"events":[{"params":[1],"name":"Camera Movement","time":0},{"params":[0],"name":"Camera Movement","time":9600},{"params":[1],"name":"Camera Movement","time":14400},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"name":"Camera Movement","time":24000},{"params":[0],"name":"Camera Movement","time":28800},{"params":[1],"name":"Camera Movement","time":33600},{"params":[0],"name":"Camera Movement","time":38400},{"params":[1],"name":"Camera Movement","time":43200},{"params":[0],"name":"Camera Movement","time":48000},{"params":[1],"name":"Camera Movement","time":50400}],"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/tutorial/charts/hard.json b/assets/songs/tutorial/charts/hard.json index c417029f..81f479b9 100644 --- a/assets/songs/tutorial/charts/hard.json +++ b/assets/songs/tutorial/charts/hard.json @@ -1 +1 @@ -{"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":1,"sLen":0,"time":62550,"type":0},{"id":2,"sLen":0,"time":62700,"type":0},{"id":3,"sLen":0,"time":62850,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":1,"sLen":0,"time":63150,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":1,"sLen":0,"time":63450,"type":0},{"id":2,"sLen":0,"time":63600,"type":0},{"id":3,"sLen":0,"time":63750,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":3,"sLen":0,"time":64050,"type":0},{"id":2,"sLen":0,"time":64200,"type":0},{"id":3,"sLen":0,"time":64350,"type":0},{"id":1,"sLen":0,"time":64500,"type":0},{"id":0,"sLen":0,"time":64650,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file +{"events":[{"params":[1],"name":"Camera Movement","time":0},{"params":[0],"name":"Camera Movement","time":9600},{"params":[1],"name":"Camera Movement","time":14400},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"name":"Camera Movement","time":24000},{"params":[0],"name":"Camera Movement","time":28800},{"params":[1],"name":"Camera Movement","time":33600},{"params":[0],"name":"Camera Movement","time":38400},{"params":[1],"name":"Camera Movement","time":43200},{"params":[0],"name":"Camera Movement","time":48000},{"params":[1],"name":"Camera Movement","time":50400}],"codenameChart":true,"scrollSpeed":1,"strumLines":[{"keyCount":4,"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"position":"girlfriend","characters":["gf"],"visible":true,"type":0},{"keyCount":4,"notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":1,"sLen":0,"time":62550,"type":0},{"id":2,"sLen":0,"time":62700,"type":0},{"id":3,"sLen":0,"time":62850,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":1,"sLen":0,"time":63150,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":1,"sLen":0,"time":63450,"type":0},{"id":2,"sLen":0,"time":63600,"type":0},{"id":3,"sLen":0,"time":63750,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":3,"sLen":0,"time":64050,"type":0},{"id":2,"sLen":0,"time":64200,"type":0},{"id":3,"sLen":0,"time":64350,"type":0},{"id":1,"sLen":0,"time":64500,"type":0},{"id":0,"sLen":0,"time":64650,"type":0}],"position":"boyfriend","characters":["bf"],"visible":true,"type":1}],"chartVersion":"1.6.0","noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/tutorial/charts/normal.json b/assets/songs/tutorial/charts/normal.json index 49a8146f..6f8aad51 100644 --- a/assets/songs/tutorial/charts/normal.json +++ b/assets/songs/tutorial/charts/normal.json @@ -1 +1 @@ -{"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file +{"events":[{"params":[1],"name":"Camera Movement","time":0},{"params":[0],"name":"Camera Movement","time":9600},{"params":[1],"name":"Camera Movement","time":14400},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"name":"Camera Movement","time":24000},{"params":[0],"name":"Camera Movement","time":28800},{"params":[1],"name":"Camera Movement","time":33600},{"params":[0],"name":"Camera Movement","time":38400},{"params":[1],"name":"Camera Movement","time":43200},{"params":[0],"name":"Camera Movement","time":48000},{"params":[1],"name":"Camera Movement","time":50400}],"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/tutorial/events.json b/assets/songs/tutorial/events.json index a97b4b89..bb95d83e 100644 --- a/assets/songs/tutorial/events.json +++ b/assets/songs/tutorial/events.json @@ -1 +1 @@ -{"events":[{"params":[1],"name":"Camera Movement","time":0},{"params":[0],"name":"Camera Movement","time":9600},{"params":[1],"name":"Camera Movement","time":14400},{"params":[0,"cheer"],"name":"Play Animation","time":18000},{"params":[1,"hey"],"name":"Play Animation","time":18600},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"name":"Camera Movement","time":24000},{"params":[0,"cheer"],"time":27600,"name":"Play Animation"},{"params":[1,"hey"],"time":28200,"name":"Play Animation"},{"params":[0],"name":"Camera Movement","time":28800},{"params":[1],"name":"Camera Movement","time":33600},{"params":[0],"name":"Camera Movement","time":38400},{"params":[1],"name":"Camera Movement","time":43200},{"params":[0],"name":"Camera Movement","time":48000},{"params":[1],"name":"Camera Movement","time":50400}]} \ No newline at end of file +{"events":[{"params":[0,"cheer"],"name":"Play Animation","time":18000},{"params":[1,"hey"],"name":"Play Animation","time":18600},{"params":[0,"cheer"],"time":27600,"name":"Play Animation"},{"params":[1,"hey"],"time":28200,"name":"Play Animation"}]} \ No newline at end of file diff --git a/assets/songs/winter horrorland/charts/easy.json b/assets/songs/winter horrorland/charts/easy.json index 4ed4f5d9..983b5955 100644 --- a/assets/songs/winter horrorland/charts/easy.json +++ b/assets/songs/winter horrorland/charts/easy.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall-evil","song":"Winter Horrorland","bpm":159.0,"needsVoices":true,"player1":"bf-christmas","player2":"Monster-christmas","speed":1.0,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9433.962,0,283.01886],[9811.32,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[10566.0381,0,660.3774],[11320.7549,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[12452.83,0,283.01886],[12830.1885,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[13584.9053,1,0.0],[14339.623,3,283.01886],[14716.9814,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[15094.34,5,754.717],[15471.6982,0,283.01886],[15849.0566,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16603.7734,0,660.3774],[17358.49,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[18490.5664,0,283.01886],[18867.9238,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[19622.64,1,0.0],[20377.36,3,283.01886],[20754.7168,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21132.0762,1,283.01886],[21132.0762,5,754.717],[21886.793,2,283.01886],[22264.15,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22641.51,1,471.69812],[23396.2266,0,0.0],[23773.584,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24150.9434,3,283.01886],[24528.3027,2,0.0],[24905.66,2,0.0],[25283.02,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[25660.377,1,471.69812],[26415.0938,3,0.0],[26792.4531,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27169.81,1,283.01886],[27924.5273,2,283.01886],[28301.8867,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28679.2461,1,471.69812],[29433.9629,0,0.0],[29811.32,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[30188.68,3,283.01886],[30566.0371,2,0.0],[30943.3965,2,0.0],[31320.7539,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31698.1133,1,471.69812],[32452.83,3,0.0],[32830.1875,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33584.9063,2,0.0],[33962.2656,0,283.01886],[34528.3,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[35094.34,2,283.01886],[35471.7,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[36603.7734,2,0.0],[36981.1328,0,283.01886],[37358.4922,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[37735.8477,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[39622.64,2,0.0],[40000.0,0,283.01886],[40566.04,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[41132.0742,2,283.01886],[41509.4336,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[42641.5078,2,0.0],[43018.8672,0,283.01886],[43396.2266,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43773.5859,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45283.02,0,283.01886],[46037.7344,2,0.0],[46415.0938,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46792.4531,5,283.01886],[47169.8125,0,0.0],[47547.168,3,0.0],[47924.5273,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48301.8867,2,283.01886],[48301.8867,6,849.0566],[48679.2461,0,188.679245],[49056.6055,3,0.0],[49433.96,3,0.0],[49622.64,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49937.1055,2,0.0],[50188.68,2,0.0],[50440.25,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[51320.7539,0,283.01886],[52075.4727,2,0.0],[52452.832,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52830.1875,5,283.01886],[53207.5469,0,0.0],[53584.9063,3,0.0],[53962.2656,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54339.62,6,1132.07544],[54339.62,2,283.01886],[54716.98,0,188.679245],[55094.34,3,0.0],[55471.7,3,0.0],[55660.38,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56037.7344,2,0.0],[56415.0938,2,0.0],[56603.7734,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57547.168,2,0.0],[58113.207,2,0.0],[58490.5664,2,0.0],[58679.2461,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[58867.9258,1,0.0],[59245.28,3,283.01886],[59622.64,2,0.0],[60000.0,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60566.04,2,0.0],[61132.0742,2,0.0],[61509.4336,2,0.0],[61698.1133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[61886.793,1,0.0],[62264.1523,3,283.01886],[62641.5078,2,0.0],[63018.8672,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[63396.2266,3,0.0],[63773.5859,1,283.01886],[64150.94,0,0.0],[64528.3,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64905.66,4,0.0],[64905.66,3,0.0],[65283.02,1,283.01886],[65660.375,0,0.0],[66037.7344,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[66415.09,0,0.0],[66415.09,4,0.0],[66981.13,7,0.0],[66981.13,3,0.0],[67358.49,6,0.0],[67358.49,2,0.0],[67735.85,5,754.717],[67735.85,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[69622.64,0,0.0],[70188.68,1,0.0],[70754.72,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[71132.08,2,0.0],[71320.76,1,0.0],[71698.11,2,0.0],[72075.47,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72452.83,2,283.01886],[72830.19,0,283.01886],[73207.55,3,283.01886],[73584.91,0,0.0],[73773.5859,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[74716.9844,3,0.0],[74905.6641,0,0.0],[75094.3359,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75660.375,0,0.0],[76226.4141,1,0.0],[76792.45,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[77169.81,2,0.0],[77358.49,1,0.0],[77735.85,2,0.0],[78113.21,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78490.56,2,283.01886],[78867.92,0,283.01886],[79245.28,3,283.01886],[79622.64,0,0.0],[79811.32,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80754.72,3,0.0],[80943.4,0,0.0],[81132.08,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[81509.44,1,0.0],[81886.79,3,0.0],[82264.15,0,0.0],[82641.51,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83018.87,1,0.0],[83396.23,3,0.0],[83773.5859,0,0.0],[84150.9453,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[84528.3047,2,0.0],[84905.6641,2,0.0],[85094.3359,0,0.0],[85283.0156,2,0.0],[85660.375,2,0.0],[85849.0547,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[86037.7344,2,0.0],[86415.09,2,0.0],[86603.77,0,0.0],[86792.45,2,0.0],[87169.81,2,0.0],[87358.49,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[87547.17,3,283.01886],[88113.21,1,283.01886],[88301.88,4,0.0],[88679.24,7,283.01886],[88679.24,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89056.6,5,283.01886],[89056.6,3,0.0],[89433.96,3,0.0],[89811.32,1,283.01886],[89811.32,7,0.0],[90188.68,6,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[90566.04,5,283.01886],[90566.04,0,0.0],[91132.08,3,0.0],[91698.11,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92075.47,2,566.0377],[92075.47,6,566.0377],[92830.19,3,566.0377],[92830.19,4,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[93773.5859,3,0.0],[94150.9453,7,0.0],[94339.625,0,283.01886],[94716.9844,4,283.01886],[94905.6641,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[95283.0156,3,283.01886],[95471.6953,6,283.01886],[95849.0547,4,283.01886],[95849.0547,2,283.01886],[96226.4141,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[96792.45,7,94.33962],[96981.13,6,0.0],[97169.81,3,94.33962],[97358.49,4,283.01886],[97358.49,2,0.0],[97735.85,0,283.01886],[97735.85,5,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98113.21,5,283.01886],[98113.21,0,283.01886],[98490.56,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[99811.32,3,0.0],[100188.68,7,0.0],[100377.359,0,283.01886],[100754.719,4,283.01886],[100943.4,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[101320.758,3,283.01886],[101509.438,6,283.01886],[101886.789,4,283.01886],[101886.789,2,283.01886],[102264.148,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[102830.188,3,94.33962],[103207.547,7,94.33962],[103396.227,0,283.01886],[103773.586,1,283.01886],[103773.586,4,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[104150.945,5,283.01886],[104150.945,0,283.01886],[104528.305,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[105849.055,2,0.0],[106415.094,2,0.0],[106792.453,2,0.0],[106981.133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[107169.813,1,0.0],[107547.172,3,283.01886],[107924.531,2,0.0],[108301.883,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[108867.922,2,0.0],[109433.961,2,0.0],[109811.32,2,0.0],[110000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[110188.68,1,0.0],[110566.039,3,283.01886],[110943.4,2,0.0],[111320.758,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[111698.109,3,0.0],[112075.469,1,283.01886],[112452.828,0,0.0],[112830.188,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[113207.547,4,0.0],[113207.547,3,0.0],[113584.906,1,283.01886],[113962.266,0,0.0],[114339.625,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[114716.984,4,0.0],[114716.984,0,0.0],[115283.016,3,0.0],[115283.016,7,0.0],[115660.375,6,0.0],[115660.375,2,0.0],[116037.734,1,754.717],[116037.734,5,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[117924.531,0,0.0],[118490.563,1,0.0],[118867.922,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[119245.281,2,283.01886],[119622.641,3,283.01886],[120000.0,0,283.01886],[120377.359,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[120754.719,6,754.717],[120943.4,0,0.0],[121509.438,1,0.0],[121886.789,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[122264.148,2,283.01886],[122641.508,3,283.01886],[123018.867,0,283.01886],[123396.227,3,754.717]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[123773.586,1,94.33962],[124150.945,3,0.0],[124528.305,2,283.01886],[124905.664,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[125283.016,2,0.0],[125283.016,5,283.01886],[125660.375,1,283.01886],[126037.734,3,0.0],[126415.094,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[126792.453,0,0.0],[126792.453,4,0.0],[127358.492,3,0.0],[127358.492,7,0.0],[127735.852,6,0.0],[127735.852,2,0.0],[128113.211,1,377.3585],[128113.211,5,377.3585]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129056.6,0,0.0],[129245.281,3,0.0],[129433.961,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129811.32,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"song":{"gf":"gf-christmas","stage":"mall-evil","song":"Winter Horrorland","bpm":159.0,"needsVoices":true,"player1":"bf-christmas","player2":"monster-christmas","speed":1.0,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9433.962,0,283.01886],[9811.32,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[10566.0381,0,660.3774],[11320.7549,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[12452.83,0,283.01886],[12830.1885,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[13584.9053,1,0.0],[14339.623,3,283.01886],[14716.9814,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[15094.34,5,754.717],[15471.6982,0,283.01886],[15849.0566,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16603.7734,0,660.3774],[17358.49,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[18490.5664,0,283.01886],[18867.9238,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[19622.64,1,0.0],[20377.36,3,283.01886],[20754.7168,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21132.0762,1,283.01886],[21132.0762,5,754.717],[21886.793,2,283.01886],[22264.15,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22641.51,1,471.69812],[23396.2266,0,0.0],[23773.584,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24150.9434,3,283.01886],[24528.3027,2,0.0],[24905.66,2,0.0],[25283.02,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[25660.377,1,471.69812],[26415.0938,3,0.0],[26792.4531,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27169.81,1,283.01886],[27924.5273,2,283.01886],[28301.8867,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28679.2461,1,471.69812],[29433.9629,0,0.0],[29811.32,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[30188.68,3,283.01886],[30566.0371,2,0.0],[30943.3965,2,0.0],[31320.7539,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31698.1133,1,471.69812],[32452.83,3,0.0],[32830.1875,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33584.9063,2,0.0],[33962.2656,0,283.01886],[34528.3,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[35094.34,2,283.01886],[35471.7,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[36603.7734,2,0.0],[36981.1328,0,283.01886],[37358.4922,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[37735.8477,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[39622.64,2,0.0],[40000.0,0,283.01886],[40566.04,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[41132.0742,2,283.01886],[41509.4336,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[42641.5078,2,0.0],[43018.8672,0,283.01886],[43396.2266,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43773.5859,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45283.02,0,283.01886],[46037.7344,2,0.0],[46415.0938,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46792.4531,5,283.01886],[47169.8125,0,0.0],[47547.168,3,0.0],[47924.5273,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48301.8867,2,283.01886],[48301.8867,6,849.0566],[48679.2461,0,188.679245],[49056.6055,3,0.0],[49433.96,3,0.0],[49622.64,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49937.1055,2,0.0],[50188.68,2,0.0],[50440.25,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[51320.7539,0,283.01886],[52075.4727,2,0.0],[52452.832,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52830.1875,5,283.01886],[53207.5469,0,0.0],[53584.9063,3,0.0],[53962.2656,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54339.62,6,1132.07544],[54339.62,2,283.01886],[54716.98,0,188.679245],[55094.34,3,0.0],[55471.7,3,0.0],[55660.38,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56037.7344,2,0.0],[56415.0938,2,0.0],[56603.7734,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57547.168,2,0.0],[58113.207,2,0.0],[58490.5664,2,0.0],[58679.2461,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[58867.9258,1,0.0],[59245.28,3,283.01886],[59622.64,2,0.0],[60000.0,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60566.04,2,0.0],[61132.0742,2,0.0],[61509.4336,2,0.0],[61698.1133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[61886.793,1,0.0],[62264.1523,3,283.01886],[62641.5078,2,0.0],[63018.8672,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[63396.2266,3,0.0],[63773.5859,1,283.01886],[64150.94,0,0.0],[64528.3,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64905.66,4,0.0],[64905.66,3,0.0],[65283.02,1,283.01886],[65660.375,0,0.0],[66037.7344,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[66415.09,0,0.0],[66415.09,4,0.0],[66981.13,7,0.0],[66981.13,3,0.0],[67358.49,6,0.0],[67358.49,2,0.0],[67735.85,5,754.717],[67735.85,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[69622.64,0,0.0],[70188.68,1,0.0],[70754.72,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[71132.08,2,0.0],[71320.76,1,0.0],[71698.11,2,0.0],[72075.47,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72452.83,2,283.01886],[72830.19,0,283.01886],[73207.55,3,283.01886],[73584.91,0,0.0],[73773.5859,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[74716.9844,3,0.0],[74905.6641,0,0.0],[75094.3359,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75660.375,0,0.0],[76226.4141,1,0.0],[76792.45,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[77169.81,2,0.0],[77358.49,1,0.0],[77735.85,2,0.0],[78113.21,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78490.56,2,283.01886],[78867.92,0,283.01886],[79245.28,3,283.01886],[79622.64,0,0.0],[79811.32,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80754.72,3,0.0],[80943.4,0,0.0],[81132.08,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[81509.44,1,0.0],[81886.79,3,0.0],[82264.15,0,0.0],[82641.51,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83018.87,1,0.0],[83396.23,3,0.0],[83773.5859,0,0.0],[84150.9453,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[84528.3047,2,0.0],[84905.6641,2,0.0],[85094.3359,0,0.0],[85283.0156,2,0.0],[85660.375,2,0.0],[85849.0547,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[86037.7344,2,0.0],[86415.09,2,0.0],[86603.77,0,0.0],[86792.45,2,0.0],[87169.81,2,0.0],[87358.49,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[87547.17,3,283.01886],[88113.21,1,283.01886],[88301.88,4,0.0],[88679.24,7,283.01886],[88679.24,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89056.6,5,283.01886],[89056.6,3,0.0],[89433.96,3,0.0],[89811.32,1,283.01886],[89811.32,7,0.0],[90188.68,6,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[90566.04,5,283.01886],[90566.04,0,0.0],[91132.08,3,0.0],[91698.11,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92075.47,2,566.0377],[92075.47,6,566.0377],[92830.19,3,566.0377],[92830.19,4,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[93773.5859,3,0.0],[94150.9453,7,0.0],[94339.625,0,283.01886],[94716.9844,4,283.01886],[94905.6641,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[95283.0156,3,283.01886],[95471.6953,6,283.01886],[95849.0547,4,283.01886],[95849.0547,2,283.01886],[96226.4141,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[96792.45,7,94.33962],[96981.13,6,0.0],[97169.81,3,94.33962],[97358.49,4,283.01886],[97358.49,2,0.0],[97735.85,0,283.01886],[97735.85,5,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98113.21,5,283.01886],[98113.21,0,283.01886],[98490.56,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[99811.32,3,0.0],[100188.68,7,0.0],[100377.359,0,283.01886],[100754.719,4,283.01886],[100943.4,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[101320.758,3,283.01886],[101509.438,6,283.01886],[101886.789,4,283.01886],[101886.789,2,283.01886],[102264.148,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[102830.188,3,94.33962],[103207.547,7,94.33962],[103396.227,0,283.01886],[103773.586,1,283.01886],[103773.586,4,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[104150.945,5,283.01886],[104150.945,0,283.01886],[104528.305,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[105849.055,2,0.0],[106415.094,2,0.0],[106792.453,2,0.0],[106981.133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[107169.813,1,0.0],[107547.172,3,283.01886],[107924.531,2,0.0],[108301.883,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[108867.922,2,0.0],[109433.961,2,0.0],[109811.32,2,0.0],[110000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[110188.68,1,0.0],[110566.039,3,283.01886],[110943.4,2,0.0],[111320.758,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[111698.109,3,0.0],[112075.469,1,283.01886],[112452.828,0,0.0],[112830.188,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[113207.547,4,0.0],[113207.547,3,0.0],[113584.906,1,283.01886],[113962.266,0,0.0],[114339.625,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[114716.984,4,0.0],[114716.984,0,0.0],[115283.016,3,0.0],[115283.016,7,0.0],[115660.375,6,0.0],[115660.375,2,0.0],[116037.734,1,754.717],[116037.734,5,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[117924.531,0,0.0],[118490.563,1,0.0],[118867.922,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[119245.281,2,283.01886],[119622.641,3,283.01886],[120000.0,0,283.01886],[120377.359,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[120754.719,6,754.717],[120943.4,0,0.0],[121509.438,1,0.0],[121886.789,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[122264.148,2,283.01886],[122641.508,3,283.01886],[123018.867,0,283.01886],[123396.227,3,754.717]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[123773.586,1,94.33962],[124150.945,3,0.0],[124528.305,2,283.01886],[124905.664,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[125283.016,2,0.0],[125283.016,5,283.01886],[125660.375,1,283.01886],[126037.734,3,0.0],[126415.094,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[126792.453,0,0.0],[126792.453,4,0.0],[127358.492,3,0.0],[127358.492,7,0.0],[127735.852,6,0.0],[127735.852,2,0.0],[128113.211,1,377.3585],[128113.211,5,377.3585]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129056.6,0,0.0],[129245.281,3,0.0],[129433.961,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129811.32,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} diff --git a/assets/songs/winter horrorland/charts/hard.json b/assets/songs/winter horrorland/charts/hard.json index fe118e9c..ce698c12 100644 --- a/assets/songs/winter horrorland/charts/hard.json +++ b/assets/songs/winter horrorland/charts/hard.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall-evil","song":"Winter Horrorland","bpm":159.0,"needsVoices":true,"player1":"bf-christmas","player2":"Monster-christmas","speed":1.3,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9433.962,0,283.01886],[9811.32,2,283.01886],[10188.68,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[10566.0381,0,660.3774],[11320.7549,2,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[12452.83,0,283.01886],[12830.1885,2,283.01886],[13207.5469,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[13584.9053,1,0.0],[14150.9434,2,0.0],[14339.623,3,283.01886],[14716.9814,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[15094.34,5,754.717],[15471.6982,0,283.01886],[15849.0566,2,283.01886],[16226.415,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16603.7734,0,660.3774],[17358.49,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[18490.5664,0,283.01886],[18867.9238,2,283.01886],[19245.2832,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[19622.64,1,0.0],[20188.68,2,0.0],[20377.36,3,283.01886],[20754.7168,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21132.0762,1,283.01886],[21132.0762,5,754.717],[21509.4336,3,283.01886],[21886.793,2,283.01886],[22264.15,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22641.51,1,471.69812],[23207.5469,0,0.0],[23301.8867,2,0.0],[23396.2266,0,0.0],[23584.9063,3,0.0],[23773.584,0,0.0],[23962.2637,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24150.9434,3,283.01886],[24528.3027,2,0.0],[24716.98,0,0.0],[24905.66,2,0.0],[25094.34,0,0.0],[25283.02,2,0.0],[25471.6973,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[25660.377,1,471.69812],[26226.416,2,0.0],[26415.0938,3,0.0],[26603.7734,2,0.0],[26792.4531,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27169.81,1,283.01886],[27547.17,3,283.01886],[27924.5273,2,283.01886],[28301.8867,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28679.2461,1,471.69812],[29245.2832,0,0.0],[29339.623,2,0.0],[29433.9629,0,0.0],[29622.64,3,0.0],[29811.32,0,0.0],[30000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[30188.68,3,283.01886],[30566.0371,2,0.0],[30754.7168,0,0.0],[30943.3965,2,0.0],[31132.0762,0,0.0],[31320.7539,2,0.0],[31509.4336,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31698.1133,1,471.69812],[32264.15,2,0.0],[32452.83,3,0.0],[32641.51,2,0.0],[32830.1875,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33396.2266,3,0.0],[33490.5664,3,0.0],[33584.9063,2,0.0],[33773.5859,1,0.0],[33962.2656,0,283.01886],[34528.3,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[35094.34,2,283.01886],[35471.7,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[36415.0938,3,94.33962],[36603.7734,2,94.33962],[36792.4531,1,94.33962],[36981.1328,0,283.01886],[37358.4922,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[37735.8477,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[39433.96,3,0.0],[39528.3,3,0.0],[39622.64,2,0.0],[39811.32,1,0.0],[40000.0,0,283.01886],[40566.04,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[41132.0742,2,283.01886],[41509.4336,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[42452.832,3,94.33962],[42641.5078,2,94.33962],[42830.1875,1,94.33962],[43018.8672,0,283.01886],[43396.2266,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43773.5859,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45283.02,0,283.01886],[45660.38,1,283.01886],[46037.7344,2,0.0],[46226.4141,3,0.0],[46415.0938,2,0.0],[46603.7734,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46792.4531,5,283.01886],[47169.8125,0,0.0],[47547.168,3,0.0],[47924.5273,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48301.8867,6,849.0566],[48301.8867,2,283.01886],[48679.2461,0,188.679245],[49056.6055,3,0.0],[49245.28,0,0.0],[49339.62,0,0.0],[49433.96,3,0.0],[49622.64,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49937.1055,2,0.0],[50188.68,2,0.0],[50440.25,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[51320.7539,0,283.01886],[51698.1133,1,283.01886],[52075.4727,2,0.0],[52264.1523,3,0.0],[52452.832,2,0.0],[52641.5078,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52830.1875,5,283.01886],[53207.5469,0,0.0],[53584.9063,3,0.0],[53962.2656,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54339.62,6,1132.07544],[54339.62,2,188.679245],[54716.98,0,188.679245],[55094.34,3,0.0],[55283.02,0,0.0],[55377.36,0,0.0],[55471.7,3,0.0],[55660.38,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56037.7344,2,0.0],[56415.0938,2,0.0],[56603.7734,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57547.168,2,0.0],[57924.5273,2,0.0],[58018.8672,3,0.0],[58113.207,2,0.0],[58301.8867,0,0.0],[58490.5664,2,0.0],[58679.2461,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[58867.9258,1,0.0],[59056.6055,0,0.0],[59245.28,3,283.01886],[59622.64,2,0.0],[59811.32,3,0.0],[60000.0,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60566.04,2,0.0],[60943.3945,2,0.0],[61037.7344,3,0.0],[61132.0742,2,0.0],[61320.7539,0,0.0],[61509.4336,2,0.0],[61698.1133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[61886.793,1,0.0],[62075.4727,0,0.0],[62264.1523,3,283.01886],[62641.5078,2,0.0],[62830.1875,3,0.0],[63018.8672,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[63396.2266,3,0.0],[63584.9063,3,0.0],[63773.5859,1,283.01886],[64150.94,0,0.0],[64528.3,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64905.66,4,0.0],[64905.66,3,0.0],[65094.34,3,0.0],[65283.02,1,283.01886],[65660.375,0,0.0],[65849.0547,0,0.0],[66037.7344,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[66415.09,4,0.0],[66415.09,0,0.0],[66603.77,5,0.0],[66603.77,1,0.0],[66981.13,7,0.0],[66981.13,3,0.0],[67358.49,6,0.0],[67358.49,2,0.0],[67735.85,1,754.717],[67735.85,5,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[69622.64,0,0.0],[69716.9844,0,0.0],[69811.32,3,0.0],[70188.68,1,0.0],[70377.36,0,0.0],[70566.04,2,0.0],[70754.72,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[71132.08,2,0.0],[71226.4141,3,0.0],[71320.76,1,0.0],[71509.44,0,0.0],[71698.11,2,0.0],[71886.79,3,0.0],[72075.47,0,0.0],[72264.15,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72452.83,2,283.01886],[72830.19,0,283.01886],[73207.55,3,283.01886],[73584.91,0,0.0],[73773.5859,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[74528.3047,0,0.0],[74716.9844,3,0.0],[74905.6641,0,0.0],[75094.3359,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75660.375,0,0.0],[75754.72,0,0.0],[75849.0547,3,0.0],[76226.4141,1,0.0],[76415.09,0,0.0],[76603.77,2,0.0],[76792.45,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[77169.81,2,0.0],[77264.15,3,0.0],[77358.49,1,0.0],[77547.17,0,0.0],[77735.85,2,0.0],[77924.53,3,0.0],[78113.21,0,0.0],[78301.88,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78490.56,2,283.01886],[78867.92,0,283.01886],[79245.28,3,283.01886],[79622.64,0,0.0],[79811.32,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80566.04,0,0.0],[80754.72,3,0.0],[80943.4,0,0.0],[81132.08,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[81509.44,1,0.0],[81698.11,1,0.0],[81886.79,3,0.0],[82075.47,3,0.0],[82264.15,0,0.0],[82452.83,3,0.0],[82641.51,2,0.0],[82830.19,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83018.87,1,0.0],[83207.55,1,0.0],[83396.23,3,0.0],[83584.91,3,0.0],[83773.5859,0,0.0],[83962.2656,3,0.0],[84150.9453,2,0.0],[84339.625,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[84528.3047,2,0.0],[84716.9844,3,0.0],[84905.6641,2,0.0],[85094.3359,0,0.0],[85283.0156,2,0.0],[85471.6953,3,0.0],[85660.375,2,0.0],[85849.0547,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[86037.7344,2,0.0],[86226.4141,3,0.0],[86415.09,2,0.0],[86603.77,0,0.0],[86792.45,2,0.0],[86981.13,3,0.0],[87169.81,2,0.0],[87358.49,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[87547.17,3,283.01886],[87924.53,0,94.33962],[88113.21,1,283.01886],[88301.88,4,0.0],[88490.56,0,0.0],[88490.56,7,0.0],[88679.24,1,0.0],[88679.24,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89056.6,5,283.01886],[89056.6,3,0.0],[89433.96,3,0.0],[89811.32,1,283.01886],[89811.32,7,0.0],[90000.0,4,0.0],[90188.68,6,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[90566.04,5,283.01886],[90566.04,0,0.0],[91132.08,3,0.0],[91698.11,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92075.47,6,566.0377],[92075.47,2,566.0377],[92830.19,3,566.0377],[92830.19,4,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[93773.5859,3,0.0],[93867.92,3,0.0],[93962.2656,2,0.0],[94150.9453,1,0.0],[94150.9453,7,0.0],[94245.28,7,0.0],[94339.625,0,283.01886],[94339.625,6,0.0],[94528.3047,5,0.0],[94716.9844,4,283.01886],[94905.6641,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[95283.0156,3,283.01886],[95471.6953,6,283.01886],[95849.0547,2,283.01886],[95849.0547,4,283.01886],[96226.4141,0,283.01886],[96226.4141,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[96603.77,3,0.0],[96792.45,7,94.33962],[96981.13,6,94.33962],[97169.81,5,94.33962],[97169.81,3,94.33962],[97358.49,4,283.01886],[97358.49,2,94.33962],[97547.17,1,94.33962],[97735.85,0,283.01886],[97735.85,5,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98113.21,5,283.01886],[98113.21,0,283.01886],[98490.56,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[99811.32,3,0.0],[99905.6641,3,0.0],[100000.0,2,0.0],[100188.68,7,0.0],[100188.68,1,0.0],[100283.016,7,0.0],[100377.359,6,0.0],[100377.359,0,283.01886],[100566.039,5,0.0],[100754.719,4,283.01886],[100943.4,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[101320.758,3,283.01886],[101509.438,6,283.01886],[101886.789,2,283.01886],[101886.789,4,283.01886],[102264.148,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[102830.188,3,94.33962],[103018.867,2,94.33962],[103207.547,7,94.33962],[103207.547,1,94.33962],[103396.227,0,283.01886],[103396.227,6,94.33962],[103584.906,5,94.33962],[103773.586,1,283.01886],[103773.586,4,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[104150.945,0,283.01886],[104150.945,5,283.01886],[104528.305,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[105849.055,2,0.0],[106226.414,2,0.0],[106320.758,3,0.0],[106415.094,2,0.0],[106603.773,0,0.0],[106792.453,2,0.0],[106981.133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[107169.813,1,0.0],[107358.492,0,0.0],[107547.172,3,283.01886],[107924.531,2,0.0],[108113.211,3,0.0],[108301.883,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[108867.922,2,0.0],[109245.281,2,0.0],[109339.625,3,0.0],[109433.961,2,0.0],[109622.641,0,0.0],[109811.32,2,0.0],[110000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[110188.68,1,0.0],[110377.359,0,0.0],[110566.039,3,283.01886],[110943.4,2,0.0],[111132.078,3,0.0],[111320.758,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[111698.109,3,0.0],[111886.789,3,0.0],[112075.469,1,283.01886],[112452.828,0,0.0],[112830.188,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[113207.547,4,0.0],[113207.547,3,0.0],[113396.227,3,0.0],[113584.906,1,283.01886],[113962.266,0,0.0],[114150.945,0,0.0],[114339.625,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[114716.984,0,0.0],[114716.984,4,0.0],[114905.664,1,0.0],[114905.664,5,0.0],[115283.016,3,0.0],[115283.016,7,0.0],[115660.375,2,0.0],[115660.375,6,0.0],[116037.734,5,754.717],[116037.734,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[117924.531,0,0.0],[118301.883,3,0.0],[118490.563,1,0.0],[118679.242,0,0.0],[118867.922,2,0.0],[119056.6,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[119245.281,2,283.01886],[119622.641,3,283.01886],[120000.0,0,283.01886],[120377.359,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[120754.719,6,754.717],[120943.4,0,0.0],[121320.758,3,0.0],[121509.438,1,0.0],[121698.109,0,0.0],[121886.789,2,0.0],[122075.469,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[122264.148,2,283.01886],[122641.508,3,283.01886],[123018.867,0,283.01886],[123396.227,3,754.717]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[123773.586,1,94.33962],[124150.945,3,0.0],[124339.625,3,0.0],[124528.305,2,283.01886],[124905.664,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[125283.016,5,283.01886],[125283.016,2,0.0],[125471.695,3,0.0],[125660.375,1,283.01886],[126037.734,3,0.0],[126226.414,0,0.0],[126415.094,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[126792.453,0,0.0],[126792.453,4,0.0],[126981.133,1,0.0],[126981.133,5,0.0],[127358.492,3,0.0],[127358.492,7,0.0],[127735.852,2,0.0],[127735.852,6,0.0],[128113.211,1,377.3585],[128113.211,5,377.3585]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129056.6,0,0.0],[129245.281,3,0.0],[129433.961,0,0.0],[129622.641,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129811.32,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"song":{"gf":"gf-christmas","stage":"mall-evil","song":"Winter Horrorland","bpm":159.0,"needsVoices":true,"player1":"bf-christmas","player2":"monster-christmas","speed":1.3,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9433.962,0,283.01886],[9811.32,2,283.01886],[10188.68,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[10566.0381,0,660.3774],[11320.7549,2,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[12452.83,0,283.01886],[12830.1885,2,283.01886],[13207.5469,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[13584.9053,1,0.0],[14150.9434,2,0.0],[14339.623,3,283.01886],[14716.9814,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[15094.34,5,754.717],[15471.6982,0,283.01886],[15849.0566,2,283.01886],[16226.415,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16603.7734,0,660.3774],[17358.49,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[18490.5664,0,283.01886],[18867.9238,2,283.01886],[19245.2832,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[19622.64,1,0.0],[20188.68,2,0.0],[20377.36,3,283.01886],[20754.7168,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21132.0762,1,283.01886],[21132.0762,5,754.717],[21509.4336,3,283.01886],[21886.793,2,283.01886],[22264.15,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22641.51,1,471.69812],[23207.5469,0,0.0],[23301.8867,2,0.0],[23396.2266,0,0.0],[23584.9063,3,0.0],[23773.584,0,0.0],[23962.2637,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24150.9434,3,283.01886],[24528.3027,2,0.0],[24716.98,0,0.0],[24905.66,2,0.0],[25094.34,0,0.0],[25283.02,2,0.0],[25471.6973,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[25660.377,1,471.69812],[26226.416,2,0.0],[26415.0938,3,0.0],[26603.7734,2,0.0],[26792.4531,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27169.81,1,283.01886],[27547.17,3,283.01886],[27924.5273,2,283.01886],[28301.8867,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28679.2461,1,471.69812],[29245.2832,0,0.0],[29339.623,2,0.0],[29433.9629,0,0.0],[29622.64,3,0.0],[29811.32,0,0.0],[30000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[30188.68,3,283.01886],[30566.0371,2,0.0],[30754.7168,0,0.0],[30943.3965,2,0.0],[31132.0762,0,0.0],[31320.7539,2,0.0],[31509.4336,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31698.1133,1,471.69812],[32264.15,2,0.0],[32452.83,3,0.0],[32641.51,2,0.0],[32830.1875,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33396.2266,3,0.0],[33490.5664,3,0.0],[33584.9063,2,0.0],[33773.5859,1,0.0],[33962.2656,0,283.01886],[34528.3,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[35094.34,2,283.01886],[35471.7,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[36415.0938,3,94.33962],[36603.7734,2,94.33962],[36792.4531,1,94.33962],[36981.1328,0,283.01886],[37358.4922,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[37735.8477,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[39433.96,3,0.0],[39528.3,3,0.0],[39622.64,2,0.0],[39811.32,1,0.0],[40000.0,0,283.01886],[40566.04,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[41132.0742,2,283.01886],[41509.4336,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[42452.832,3,94.33962],[42641.5078,2,94.33962],[42830.1875,1,94.33962],[43018.8672,0,283.01886],[43396.2266,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43773.5859,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45283.02,0,283.01886],[45660.38,1,283.01886],[46037.7344,2,0.0],[46226.4141,3,0.0],[46415.0938,2,0.0],[46603.7734,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46792.4531,5,283.01886],[47169.8125,0,0.0],[47547.168,3,0.0],[47924.5273,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48301.8867,6,849.0566],[48301.8867,2,283.01886],[48679.2461,0,188.679245],[49056.6055,3,0.0],[49245.28,0,0.0],[49339.62,0,0.0],[49433.96,3,0.0],[49622.64,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49937.1055,2,0.0],[50188.68,2,0.0],[50440.25,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[51320.7539,0,283.01886],[51698.1133,1,283.01886],[52075.4727,2,0.0],[52264.1523,3,0.0],[52452.832,2,0.0],[52641.5078,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52830.1875,5,283.01886],[53207.5469,0,0.0],[53584.9063,3,0.0],[53962.2656,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54339.62,6,1132.07544],[54339.62,2,188.679245],[54716.98,0,188.679245],[55094.34,3,0.0],[55283.02,0,0.0],[55377.36,0,0.0],[55471.7,3,0.0],[55660.38,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56037.7344,2,0.0],[56415.0938,2,0.0],[56603.7734,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57547.168,2,0.0],[57924.5273,2,0.0],[58018.8672,3,0.0],[58113.207,2,0.0],[58301.8867,0,0.0],[58490.5664,2,0.0],[58679.2461,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[58867.9258,1,0.0],[59056.6055,0,0.0],[59245.28,3,283.01886],[59622.64,2,0.0],[59811.32,3,0.0],[60000.0,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60566.04,2,0.0],[60943.3945,2,0.0],[61037.7344,3,0.0],[61132.0742,2,0.0],[61320.7539,0,0.0],[61509.4336,2,0.0],[61698.1133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[61886.793,1,0.0],[62075.4727,0,0.0],[62264.1523,3,283.01886],[62641.5078,2,0.0],[62830.1875,3,0.0],[63018.8672,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[63396.2266,3,0.0],[63584.9063,3,0.0],[63773.5859,1,283.01886],[64150.94,0,0.0],[64528.3,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64905.66,4,0.0],[64905.66,3,0.0],[65094.34,3,0.0],[65283.02,1,283.01886],[65660.375,0,0.0],[65849.0547,0,0.0],[66037.7344,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[66415.09,4,0.0],[66415.09,0,0.0],[66603.77,5,0.0],[66603.77,1,0.0],[66981.13,7,0.0],[66981.13,3,0.0],[67358.49,6,0.0],[67358.49,2,0.0],[67735.85,1,754.717],[67735.85,5,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[69622.64,0,0.0],[69716.9844,0,0.0],[69811.32,3,0.0],[70188.68,1,0.0],[70377.36,0,0.0],[70566.04,2,0.0],[70754.72,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[71132.08,2,0.0],[71226.4141,3,0.0],[71320.76,1,0.0],[71509.44,0,0.0],[71698.11,2,0.0],[71886.79,3,0.0],[72075.47,0,0.0],[72264.15,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72452.83,2,283.01886],[72830.19,0,283.01886],[73207.55,3,283.01886],[73584.91,0,0.0],[73773.5859,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[74528.3047,0,0.0],[74716.9844,3,0.0],[74905.6641,0,0.0],[75094.3359,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75660.375,0,0.0],[75754.72,0,0.0],[75849.0547,3,0.0],[76226.4141,1,0.0],[76415.09,0,0.0],[76603.77,2,0.0],[76792.45,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[77169.81,2,0.0],[77264.15,3,0.0],[77358.49,1,0.0],[77547.17,0,0.0],[77735.85,2,0.0],[77924.53,3,0.0],[78113.21,0,0.0],[78301.88,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78490.56,2,283.01886],[78867.92,0,283.01886],[79245.28,3,283.01886],[79622.64,0,0.0],[79811.32,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80566.04,0,0.0],[80754.72,3,0.0],[80943.4,0,0.0],[81132.08,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[81509.44,1,0.0],[81698.11,1,0.0],[81886.79,3,0.0],[82075.47,3,0.0],[82264.15,0,0.0],[82452.83,3,0.0],[82641.51,2,0.0],[82830.19,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83018.87,1,0.0],[83207.55,1,0.0],[83396.23,3,0.0],[83584.91,3,0.0],[83773.5859,0,0.0],[83962.2656,3,0.0],[84150.9453,2,0.0],[84339.625,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[84528.3047,2,0.0],[84716.9844,3,0.0],[84905.6641,2,0.0],[85094.3359,0,0.0],[85283.0156,2,0.0],[85471.6953,3,0.0],[85660.375,2,0.0],[85849.0547,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[86037.7344,2,0.0],[86226.4141,3,0.0],[86415.09,2,0.0],[86603.77,0,0.0],[86792.45,2,0.0],[86981.13,3,0.0],[87169.81,2,0.0],[87358.49,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[87547.17,3,283.01886],[87924.53,0,94.33962],[88113.21,1,283.01886],[88301.88,4,0.0],[88490.56,0,0.0],[88490.56,7,0.0],[88679.24,1,0.0],[88679.24,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89056.6,5,283.01886],[89056.6,3,0.0],[89433.96,3,0.0],[89811.32,1,283.01886],[89811.32,7,0.0],[90000.0,4,0.0],[90188.68,6,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[90566.04,5,283.01886],[90566.04,0,0.0],[91132.08,3,0.0],[91698.11,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92075.47,6,566.0377],[92075.47,2,566.0377],[92830.19,3,566.0377],[92830.19,4,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[93773.5859,3,0.0],[93867.92,3,0.0],[93962.2656,2,0.0],[94150.9453,1,0.0],[94150.9453,7,0.0],[94245.28,7,0.0],[94339.625,0,283.01886],[94339.625,6,0.0],[94528.3047,5,0.0],[94716.9844,4,283.01886],[94905.6641,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[95283.0156,3,283.01886],[95471.6953,6,283.01886],[95849.0547,2,283.01886],[95849.0547,4,283.01886],[96226.4141,0,283.01886],[96226.4141,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[96603.77,3,0.0],[96792.45,7,94.33962],[96981.13,6,94.33962],[97169.81,5,94.33962],[97169.81,3,94.33962],[97358.49,4,283.01886],[97358.49,2,94.33962],[97547.17,1,94.33962],[97735.85,0,283.01886],[97735.85,5,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98113.21,5,283.01886],[98113.21,0,283.01886],[98490.56,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[99811.32,3,0.0],[99905.6641,3,0.0],[100000.0,2,0.0],[100188.68,7,0.0],[100188.68,1,0.0],[100283.016,7,0.0],[100377.359,6,0.0],[100377.359,0,283.01886],[100566.039,5,0.0],[100754.719,4,283.01886],[100943.4,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[101320.758,3,283.01886],[101509.438,6,283.01886],[101886.789,2,283.01886],[101886.789,4,283.01886],[102264.148,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[102830.188,3,94.33962],[103018.867,2,94.33962],[103207.547,7,94.33962],[103207.547,1,94.33962],[103396.227,0,283.01886],[103396.227,6,94.33962],[103584.906,5,94.33962],[103773.586,1,283.01886],[103773.586,4,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[104150.945,0,283.01886],[104150.945,5,283.01886],[104528.305,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[105849.055,2,0.0],[106226.414,2,0.0],[106320.758,3,0.0],[106415.094,2,0.0],[106603.773,0,0.0],[106792.453,2,0.0],[106981.133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[107169.813,1,0.0],[107358.492,0,0.0],[107547.172,3,283.01886],[107924.531,2,0.0],[108113.211,3,0.0],[108301.883,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[108867.922,2,0.0],[109245.281,2,0.0],[109339.625,3,0.0],[109433.961,2,0.0],[109622.641,0,0.0],[109811.32,2,0.0],[110000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[110188.68,1,0.0],[110377.359,0,0.0],[110566.039,3,283.01886],[110943.4,2,0.0],[111132.078,3,0.0],[111320.758,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[111698.109,3,0.0],[111886.789,3,0.0],[112075.469,1,283.01886],[112452.828,0,0.0],[112830.188,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[113207.547,4,0.0],[113207.547,3,0.0],[113396.227,3,0.0],[113584.906,1,283.01886],[113962.266,0,0.0],[114150.945,0,0.0],[114339.625,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[114716.984,0,0.0],[114716.984,4,0.0],[114905.664,1,0.0],[114905.664,5,0.0],[115283.016,3,0.0],[115283.016,7,0.0],[115660.375,2,0.0],[115660.375,6,0.0],[116037.734,5,754.717],[116037.734,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[117924.531,0,0.0],[118301.883,3,0.0],[118490.563,1,0.0],[118679.242,0,0.0],[118867.922,2,0.0],[119056.6,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[119245.281,2,283.01886],[119622.641,3,283.01886],[120000.0,0,283.01886],[120377.359,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[120754.719,6,754.717],[120943.4,0,0.0],[121320.758,3,0.0],[121509.438,1,0.0],[121698.109,0,0.0],[121886.789,2,0.0],[122075.469,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[122264.148,2,283.01886],[122641.508,3,283.01886],[123018.867,0,283.01886],[123396.227,3,754.717]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[123773.586,1,94.33962],[124150.945,3,0.0],[124339.625,3,0.0],[124528.305,2,283.01886],[124905.664,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[125283.016,5,283.01886],[125283.016,2,0.0],[125471.695,3,0.0],[125660.375,1,283.01886],[126037.734,3,0.0],[126226.414,0,0.0],[126415.094,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[126792.453,0,0.0],[126792.453,4,0.0],[126981.133,1,0.0],[126981.133,5,0.0],[127358.492,3,0.0],[127358.492,7,0.0],[127735.852,2,0.0],[127735.852,6,0.0],[128113.211,1,377.3585],[128113.211,5,377.3585]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129056.6,0,0.0],[129245.281,3,0.0],[129433.961,0,0.0],[129622.641,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129811.32,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} diff --git a/assets/songs/winter horrorland/charts/normal.json b/assets/songs/winter horrorland/charts/normal.json index a00dcf64..b1c4eda2 100644 --- a/assets/songs/winter horrorland/charts/normal.json +++ b/assets/songs/winter horrorland/charts/normal.json @@ -1 +1 @@ -{"song":{"gf":"gf-christmas","stage":"mall-evil","song":"Winter Horrorland","bpm":159.0,"needsVoices":true,"player1":"bf-christmas","player2":"Monster-christmas","speed":1.1,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9433.962,0,283.01886],[9811.32,2,283.01886],[10188.68,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[10566.0381,0,660.3774],[11320.7549,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[12452.83,0,283.01886],[12830.1885,2,283.01886],[13207.5469,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[13584.9053,1,0.0],[14339.623,3,283.01886],[14716.9814,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[15094.34,5,754.717],[15471.6982,0,283.01886],[15849.0566,2,283.01886],[16226.415,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16603.7734,0,660.3774],[17358.49,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[18490.5664,0,283.01886],[18867.9238,2,283.01886],[19245.2832,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[19622.64,1,0.0],[20377.36,3,283.01886],[20754.7168,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21132.0762,1,283.01886],[21132.0762,5,754.717],[21509.4336,3,283.01886],[21886.793,2,283.01886],[22264.15,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22641.51,1,471.69812],[23207.5469,0,0.0],[23396.2266,0,0.0],[23773.584,0,0.0],[23962.2637,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24150.9434,3,283.01886],[24528.3027,2,0.0],[24905.66,2,0.0],[25283.02,2,0.0],[25471.6973,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[25660.377,1,471.69812],[26226.416,2,0.0],[26415.0938,3,0.0],[26603.7734,2,0.0],[26792.4531,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27169.81,1,283.01886],[27547.17,3,283.01886],[27924.5273,2,283.01886],[28301.8867,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28679.2461,1,471.69812],[29245.2832,0,0.0],[29433.9629,0,0.0],[29811.32,0,0.0],[30000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[30188.68,3,283.01886],[30566.0371,2,0.0],[30943.3965,2,0.0],[31320.7539,2,0.0],[31509.4336,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31698.1133,1,471.69812],[32264.15,2,0.0],[32452.83,3,0.0],[32641.51,2,0.0],[32830.1875,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33396.2266,3,0.0],[33584.9063,2,0.0],[33962.2656,0,283.01886],[34528.3,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[35094.34,2,283.01886],[35471.7,0,283.01886],[35849.0547,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[36603.7734,2,0.0],[36981.1328,0,283.01886],[37358.4922,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[37735.8477,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[39433.96,3,0.0],[39622.64,2,0.0],[40000.0,0,283.01886],[40566.04,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[41132.0742,2,283.01886],[41509.4336,0,283.01886],[41886.793,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[42641.5078,2,0.0],[43018.8672,0,283.01886],[43396.2266,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43773.5859,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45283.02,0,283.01886],[45660.38,1,283.01886],[46037.7344,2,0.0],[46415.0938,2,0.0],[46603.7734,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46792.4531,5,283.01886],[47169.8125,0,0.0],[47547.168,3,0.0],[47924.5273,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48301.8867,2,283.01886],[48301.8867,6,849.0566],[48679.2461,0,188.679245],[49056.6055,3,0.0],[49245.28,0,0.0],[49433.96,3,0.0],[49622.64,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49937.1055,2,0.0],[50188.68,2,0.0],[50440.25,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[51320.7539,0,283.01886],[51698.1133,1,283.01886],[52075.4727,2,0.0],[52452.832,2,0.0],[52641.5078,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52830.1875,5,283.01886],[53207.5469,0,0.0],[53584.9063,3,0.0],[53962.2656,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54339.62,6,1132.07544],[54339.62,2,188.679245],[54716.98,0,188.679245],[55094.34,3,0.0],[55283.02,0,0.0],[55471.7,3,0.0],[55660.38,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56037.7344,2,0.0],[56415.0938,2,0.0],[56603.7734,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57547.168,2,0.0],[57924.5273,2,0.0],[58113.207,2,0.0],[58490.5664,2,0.0],[58679.2461,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[58867.9258,1,0.0],[59245.28,3,283.01886],[59622.64,2,0.0],[60000.0,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60566.04,2,0.0],[60943.3945,2,0.0],[61132.0742,2,0.0],[61509.4336,2,0.0],[61698.1133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[61886.793,1,0.0],[62264.1523,3,283.01886],[62641.5078,2,0.0],[63018.8672,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[63396.2266,3,0.0],[63773.5859,1,283.01886],[64150.94,0,0.0],[64528.3,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64905.66,4,0.0],[64905.66,3,0.0],[65283.02,1,283.01886],[65660.375,0,0.0],[66037.7344,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[66415.09,4,0.0],[66415.09,0,0.0],[66981.13,7,0.0],[66981.13,3,0.0],[67358.49,6,0.0],[67358.49,2,0.0],[67735.85,5,754.717],[67735.85,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[69622.64,0,0.0],[69811.32,3,0.0],[70188.68,1,0.0],[70566.04,2,0.0],[70754.72,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[71132.08,2,0.0],[71320.76,1,0.0],[71698.11,2,0.0],[72075.47,0,0.0],[72264.15,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72452.83,2,283.01886],[72830.19,0,283.01886],[73207.55,3,283.01886],[73584.91,0,0.0],[73773.5859,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[74528.3047,0,0.0],[74716.9844,3,0.0],[74905.6641,0,0.0],[75094.3359,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75660.375,0,0.0],[75849.0547,3,0.0],[76226.4141,1,0.0],[76603.77,2,0.0],[76792.45,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[77169.81,2,0.0],[77358.49,1,0.0],[77735.85,2,0.0],[78113.21,0,0.0],[78301.88,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78490.56,2,283.01886],[78867.92,0,283.01886],[79245.28,3,283.01886],[79622.64,0,0.0],[79811.32,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80566.04,0,0.0],[80754.72,3,0.0],[80943.4,0,0.0],[81132.08,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[81509.44,1,0.0],[81886.79,3,0.0],[82264.15,0,0.0],[82641.51,2,0.0],[82830.19,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83018.87,1,0.0],[83396.23,3,0.0],[83773.5859,0,0.0],[84150.9453,2,0.0],[84339.625,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[84528.3047,2,0.0],[84905.6641,2,0.0],[85094.3359,0,0.0],[85283.0156,2,0.0],[85660.375,2,0.0],[85849.0547,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[86037.7344,2,0.0],[86415.09,2,0.0],[86603.77,0,0.0],[86792.45,2,0.0],[87169.81,2,0.0],[87358.49,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[87547.17,3,283.01886],[87924.53,0,94.33962],[88113.21,1,283.01886],[88301.88,4,0.0],[88679.24,1,0.0],[88679.24,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89056.6,5,283.01886],[89056.6,3,0.0],[89433.96,3,0.0],[89811.32,1,283.01886],[89811.32,7,0.0],[90188.68,6,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[90566.04,5,283.01886],[90566.04,0,0.0],[91132.08,3,0.0],[91698.11,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92075.47,2,566.0377],[92075.47,6,566.0377],[92830.19,3,566.0377],[92830.19,4,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[93773.5859,3,0.0],[93962.2656,2,0.0],[94150.9453,7,0.0],[94339.625,6,0.0],[94339.625,0,283.01886],[94716.9844,4,283.01886],[94905.6641,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[95283.0156,3,283.01886],[95471.6953,6,283.01886],[95849.0547,2,283.01886],[95849.0547,4,283.01886],[96226.4141,0,283.01886],[96226.4141,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[96603.77,3,0.0],[96981.13,6,0.0],[97358.49,2,0.0],[97358.49,4,283.01886],[97735.85,0,283.01886],[97735.85,5,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98113.21,5,283.01886],[98113.21,0,283.01886],[98490.56,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[99811.32,3,0.0],[100000.0,2,0.0],[100188.68,7,0.0],[100377.359,0,283.01886],[100377.359,6,0.0],[100754.719,4,283.01886],[100943.4,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[101320.758,3,283.01886],[101509.438,6,283.01886],[101886.789,2,283.01886],[101886.789,4,283.01886],[102264.148,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[102830.188,3,94.33962],[103018.867,2,94.33962],[103207.547,7,94.33962],[103396.227,0,283.01886],[103396.227,6,94.33962],[103773.586,1,283.01886],[103773.586,4,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[104150.945,0,283.01886],[104150.945,5,283.01886],[104528.305,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[105849.055,2,0.0],[106226.414,2,0.0],[106415.094,2,0.0],[106792.453,2,0.0],[106981.133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[107169.813,1,0.0],[107547.172,3,283.01886],[107924.531,2,0.0],[108301.883,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[108867.922,2,0.0],[109245.281,2,0.0],[109433.961,2,0.0],[109811.32,2,0.0],[110000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[110188.68,1,0.0],[110566.039,3,283.01886],[110943.4,2,0.0],[111320.758,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[111698.109,3,0.0],[112075.469,1,283.01886],[112452.828,0,0.0],[112830.188,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[113207.547,3,0.0],[113207.547,4,0.0],[113584.906,1,283.01886],[113962.266,0,0.0],[114339.625,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[114716.984,4,0.0],[114716.984,0,0.0],[115283.016,7,0.0],[115283.016,3,0.0],[115660.375,6,0.0],[115660.375,2,0.0],[116037.734,5,754.717],[116037.734,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[117924.531,0,0.0],[118490.563,1,0.0],[118867.922,2,0.0],[119056.6,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[119245.281,2,283.01886],[119622.641,3,283.01886],[120000.0,0,283.01886],[120377.359,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[120754.719,6,754.717],[120943.4,0,0.0],[121509.438,1,0.0],[121886.789,2,0.0],[122075.469,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[122264.148,2,283.01886],[122641.508,3,283.01886],[123018.867,0,283.01886],[123396.227,3,754.717]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[123773.586,1,94.33962],[124150.945,3,0.0],[124528.305,2,283.01886],[124905.664,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[125283.016,5,283.01886],[125283.016,2,0.0],[125660.375,1,283.01886],[126037.734,3,0.0],[126226.414,0,0.0],[126415.094,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[126792.453,0,0.0],[126792.453,4,0.0],[127358.492,3,0.0],[127358.492,7,0.0],[127735.852,2,0.0],[127735.852,6,0.0],[128113.211,1,377.3585],[128113.211,5,377.3585]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129056.6,0,0.0],[129245.281,3,0.0],[129433.961,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129811.32,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} \ No newline at end of file +{"song":{"gf":"gf-christmas","stage":"mall-evil","song":"Winter Horrorland","bpm":159.0,"needsVoices":true,"player1":"bf-christmas","player2":"monster-christmas","speed":1.1,"notes":[{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[9433.962,0,283.01886],[9811.32,2,283.01886],[10188.68,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[10566.0381,0,660.3774],[11320.7549,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[12452.83,0,283.01886],[12830.1885,2,283.01886],[13207.5469,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[13584.9053,1,0.0],[14339.623,3,283.01886],[14716.9814,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[15094.34,5,754.717],[15471.6982,0,283.01886],[15849.0566,2,283.01886],[16226.415,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[16603.7734,0,660.3774],[17358.49,2,660.3774]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[18490.5664,0,283.01886],[18867.9238,2,283.01886],[19245.2832,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[19622.64,1,0.0],[20377.36,3,283.01886],[20754.7168,2,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[21132.0762,1,283.01886],[21132.0762,5,754.717],[21509.4336,3,283.01886],[21886.793,2,283.01886],[22264.15,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[22641.51,1,471.69812],[23207.5469,0,0.0],[23396.2266,0,0.0],[23773.584,0,0.0],[23962.2637,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[24150.9434,3,283.01886],[24528.3027,2,0.0],[24905.66,2,0.0],[25283.02,2,0.0],[25471.6973,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[25660.377,1,471.69812],[26226.416,2,0.0],[26415.0938,3,0.0],[26603.7734,2,0.0],[26792.4531,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[27169.81,1,283.01886],[27547.17,3,283.01886],[27924.5273,2,283.01886],[28301.8867,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[28679.2461,1,471.69812],[29245.2832,0,0.0],[29433.9629,0,0.0],[29811.32,0,0.0],[30000.0,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[30188.68,3,283.01886],[30566.0371,2,0.0],[30943.3965,2,0.0],[31320.7539,2,0.0],[31509.4336,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[31698.1133,1,471.69812],[32264.15,2,0.0],[32452.83,3,0.0],[32641.51,2,0.0],[32830.1875,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[33396.2266,3,0.0],[33584.9063,2,0.0],[33962.2656,0,283.01886],[34528.3,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[35094.34,2,283.01886],[35471.7,0,283.01886],[35849.0547,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[36603.7734,2,0.0],[36981.1328,0,283.01886],[37358.4922,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[37735.8477,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[39433.96,3,0.0],[39622.64,2,0.0],[40000.0,0,283.01886],[40566.04,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[41132.0742,2,283.01886],[41509.4336,0,283.01886],[41886.793,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[42641.5078,2,0.0],[43018.8672,0,283.01886],[43396.2266,1,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[43773.5859,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[45283.02,0,283.01886],[45660.38,1,283.01886],[46037.7344,2,0.0],[46415.0938,2,0.0],[46603.7734,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[46792.4531,5,283.01886],[47169.8125,0,0.0],[47547.168,3,0.0],[47924.5273,1,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[48301.8867,2,283.01886],[48301.8867,6,849.0566],[48679.2461,0,188.679245],[49056.6055,3,0.0],[49245.28,0,0.0],[49433.96,3,0.0],[49622.64,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[49937.1055,2,0.0],[50188.68,2,0.0],[50440.25,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[51320.7539,0,283.01886],[51698.1133,1,283.01886],[52075.4727,2,0.0],[52452.832,2,0.0],[52641.5078,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[52830.1875,5,283.01886],[53207.5469,0,0.0],[53584.9063,3,0.0],[53962.2656,1,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[54339.62,6,1132.07544],[54339.62,2,188.679245],[54716.98,0,188.679245],[55094.34,3,0.0],[55283.02,0,0.0],[55471.7,3,0.0],[55660.38,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[56037.7344,2,0.0],[56415.0938,2,0.0],[56603.7734,2,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[57547.168,2,0.0],[57924.5273,2,0.0],[58113.207,2,0.0],[58490.5664,2,0.0],[58679.2461,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[58867.9258,1,0.0],[59245.28,3,283.01886],[59622.64,2,0.0],[60000.0,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[60566.04,2,0.0],[60943.3945,2,0.0],[61132.0742,2,0.0],[61509.4336,2,0.0],[61698.1133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[61886.793,1,0.0],[62264.1523,3,283.01886],[62641.5078,2,0.0],[63018.8672,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[63396.2266,3,0.0],[63773.5859,1,283.01886],[64150.94,0,0.0],[64528.3,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[64905.66,4,0.0],[64905.66,3,0.0],[65283.02,1,283.01886],[65660.375,0,0.0],[66037.7344,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[66415.09,4,0.0],[66415.09,0,0.0],[66981.13,7,0.0],[66981.13,3,0.0],[67358.49,6,0.0],[67358.49,2,0.0],[67735.85,5,754.717],[67735.85,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[69622.64,0,0.0],[69811.32,3,0.0],[70188.68,1,0.0],[70566.04,2,0.0],[70754.72,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[71132.08,2,0.0],[71320.76,1,0.0],[71698.11,2,0.0],[72075.47,0,0.0],[72264.15,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[72452.83,2,283.01886],[72830.19,0,283.01886],[73207.55,3,283.01886],[73584.91,0,0.0],[73773.5859,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[74528.3047,0,0.0],[74716.9844,3,0.0],[74905.6641,0,0.0],[75094.3359,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[75660.375,0,0.0],[75849.0547,3,0.0],[76226.4141,1,0.0],[76603.77,2,0.0],[76792.45,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[77169.81,2,0.0],[77358.49,1,0.0],[77735.85,2,0.0],[78113.21,0,0.0],[78301.88,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[78490.56,2,283.01886],[78867.92,0,283.01886],[79245.28,3,283.01886],[79622.64,0,0.0],[79811.32,1,566.0377]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[80566.04,0,0.0],[80754.72,3,0.0],[80943.4,0,0.0],[81132.08,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[81509.44,1,0.0],[81886.79,3,0.0],[82264.15,0,0.0],[82641.51,2,0.0],[82830.19,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[83018.87,1,0.0],[83396.23,3,0.0],[83773.5859,0,0.0],[84150.9453,2,0.0],[84339.625,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[84528.3047,2,0.0],[84905.6641,2,0.0],[85094.3359,0,0.0],[85283.0156,2,0.0],[85660.375,2,0.0],[85849.0547,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[86037.7344,2,0.0],[86415.09,2,0.0],[86603.77,0,0.0],[86792.45,2,0.0],[87169.81,2,0.0],[87358.49,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[87547.17,3,283.01886],[87924.53,0,94.33962],[88113.21,1,283.01886],[88301.88,4,0.0],[88679.24,1,0.0],[88679.24,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[89056.6,5,283.01886],[89056.6,3,0.0],[89433.96,3,0.0],[89811.32,1,283.01886],[89811.32,7,0.0],[90188.68,6,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[90566.04,5,283.01886],[90566.04,0,0.0],[91132.08,3,0.0],[91698.11,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[92075.47,2,566.0377],[92075.47,6,566.0377],[92830.19,3,566.0377],[92830.19,4,566.0377]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[93773.5859,3,0.0],[93962.2656,2,0.0],[94150.9453,7,0.0],[94339.625,6,0.0],[94339.625,0,283.01886],[94716.9844,4,283.01886],[94905.6641,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[95283.0156,3,283.01886],[95471.6953,6,283.01886],[95849.0547,2,283.01886],[95849.0547,4,283.01886],[96226.4141,0,283.01886],[96226.4141,7,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[96603.77,3,0.0],[96981.13,6,0.0],[97358.49,2,0.0],[97358.49,4,283.01886],[97735.85,0,283.01886],[97735.85,5,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[98113.21,5,283.01886],[98113.21,0,283.01886],[98490.56,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[99811.32,3,0.0],[100000.0,2,0.0],[100188.68,7,0.0],[100377.359,0,283.01886],[100377.359,6,0.0],[100754.719,4,283.01886],[100943.4,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[101320.758,3,283.01886],[101509.438,6,283.01886],[101886.789,2,283.01886],[101886.789,4,283.01886],[102264.148,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[102830.188,3,94.33962],[103018.867,2,94.33962],[103207.547,7,94.33962],[103396.227,0,283.01886],[103396.227,6,94.33962],[103773.586,1,283.01886],[103773.586,4,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[104150.945,0,283.01886],[104150.945,5,283.01886],[104528.305,7,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[105849.055,2,0.0],[106226.414,2,0.0],[106415.094,2,0.0],[106792.453,2,0.0],[106981.133,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[107169.813,1,0.0],[107547.172,3,283.01886],[107924.531,2,0.0],[108301.883,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[108867.922,2,0.0],[109245.281,2,0.0],[109433.961,2,0.0],[109811.32,2,0.0],[110000.0,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[110188.68,1,0.0],[110566.039,3,283.01886],[110943.4,2,0.0],[111320.758,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[111698.109,3,0.0],[112075.469,1,283.01886],[112452.828,0,0.0],[112830.188,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[113207.547,3,0.0],[113207.547,4,0.0],[113584.906,1,283.01886],[113962.266,0,0.0],[114339.625,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[114716.984,4,0.0],[114716.984,0,0.0],[115283.016,7,0.0],[115283.016,3,0.0],[115660.375,6,0.0],[115660.375,2,0.0],[116037.734,5,754.717],[116037.734,1,754.717]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[117924.531,0,0.0],[118490.563,1,0.0],[118867.922,2,0.0],[119056.6,3,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[119245.281,2,283.01886],[119622.641,3,283.01886],[120000.0,0,283.01886],[120377.359,3,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[120754.719,6,754.717],[120943.4,0,0.0],[121509.438,1,0.0],[121886.789,2,0.0],[122075.469,3,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[122264.148,2,283.01886],[122641.508,3,283.01886],[123018.867,0,283.01886],[123396.227,3,754.717]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[123773.586,1,94.33962],[124150.945,3,0.0],[124528.305,2,283.01886],[124905.664,0,283.01886]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[125283.016,5,283.01886],[125283.016,2,0.0],[125660.375,1,283.01886],[126037.734,3,0.0],[126226.414,0,0.0],[126415.094,0,0.0]]},{"lengthInSteps":16,"mustHitSection":false,"sectionNotes":[[126792.453,0,0.0],[126792.453,4,0.0],[127358.492,3,0.0],[127358.492,7,0.0],[127735.852,2,0.0],[127735.852,6,0.0],[128113.211,1,377.3585],[128113.211,5,377.3585]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129056.6,0,0.0],[129245.281,3,0.0],[129433.961,0,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[[129811.32,2,0.0]]},{"lengthInSteps":16,"mustHitSection":true,"sectionNotes":[]}]},"generatedBy":"SNIFF ver.6"} diff --git a/assets/sounds/sfxTodo.md b/assets/sounds/sfxTodo.md new file mode 100644 index 00000000..74fdf30e --- /dev/null +++ b/assets/sounds/sfxTodo.md @@ -0,0 +1,54 @@ +List of SFX to-do for editors, +maybe some other menus will need them in the future, +but for now just editors! + +Putting this txt in here so others can edit and commit this to suggest or add missing sfx! +- Cass (GlitchDotSmh) + +[] = Not done +[!] = Priority/In progress +[x] = Done + +# Editor SFX + +## Global: +- Opening/Closing Context/Dropdown Menus [] +- Opening/Closing Pop-up Windows [] +- Mouse Click [x] +- UI Button being clicked [] +- Focusing/Unfocusing a text field [] +- Typing in a text field [] +- Saving [] +- Auto-Saving [] +- Copying [] +- Pasting [] +- Undoing [] +- Redoing [] +- Deleting [] + +## Chart Editor: +- Placing Notes [] +- Deleting Notes [] +- Subtracting Sustain [] +- Adding Sustain [] +- Picking Up Notes [] +- Moving Notes [] +- Dropping Notes [] +- Selecting Notes (via `CTRL + Drag` or `CTRL + A`) [] +- Hitsounds [] +- Metronome [] +- Creating an Event [] +- Pulling up Event Editing menu [] +- Scrolling Up/Down (via `Scroll Wheel` or `W/S`) [] +- Note Snapping Change [] +- Locking Strums [] +- Unlocking Strums [] +- Creating Strum [] + +## Character/Stage Editor +- Selecting a Stage asset [] +- Switching Stage [] +- Changing Offset (via `Arrows`, `WASD`, or `Mouse Drag`) [] +- Enabling/Disabling Ghost Animation [] + + diff --git a/commandline/ArgParser.hx b/commandline/ArgParser.hx index 61ff15b8..94ddcaac 100644 --- a/commandline/ArgParser.hx +++ b/commandline/ArgParser.hx @@ -17,18 +17,18 @@ class ArgParser { this.args = args; this.options = new Map(); - var copy = args.copy(); + final copy = args.copy(); var i = 0; while(copy.length > 0) { var arg = copy.shift(); // this parses the -NAME=VALUE if (arg.startsWith("-")) { var key = arg.substr(1); - var isLongKey = key.startsWith("-"); + final isLongKey = key.startsWith("-"); if (isLongKey) { key = key.substr(1); } - var split = key.split("="); + final split = key.split("="); var longName = split.shift(); if(!isLongKey) { // Allow -ABC to be parsed as -A -B -C @@ -43,8 +43,8 @@ class ArgParser { // Parse the last option with value support } - var name = rename(longName); - var value = (split.length > 0) ? split.join("=") : null; + final name = rename(longName); + final value = (split.length > 0) ? split.join("=") : null; options.set(name, value); args.splice(i, 1); diff --git a/commandline/Main.hx b/commandline/Main.hx index fc0c7333..beb170f0 100644 --- a/commandline/Main.hx +++ b/commandline/Main.hx @@ -49,7 +49,8 @@ class Main { "If you want a full build which contains all assets, run \"cne release\" or \"cne test-release\"", "Additional arguments will be sent to the lime compiler.", "", - "-debug : Builds a debug build." + "-debug : Builds a debug build.", + "-clean : Compiled files will be deleted before compiling." ].join("\n") }, { @@ -67,7 +68,20 @@ class Main { "If you want a full build which contains all assets, run \"cne release\" or \"cne test-release\"", "Additional arguments will be sent to the lime compiler.", "", - "-debug : Builds a debug build." + "-debug : Builds a debug build.", + "-clean : Compiled files will be deleted before compiling." + ].join("\n") + }, + { + names: ["run"], + doc: "Runs the last build that was created.", + func: Compiler.run, + dDoc: [ + "Usage: run <optional arguments>", + "", + "This will run the last build that was created.", + "If the last build was a debug build, you need to pass the -debug argument to run it.", + "Additional arguments will be sent to the lime compiler." ].join("\n") }, { @@ -79,7 +93,9 @@ class Main { "", "This will create and run a final ready-for-release build,", "which means this build will be able to be release on websites such as GameBanana without worrying about source-dependant stuff.", - "Additional arguments will be sent to the lime compiler." + "Additional arguments will be sent to the lime compiler.", + "", + "-clean : Compiled files will be deleted before compiling." ].join("\n") }, { @@ -91,7 +107,9 @@ class Main { "", "This will create and run a final ready-for-release build,", "which means this build will be able to be release on websites such as GameBanana without worrying about source-dependant stuff.", - "Additional arguments will be sent to the lime compiler." + "Additional arguments will be sent to the lime compiler.", + "", + "-clean : Compiled files will be deleted before compiling." ].join("\n") }, { @@ -113,7 +131,7 @@ class Main { public static function main() { initCommands(); - var args = Sys.args(); + final args = Sys.args(); var commandName = args.shift(); if (commandName != null) commandName = commandName.toLowerCase(); diff --git a/commandline/Utils.hx b/commandline/Utils.hx index 20216822..3cc94a40 100644 --- a/commandline/Utils.hx +++ b/commandline/Utils.hx @@ -17,11 +17,11 @@ class Utils { condition = condition.replace('&&', '&').replace('||', '|').replace(" ", ""); - var stack:Array<Dynamic> = []; + final stack:Array<Dynamic> = []; var i = 0; while (i < condition.length) { - var char = condition.charAt(i); + final char = condition.charAt(i); switch (char) { case '(': @@ -32,7 +32,7 @@ class Utils { if (condition.charAt(j) == '(') balance++; if (condition.charAt(j) == ')') balance--; } - var subExpr = condition.substr(i + 1, j - i - 1); + final subExpr = condition.substr(i + 1, j - i - 1); stack.push(evaluateArgsCondition(subExpr, args)); i = j; case '&', '|': @@ -55,8 +55,8 @@ class Utils { public static function evaluate(stack:Array<Dynamic>):Bool { var result = stack.shift(); while (stack.length > 0) { - var op = stack.shift(); - var next = stack.shift(); + final op = stack.shift(); + final next = stack.shift(); switch(op) { case '&': result = result && next; diff --git a/commandline/commands/Compiler.hx b/commandline/commands/Compiler.hx index 5f573328..671c7ffa 100644 --- a/commandline/commands/Compiler.hx +++ b/commandline/commands/Compiler.hx @@ -2,19 +2,22 @@ package commands; class Compiler { public static function test(args:Array<String>) { - __build(args, ["test", getBuildTarget(), "-DTEST_BUILD"]); + __runLime(args, ["test", getBuildTarget(), "-DTEST_BUILD"]); } public static function build(args:Array<String>) { - __build(args, ["build", getBuildTarget(), "-DTEST_BUILD"]); + __runLime(args, ["build", getBuildTarget(), "-DTEST_BUILD"]); } public static function release(args:Array<String>) { - __build(args, ["build", getBuildTarget()]); + __runLime(args, ["build", getBuildTarget()]); } public static function testRelease(args:Array<String>) { - __build(args, ["test", getBuildTarget()]); + __runLime(args, ["test", getBuildTarget()]); + } + public static function run(args:Array<String>) { + __runLime(args, ["run", getBuildTarget()]); } - private static function __build(args:Array<String>, arg:Array<String>) { + private static function __runLime(args:Array<String>, arg:Array<String>) { arg.insert(0, "lime"); arg.insert(0, "run"); for(a in args) diff --git a/commandline/commands/Optimizer.hx b/commandline/commands/Optimizer.hx index b82d9182..a30d2949 100644 --- a/commandline/commands/Optimizer.hx +++ b/commandline/commands/Optimizer.hx @@ -4,17 +4,17 @@ import sys.io.File; class Optimizer { public static function main(args:Array<String>) { - var args = ArgParser.parse(args); - var saveOld = args.existsOption("no-old"); + final args = ArgParser.parse(args); + final saveOld = !args.existsOption("no-old"); if(args.length == 0) { Sys.println(Main.curCommand.dDoc); return; } - var filename = args.get(0); + final filename = args.get(0); - var data = File.getContent(filename); + final data = File.getContent(filename); var json = null; try { json = haxe.Json.parse(data); diff --git a/commandline/commands/Setup.hx b/commandline/commands/Setup.hx index 42711fb4..7d4255f6 100644 --- a/commandline/commands/Setup.hx +++ b/commandline/commands/Setup.hx @@ -44,6 +44,7 @@ class Setup { var REINSTALL_ALL = args.existsOption("reinstall"); var SILENT = args.existsOption("silent-progress"); var FAST = args.existsOption("fast"); + // TODO: add only install missing libs // to prevent messing with currently installed libs if (!FileSystem.exists('.haxelib')) @@ -67,13 +68,13 @@ class Setup { return; } - var events:Array<Event> = []; - var libsXML:Access = new Access(Xml.parse(File.getContent(libFile)).firstElement()); + final events:Array<Event> = []; + final libsXML:Access = new Access(Xml.parse(File.getContent(libFile)).firstElement()); function handleLib(libNode:Access) { switch(libNode.name) { case "lib" | "git" | "custom": - var lib:Library = { + final lib:Library = { name: libNode.att.name, type: libNode.name, skipDeps: libNode.has.skipDeps ? libNode.att.skipDeps == "true" : false, @@ -122,15 +123,16 @@ class Setup { } } - var platform = switch(Sys.systemName()) { + final platform = switch(Sys.systemName()) { case "Windows": "windows"; case "Mac": "mac"; case "Linux": "linux"; case def: def.toLowerCase(); } - var defines = args.args.copy(); + final defines = args.args.copy(); defines.push(platform); + defines.push("true"); if(args.args.length == 0) defines.push("all"); function parse(libNode:Access) { @@ -148,6 +150,8 @@ class Setup { } } + final shouldCheckHaxeVersion = !libsXML.has.checkVersion || Utils.evaluateArgsCondition(libsXML.att.checkVersion, defines); + for (libNode in libsXML.elements) { parse(libNode); } @@ -201,10 +205,10 @@ class Setup { } var oldCwd = Sys.getCwd(); if(lib != null) { - var libPrefix = '.haxelib/$lib'; + final libPrefix = '.haxelib/$lib'; if(FileSystem.exists(libPrefix)) { if(FileSystem.exists(libPrefix + '/.dev')) { // haxelib dev - var devPath = File.getContent(libPrefix + '/.dev'); + final devPath = File.getContent(libPrefix + '/.dev'); if(!FileSystem.exists(devPath)) { Sys.println('Cannot find dev path $devPath for $lib'); Sys.setCwd(oldCwd); @@ -212,7 +216,7 @@ class Setup { } Sys.setCwd(devPath + dir); } else if(FileSystem.exists(libPrefix + '/.current')) { - var version = StringTools.replace(File.getContent(libPrefix + '/.current'), ".", ","); + final version = StringTools.replace(File.getContent(libPrefix + '/.current'), ".", ","); if(!FileSystem.exists(libPrefix + '/$version')) { Sys.println('Cannot find version $version of $lib'); Sys.setCwd(oldCwd); @@ -227,13 +231,13 @@ class Setup { } } for(line in cmd.lines) { - var line = StringTools.replace(line, "$PLATFORM", platform); + final line = StringTools.replace(line, "$PLATFORM", platform); //Sys.println(line); command(line); } Sys.setCwd(oldCwd); case PRINT: - var data:PrintData = event.data; + final data:PrintData = event.data; if(data.pretty) prettyPrint(data.text); else @@ -241,30 +245,28 @@ class Setup { } } - /*var proc = new Process('haxe --version'); - proc.exitCode(true); - var haxeVer = proc.stdout.readLine(); - if (haxeVer != "4.2.5") { + if(shouldCheckHaxeVersion) { + final proc = new Process('haxe --version'); + proc.exitCode(true); + final haxeVer = proc.stdout.readLine(); + // check for outdated haxe - var curHaxeVer = [for(v in haxeVer.split(".")) Std.parseInt(v)]; - var requiredHaxeVer = [4, 2, 5]; - for(i in 0...requiredHaxeVer.length) { - if (curHaxeVer[i] < requiredHaxeVer[i]) { - prettyPrint("!! WARNING !!"); - Sys.println("Your current Haxe version is outdated."); - Sys.println('You\'re using ${haxeVer}, while the required version is 4.2.5.'); - Sys.println('The engine may not compile with your current version of Haxe.'); - Sys.println('We recommend upgrading to 4.2.5'); - break; - } else if (curHaxeVer[i] > requiredHaxeVer[i]) { - prettyPrint("!! WARNING !!" - + "\nUsing Haxe 4.3.0 and above is currently not recommended due to lack of testing."); - Sys.println(''); - Sys.println('We recommend downgrading back to 4.2.5.'); + final curHaxeVer = [for(v in haxeVer.split(".")) Std.parseInt(v)]; + final minumumVersion = [4, 2, 5]; + for(i in 0...minumumVersion.length) { + if (curHaxeVer[i] > minumumVersion[i]) break; + if (curHaxeVer[i] < minumumVersion[i]) { + prettyPrint([ + "!! WARNING !!", + "Your current Haxe version is outdated.", + 'You\'re using ${haxeVer}, whilst the required version is 4.2.5 or newer.', + 'The engine may not compile with your current version of Haxe.', + 'We recommend upgrading to 4.2.5 or newer' + ].join("\n")); break; } } - }*/ + } // vswhere.exe its used to find any visual studio related installations on the system, including full visual studio ide installations, visual studio build tools installations, and other related components - Nex if (CHECK_VSTUDIO && Compiler.getBuildTarget().toLowerCase() == "windows" && new Process('"C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" -property catalog_productDisplayVersion').exitCode(true) == 1) { @@ -282,7 +284,7 @@ class Setup { } public static function prettyPrint(text:String) { - var lines = text.split("\n"); + final lines = text.split("\n"); var length = -1; for(line in lines) if(line.length > length) @@ -300,9 +302,9 @@ class Setup { public static function centerText(text:String, width:Int):String { - var centerOffset = (width - text.length) / 2; - var left = repeat(' ', Math.floor(centerOffset)); - var right = repeat(' ', Math.ceil(centerOffset)); + final centerOffset = (width - text.length) / 2; + final left = repeat(' ', Math.floor(centerOffset)); + final right = repeat(' ', Math.ceil(centerOffset)); return left + text + right; } diff --git a/libs.xml b/libs.xml index c7285668..76995203 100644 --- a/libs.xml +++ b/libs.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<libraries> +<libraries checkVersion="update || all"> <if cond="update || all"> <print pretty="true">Preparing installation...</print> @@ -16,6 +16,7 @@ <git name="hscript-improved" url="https://github.com/CodenameCrew/hscript-improved" ref="codename-dev" /> <git name="flxanimate" url="https://github.com/CodenameCrew/cne-flxanimate" /> <git name="hxdiscord_rpc" url="https://github.com/CodenameCrew/cne-hxdiscord_rpc" skipDeps="true" /> + <git name="funkin-modchart" url="https://github.com/CodenameCrew/FunkinModchart" skipDeps="true" /> <lib name="hxvlc" version="1.9.3" skipDeps="true" /> <!-- Documentation and other features --> @@ -24,6 +25,8 @@ <git name="grig.audio" url="https://gitlab.com/haxe-grig/grig.audio.git" ref="57f5d47f2533fd0c3dcd025a86cb86c0dfa0b6d2" /> <!-- <lib name="dox" /> --> <lib name="markdown" /> + <lib name="hxcpp-debug-server" /> + <lib name="nape-haxe4" /> <custom name="macro-fixer" /> diff --git a/project.xml b/project.xml index 942779db..618c397a 100644 --- a/project.xml +++ b/project.xml @@ -5,7 +5,7 @@ title="Friday Night Funkin' - Codename Engine" main="funkin.backend.system.Main" file="CodenameEngine" packageName="com.yoshman29.codenameengine" package="com.yoshman29.codenameengine" - version="0.1.0" company="Yoshman29" /> + version="0.1.0-legacy" company="Yoshman29" /> <!-- CHANGE THE SAVE PATH & NAME FOR YOUR MOD HERE! @@ -28,7 +28,7 @@ <!-- ____________________________ Window Settings ___________________________ --> <!--These window settings apply to all targets--> - <window width="1280" height="720" fps="" background="#000000" hardware="true" vsync="false" /> + <window width="1280" height="720" background="#000000" hardware="true" vsync="false" /> <!--HTML5-specific--> <window if="html5" resizable="true" /> @@ -62,10 +62,11 @@ <section unless="TEST_BUILD"> <assets path="assets/" rename="assets" exclude="*.ogg" if="web"/> <assets path="assets/" rename="assets" exclude="*.mp3" unless="web"/> + + <!-- TODO: make it copy this manually, instead of using assets node, so it doesn't manually check for fonts --> + <assets path='mods' rename='mods' embed='false'/> </section> - <!-- TODO: make it copy this manually, instead of using assets node, so it doesn't manually check for fonts --> - <assets path='mods' rename='mods' embed='false'/> <assets path='art/readme.txt' rename='do NOT readme.txt' /> <!-- OpenAL config --> @@ -108,19 +109,25 @@ <!-- Comment this out to disable Discord RPC !--> <section if="cpp"> - <define name="DISCORD_RPC" if="desktop"/> + <!-- <define name="DISCORD_RPC" if="desktop"/> --> </section> + <!-- Uncomment this to achive faster compile times, will strip extraneous and commonly unused features. --> + <define name="STRIPPED_COMPILE" unless="debug"/> + + <!-- Comment this out to disable modcharting features --> + <define name="MODCHARTING_FEATURES" unless="STRIPPED_COMPILE" /> + <!-- Comment this out to disable GitHub API integration !--> <define name="GITHUB_API" unless="web || hl"/> <!-- Comment this out to prevent unused classes from being compiled. Reduces compilation time at the cost of limited HScript flexibility. !--> - <define name="COMPILE_ALL_CLASSES" /> + <define name="COMPILE_ALL_CLASSES" unless="STRIPPED_COMPILE" /> <!-- Comment this out to prevent extending classes in hscript. Reduces compilation time at the cost of limited HScript flexibility. !--> - <define name="CUSTOM_CLASSES" /> + <define name="CUSTOM_CLASSES" unless="STRIPPED_COMPILE" /> <!-- Comment this out to disable multithreading !--> <haxedef name="ALLOW_MULTITHREADING" unless="web || flash" /> @@ -144,13 +151,13 @@ <haxedef name="NDLLS_SUPPORTED" unless="web || iphonesim || hl" /> <!-- Comment this out to disable support for Away3D Flixel Integration (reduces compile times) !--> - <define name="THREE_D_SUPPORT" /> + <define name="THREE_D_SUPPORT" unless="STRIPPED_COMPILE" /> <!-- Comment this out to disable support for Funkin Audio Visualizer (reduces compile times) !--> - <define name="AUDIO_VIS_SUPPORT" /> + <define name="AUDIO_VIS_SUPPORT" unless="STRIPPED_COMPILE" /> <!-- Comment this out to disable support for Nape integration (flixel.addons.nape) (reduces compile times) !--> - <define name="NAPE_ENABLED" /> + <define name="NAPE_ENABLED" unless="STRIPPED_COMPILE" /> <!-- Comment this out to disable video cutscene support, in case of compilation errors. !--> <section if="cpp"> @@ -175,8 +182,6 @@ <haxedef name="SOFTCODED_CLASSES" if="SOFTCODED_CLASSES" /> <haxedef name="openfl_dpi_aware" if="openfl_dpi_aware" /> - <haxedef name="EXPERIMENTAL_FLXGRAPHIC_DESTROY_FIX" /> - <!-- _______________________________ Libraries ______________________________ --> <!-- KEEP BOTH OF THESE AS THERE ARE SOME PARTICULAR CASES! --> @@ -200,12 +205,21 @@ <haxelib name="hscript-improved" /> <haxelib name="hxdiscord_rpc" if="DISCORD_RPC"/> <haxelib name="markdown" /> + <haxelib name="hxcpp-debug-server" if="TEST_BUILD debug" /> <!-- Custom Libraries --> <haxelib name="macro-fixer" /> <!-- _________________________________ Additional stuff _______________________________ --> + <section if="MODCHARTING_FEATURES"> + <haxedef name="FM_ENGINE" value="CODENAME"/> + <haxedef name="FM_ENGINE_VERSION" value="1.0"/> + + <haxelib name="funkin-modchart" /> + <haxeflag name="--macro" value="modchart.backend.macros.Macro.includeFiles()"/> + </section> + <section if="COMPILE_ALL_CLASSES"> <haxeflag name="-dce" value="no" /> <haxeflag name="--macro" value="funkin.backend.system.macros.Macros.addAdditionalClasses()" /> @@ -222,4 +236,7 @@ <haxedef name="HXCPP_CATCH_SEGV" /> <haxedef name="hscriptPos" /> <!-- for logging --> + + <!-- EXPERMENTAL FEATURES --> + <haxedef name="EXPERIMENTAL_FLXGRAPHIC_DESTROY_FIX" /> </project> diff --git a/setup-dev-branch.xml b/setup-dev-branch.xml index 64fb0c03..0835f21c 100644 --- a/setup-dev-branch.xml +++ b/setup-dev-branch.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<libraries> +<libraries checkVersion="false"> <if cond="time-fix"> <print pretty="true">Switching to branch 'time-fix'...</print> <!-- <cmd inLib="lime" cmd="git checkout time-fix" /> --> diff --git a/setup-dev.xml b/setup-dev.xml index 6299875b..09bc60dd 100644 --- a/setup-dev.xml +++ b/setup-dev.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<libraries> +<libraries checkVersion="false"> <print pretty="true">Setting up dev libs...</print> <cmd> @@ -14,7 +14,7 @@ <line>haxelib dev hxcpp dev-libs/hxcpp</line> </cmd> - <!-- Build hxcpp --> + <!-- Build hxcppp --> <cmd inLib="hxcpp" dir="tools/hxcpp" cmd="haxe compile.hxml" /> </if> @@ -94,5 +94,20 @@ </cmd> </if> + <if cond="ne_eo"> + <!-- Ignore this its only for Ne_Eo --> + <cmd> + <line>haxelib dev lime ../../lime</line> + <line>haxelib dev openfl ../../openfl</line> + <line>haxelib dev flixel ../../flixel</line> + <line>haxelib dev flixel-addons ../../flixel-addons</line> + <line>haxelib dev hscript-improved ../../hscript-improved</line> + <line>haxelib dev flxanimate ../../flxanimate</line> + <!-- <line>haxelib dev hxdiscord_rpc ../../hxdiscord_rpc</line> --> + <line>haxelib dev away3d ../../away3d</line> + <line>haxelib dev hxcpp ../../../hxcpp</line> + </cmd> + </if> + <print pretty="true">Setup for dev libs complete!</print> </libraries> diff --git a/source/external/ExternalMac.hx b/source/external/ExternalMac.hx new file mode 100644 index 00000000..b25da9bf --- /dev/null +++ b/source/external/ExternalMac.hx @@ -0,0 +1,12 @@ +package external; + +#if (mac && cpp) +@:build(external.LinkerMacro.xml('external_code.xml')) +@:include('Mac.h') +@:native('ExternalMac') +extern class ExternalMac +{ + @:native('ExternalMac::setCursorIcon') + public static function setCursorIcon(icon:Int, customCursor:cpp.ConstCharStar, customX:Single, customY:Single):Bool; +} +#end diff --git a/source/external/LinkerMacro.hx b/source/external/LinkerMacro.hx new file mode 100644 index 00000000..3f3912b9 --- /dev/null +++ b/source/external/LinkerMacro.hx @@ -0,0 +1,40 @@ +package external; + +import haxe.io.Path; +import haxe.macro.Expr; +import haxe.macro.Context; +import haxe.xml.Printer; +import sys.FileSystem; + +class LinkerMacro +{ + public static macro function xml(filename:String = 'Build.xml'):Array<Field> + { + var pos:Position = Context.currentPos(); + + var sourcePath:String = Path.directory(Context.getPosInfos(pos).file); + + if (!Path.isAbsolute(sourcePath)) + sourcePath = FileSystem.absolutePath(sourcePath); + + sourcePath = Path.removeTrailingSlashes(Path.normalize(sourcePath)); + + var includeElement:Xml = Xml.createElement('include'); + + includeElement.set('name', Path.join([sourcePath, filename])); + + @:privateAccess + { + var printer:Printer = new Printer(true); + + printer.writeNode(includeElement, '\n'); + + Context.getLocalClass().get().meta.add(':buildXml', [{ + expr: EConst(CString(printer.output.toString())), + pos: pos + }], pos); + } + + return Context.getBuildFields(); + } +} \ No newline at end of file diff --git a/source/external/external_code.xml b/source/external/external_code.xml new file mode 100644 index 00000000..308b0d35 --- /dev/null +++ b/source/external/external_code.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xml> + <pragma once="true" /> + + <files id="haxe"> + <compilerflag value="-I${this_dir}/include/mac" if="macos" /> + </files> + + <files id="__main__"> + <compilerflag value="-I${this_dir}/include/mac" if="macos" /> + </files> + + <files id="external-code"> + <section if="macos"> + <compilerflag value="-I${this_dir}/include/mac" /> + + <file name="${this_dir}/src/mac/CursorHelper.mm" /> + <file name="${this_dir}/src/mac/Mac.mm" /> + </section> + </files> + + <target id="haxe"> + <section if="macos"> + <vflag name="-framework" value="IOKit" /> + <vflag name="-framework" value="AppKit" /> + <vflag name="-framework" value="Cocoa" /> + <vflag name="-framework" value="Foundation" /> + </section> + + <files id="external-code" /> + </target> +</xml> diff --git a/source/external/include/mac/CursorHelper.h b/source/external/include/mac/CursorHelper.h new file mode 100644 index 00000000..6045293a --- /dev/null +++ b/source/external/include/mac/CursorHelper.h @@ -0,0 +1,7 @@ +#import <Foundation/Foundation.h> + +@interface CursorHelper : NSObject + ++ (NSCursor *)getCursorForSelector:(SEL)selector defaultCursor:(NSCursor *)defaultCursor; + +@end \ No newline at end of file diff --git a/source/external/include/mac/Mac.h b/source/external/include/mac/Mac.h new file mode 100644 index 00000000..516b5050 --- /dev/null +++ b/source/external/include/mac/Mac.h @@ -0,0 +1,13 @@ +#ifndef MAC_H +#define MAC_H + +#if defined(__APPLE__) + +namespace ExternalMac +{ + bool setCursorIcon(int icon, const char *customCursor, float customX, float customY); +} + +#endif + +#endif diff --git a/source/external/src/mac/CursorHelper.mm b/source/external/src/mac/CursorHelper.mm new file mode 100644 index 00000000..3999bbb5 --- /dev/null +++ b/source/external/src/mac/CursorHelper.mm @@ -0,0 +1,24 @@ +#import <Cocoa/Cocoa.h> +#import "CursorHelper.h" + +@implementation CursorHelper + ++ (NSCursor *)getCursorForSelector:(SEL)selector defaultCursor:(NSCursor *)defaultCursor +{ + NSCursor *cursor = nil; + + if ([NSCursor respondsToSelector:selector]) + { + cursor = [NSCursor performSelector:selector]; + } + else + { + NSLog(@"CursorHelper: selector %@ not found", NSStringFromSelector(selector)); + + cursor = defaultCursor; + } + + return cursor; +} + +@end diff --git a/source/external/src/mac/Mac.mm b/source/external/src/mac/Mac.mm new file mode 100644 index 00000000..de4c62f8 --- /dev/null +++ b/source/external/src/mac/Mac.mm @@ -0,0 +1,140 @@ +#include <unordered_map> + +#import <Cocoa/Cocoa.h> +#import "CursorHelper.h" + +#define ARROW 0 +#define CROSSHAIR 1 +#define CLICK 2 +#define IBEAM 3 +#define MOVE 4 +#define HAND 5 +#define DRAG 6 +#define DRAG_OPEN 7 +#define WAIT 8 +#define WAIT_ARROW 9 +#define DISABLED 10 +#define RESIZE_TR 11 +#define RESIZE_BL 12 +#define RESIZE_TL 13 +#define RESIZE_BR 14 +#define RESIZE_H 15 +#define RESIZE_V 16 +#define RESIZE_T 17 +#define RESIZE_B 18 +#define RESIZE_L 19 +#define RESIZE_R 20 +#define RESIZE_TLBR 21 +#define RESIZE_TRBL 22 +#define CUSTOM -1 + +namespace ExternalMac +{ + std::unordered_map<const char *, NSCursor *> customCursorCache; + + bool setCursorIcon(int icon, const char *customCursor, float customX, float customY) + { + NSCursor *cursor = nil; + + switch (icon) + { + case ARROW: + cursor = [NSCursor arrowCursor]; + break; + case CLICK: + cursor = [NSCursor pointingHandCursor]; + break; + case CROSSHAIR: + cursor = [NSCursor crosshairCursor]; + break; + case HAND: + cursor = [NSCursor closedHandCursor]; + break; + case IBEAM: + cursor = [NSCursor IBeamCursor]; + break; + case RESIZE_H: + cursor = [NSCursor resizeLeftRightCursor]; + break; + case RESIZE_V: + cursor = [NSCursor resizeUpDownCursor]; + break; + // TODO: DEFAULTS + case RESIZE_TL: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthWestCursor)defaultCursor:nil]; + break; + case RESIZE_TR: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthEastCursor)defaultCursor:nil]; + break; + case RESIZE_BL: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeSouthWestCursor)defaultCursor:nil]; + break; + case RESIZE_BR: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeSouthEastCursor)defaultCursor:nil]; + break; + case RESIZE_T: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthCursor)defaultCursor:nil]; + break; + case RESIZE_B: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeSouthCursor)defaultCursor:nil]; + break; + case RESIZE_L: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeWestCursor)defaultCursor:nil]; + break; + case RESIZE_R: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeEastCursor)defaultCursor:nil]; + break; + case RESIZE_TLBR: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthWestSouthEastCursor)defaultCursor:nil]; + break; + case RESIZE_TRBL: + cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthEastSouthWestCursor)defaultCursor:nil]; + break; + case WAIT: + cursor = [CursorHelper getCursorForSelector:@selector(_waitCursor)defaultCursor:nil]; + break; + case WAIT_ARROW: + cursor = [CursorHelper getCursorForSelector:@selector(_waitCursor)defaultCursor:nil]; + break; + case DISABLED: + cursor = [NSCursor operationNotAllowedCursor]; + break; + case DRAG: + cursor = [NSCursor closedHandCursor]; + break; + case DRAG_OPEN: + cursor = [NSCursor openHandCursor]; + break; + case CUSTOM: + if (customCursorCache.find(customCursor) != customCursorCache.end()) + cursor = customCursorCache[customCursor]; + else + { + NSString *cursorImageName = [NSString stringWithUTF8String:customCursor]; + NSImage *tmpImage = [[NSImage alloc] initWithContentsOfFile:cursorImageName]; + + if (tmpImage != nil) + { + cursor = [[NSCursor alloc] initWithImage:tmpImage hotSpot:NSMakePoint(customX, customY)]; + customCursorCache[customCursor] = cursor; + [tmpImage release]; + } + else + NSLog(@"Failed to load image %@", cursorImageName); + + [cursorImageName release]; + } + + break; + } + + if (cursor != nil) + { + [cursor set]; + + return true; + } + + return false; + } +} diff --git a/source/flixel/sound/FlxSound.hx b/source/flixel/sound/FlxSound.hx new file mode 100644 index 00000000..e52b8cc7 --- /dev/null +++ b/source/flixel/sound/FlxSound.hx @@ -0,0 +1,917 @@ +package flixel.sound; + +import lime.media.AudioBuffer; +import lime.media.AudioSource; +import lime.media.AudioManager; + +import openfl.Assets; +import openfl.events.IEventDispatcher; +import openfl.events.Event; +import openfl.media.Sound; +import openfl.media.SoundChannel; +import openfl.media.SoundTransform; +import openfl.media.SoundMixer; +import openfl.net.URLRequest; +import openfl.utils.AssetType; +#if flash11 +import openfl.utils.ByteArray; +#end + +import flixel.math.FlxMath; +import flixel.math.FlxPoint; +import flixel.system.FlxAssets.FlxSoundAsset; +import flixel.tweens.FlxTween; +import flixel.util.FlxSignal; +import flixel.util.FlxStringUtil; +import flixel.FlxBasic; +import flixel.FlxObject; +import flixel.FlxG; + +@:access(flixel.FlxGame) +class FlxSound extends FlxBasic { + /** + * The x position of this sound in world coordinates. + * Only really matters if you are doing proximity/panning stuff. + */ + public var x:Float; + + /** + * The y position of this sound in world coordinates. + * Only really matters if you are doing proximity/panning stuff. + */ + public var y:Float; + + /** + * Whether or not this sound should be automatically destroyed when you switch states. + */ + public var persist:Bool; + + /** + * Whether or not the sound is currently playing. + */ + public var playing(get, never):Bool; + + /** + * Set volume to a value between 0 and 1* to change how this sound is. + */ + public var volume(get, set):Float; + + /** + * Whether to make this sound muted or not. + */ + public var muted(default, set):Bool; + + #if FLX_PITCH + /** + * Set pitch, which also alters the playback speed. Default is 1. + * @since 5.0.0 + */ + public var pitch(get, set):Float; + + /** + * Alters the pitch of the sound depends on the current FlxG.timeScale. Default is true. + * @since raltyMod + */ + public var timeScaleBased:Bool; + #end + + /** + * Pan amount. -1 = full left, 1 = full right. Proximity based panning overrides this. + */ + public var pan(get, set):Float; + + /** + * The position in runtime of the music playback in milliseconds. + * If set while paused, changes only come into effect after a `resume()` call. + */ + public var time(get, set):Float; + + /** + * The offset for this FlxSound. + * Useful for just generally offsetting this sound without affecting time. + * @since raltyMod + */ + public var offset(get, set):Float; + + /** + * The length of the sound in milliseconds. + * @since 4.2.0 + */ + public var length(get, never):Float; + + /** + * The latency of the sound in milliseconds. + * @since raltyMod + */ + //public var latency(get, never):Float; + + /** + * Whether or not this sound should loop. + */ + public var looped(default, set):Bool; + + /** + * In case of looping, the point (in milliseconds) from where to restart the sound when it loops back + * @since 4.1.0 + */ + public var loopTime(default, set):Float; + + /** + * At which point to stop playing the sound, in milliseconds. + * If not set / `null`, the sound completes normally. + * @since 4.2.0 + */ + public var endTime(default, set):Null<Float>; + + /** + * The sound's "target" (for proximity and panning). + * @since raltyMod + */ + public var target:Null<FlxObject>; + + /** + * The maximum effective radius of this sound (for proximity and panning). + * @since raltyMod + */ + public var radius:Float; + + /** + * Whether the proximity alters the pan or not + * @since raltyMod + */ + public var proximityPan:Bool; + + /** + * Stores for how much channels are in the loaded sound. + * @since raltyMod + */ + public var channels(get, never):Int; + + /** + * Whether or not this sound is stereo instead of mono. + * @since raltyMod + */ + public var stereo(get, never):Bool; + + /** + * Whether or not this sound is loaded yet. + * @since raltyMod + */ + public var loaded(get, never):Bool; + + /** + * Whether or not this sound is streamed, only vorbis though. + * @since raltyMod + */ + public var streamed(get, never):Bool; + + /** + * Stores the average wave amplitude of both stereo channels. + * @since raltyMod + */ + public var amplitude(get, never):Float; + + /** + * Just the amplitude of the left stereo channel + * @since raltyMod + */ + public var amplitudeLeft(get, never):Float; + + /** + * Just the amplitude of the right stereo channel + * @since raltyMod + */ + public var amplitudeRight(get, never):Float; + + /** + * The ID3 song name. Defaults to null. Currently only works for MP3 streamed sounds. + */ + public var name(default, null):String; + + /** + * The ID3 artist name. Defaults to null. Currently only works for MP3 streamed sounds. + */ + public var artist(default, null):String; + + /** + * Whether to call `destroy()` when the sound has finished playing. + */ + public var autoDestroy:Bool; + + /** + * Signal that is dispatched on sound complete. + * Seperates the looping from onComplete. + */ + public final onFinish:FlxSignal; + + /** + * Tracker for sound complete callback. If assigned, will be called + * each time when sound reaches its end. + */ + //@:deprecated("`FlxSound.onComplete` is deprecated! Use `FlxSound.onFinish` instead.") + public var onComplete:Void->Void; + + /** + * The sound group this sound belongs to + */ + public var group(default, set):FlxSoundGroup; + + /** + * Stores the sound lime AudioBuffer if exists. + * @since raltyMod + */ + public var buffer(get, never):AudioBuffer; + + /** + * The tween used to fade this sound's volume in and out (set via `fadeIn()` and `fadeOut()`) + * @since 4.1.0 + */ + public var fadeTween:FlxTween; + + @:allow(flixel.system.frontEnds.SoundFrontEnd.load) var _sound:Sound; + var _transform:SoundTransform; + var _channel:SoundChannel; + var _source:AudioSource; + var _paused:Bool; + var _volume:Float; + var _volumeAdjust:Float; + var _time:Float; + var _offset:Float; + var _timeInterpolation:Float; + var _lastTime:Null<Float>; // FlxG.game.getTicks(), in MS + var _length:Float; + #if FLX_PITCH + var _pitch:Float; + var _timeScaleAdjust:Float; + var _realPitch:Float; + #end + var _amplitudeLeft:Float; + var _amplitudeRight:Float; + var _amplitudeUpdate:Bool; + var _alreadyPaused:Null<Bool>; + + public function new() { + super(); + onFinish = new FlxSignal(); + revive(); + } + + /** + * Resets this FlxSound properties. + * + * @param clean Whether if this FlxSound also needs to be cleaned up too. + */ + public function reset():Void { + if (_source != null) stop(); + x = y = 0; + + muted = false; + looped = false; + loopTime = 0; + endTime = null; + autoDestroy = false; + visible = false; + target = null; + radius = 0; + proximityPan = true; + onComplete = null; + timeScaleBased = true; + + _alreadyPaused = null; + _cameras = null; + _lastTime = null; + _paused = false; + _offset = _length = _time = 0; + _volume = _volumeAdjust = 1; + _amplitudeLeft = _amplitudeRight = 0; + _amplitudeUpdate = true; + #if FLX_PITCH _pitch = _realPitch = _timeScaleAdjust = 1; #end + } + + /** + * Internal cleanup function for cleaning up this FlxSound. + * @since raltyMod + */ + function cleanup(destroySound:Bool, resetPosition:Bool = true) @:privateAccess { + active = false; + _lastTime = null; + + if (destroySound) { + onFinish.removeAll(); + + if (group != null) group.remove(this); + + if (_channel != null) { + _channel.stop(); + _channel = null; + } + if (_source != null) { + _source.onComplete.remove(stopped); + _source.onLoop.remove(source_looped); + _source.dispose(); + } + _source = null; + _sound = null; + + reset(); + } + else if (_channel != null && _channel.__isValid) { + if (resetPosition) { + _source.stop(); + + _time = 0; + _paused = false; + } + else if (!_paused) { + get_time(); + _source.pause(); + } + } + } + + /** + * Handles fade out, fade in, panning, proximity, and amplitude operations each frame. + */ + override function update(elapsed:Float):Void { + if (!playing) return; + + var timeScaleTarget = timeScaleBased ? FlxG.timeScale : 1.0; + if (_timeScaleAdjust != timeScaleTarget) { + _timeScaleAdjust = timeScaleTarget; + pitch = _pitch; + if (_channel == null) return; + } + + _amplitudeUpdate = true; + + var radialMultiplier = 1.0; + + // Distance-based volume control (TODO for Ralty: REDO THIS) + if (target != null) { + var targetPosition = target.getPosition(); + radialMultiplier = targetPosition.distanceTo(FlxPoint.weak(x, y)) / radius; + targetPosition.put(); + radialMultiplier = 1 - FlxMath.bound(radialMultiplier, 0, 1); + + if (proximityPan && _transform != null) { + var d:Float = (x - target.x) / radius; + _transform.pan = FlxMath.bound(d, -1, 1); + } + } + + _volumeAdjust = radialMultiplier; + updateTransform(); + } + + override function revive() { + reset(); + super.revive(); + } + + override function kill() { + super.kill(); + reset(); + } + + override function destroy() { + super.destroy(); + cleanup(true); + } + + /** + * One of the main setup functions for sounds, this function loads a sound from an embedded MP3. + * + * @param embeddedSound An embedded Class object representing an MP3 file. + * @param looped Whether or not this sound should loop endlessly. + * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default. + * @param onComplete Called when the sound finished playing + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function loadEmbedded(embeddedSound:FlxSoundAsset, looped = false, autoDestroy = false, ?onComplete:Void->Void):FlxSound { + if (!exists || embeddedSound == null) return this; + cleanup(true); + + if ((embeddedSound is Sound)) _sound = embeddedSound; + else if ((embeddedSound is Class)) _sound = Type.createInstance(embeddedSound, []); + else if ((embeddedSound is String)) { + if (Assets.exists(embeddedSound, AssetType.MUSIC) && this == FlxG.sound.music) + _sound = Assets.getMusic(embeddedSound); + else if (Assets.exists(embeddedSound, AssetType.SOUND)) + _sound = Assets.getSound(embeddedSound); + else + FlxG.log.error('Could not find a Sound asset with an ID of \'$embeddedSound\'.'); + } + + return init(looped, autoDestroy, onComplete); + } + + /** + * One of the main setup functions for sounds, this function loads a sound from a URL. + * + * @param soundURL A string representing the URL of the MP3 file you want to play. + * @param looped Whether or not this sound should loop endlessly. + * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default. + * @param onComplete Called when the sound finished playing + * @param onLoad Called when the sound finished loading. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function loadStream(soundURL:String, looped = false, autoDestroy = false, ?onComplete:Void->Void, ?onLoad:Void->Void):FlxSound { + if (!exists) return this; + cleanup(true); + + _sound = new Sound(); + _sound.addEventListener(Event.ID3, gotID3); + var loadCallback:Event->Void = null; + loadCallback = function(e:Event) + { + (e.target : IEventDispatcher).removeEventListener(e.type, loadCallback); + // Check if the sound was destroyed before calling. Weak ref doesn't guarantee GC. + if (_sound == e.target) + { + _length = _sound.length; + if (onLoad != null) + onLoad(); + } + } + // Use a weak reference so this can be garbage collected if destroyed before loading. + _sound.addEventListener(Event.COMPLETE, loadCallback, false, 0, true); + _sound.load(new URLRequest(soundURL)); + + return init(looped, autoDestroy, onComplete); + } + + #if flash11 + /** + * One of the main setup functions for sounds, this function loads a sound from a ByteArray. + * + * @param bytes A ByteArray object. + * @param looped Whether or not this sound should loop endlessly. + * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing. + * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function loadByteArray(bytes:ByteArray, looped = false, autoDestroy = false, ?onComplete:Void->Void):FlxSound { + if (!exists) return this; + cleanup(true); + + _sound = new Sound(); + _sound.addEventListener(Event.ID3, gotID3); + _sound.loadCompressedDataFromByteArray(bytes, bytes.length); + + return init(looped, autoDestroy, onComplete); + } + #end + + function init(?looped:Null<Bool>, ?autoDestroy:Null<Bool>, ?onComplete:Null<Void->Void>):FlxSound { + if (looped != null) this.looped = looped; + if (autoDestroy != null) this.autoDestroy = autoDestroy; + if (onComplete != null) this.onComplete = onComplete; + + if (_sound != null) makeChannel(); + else _length = 0; + + endTime = null; + + return this; + } + + /** + * Call inbetween init function if a sound asset does exists. + * @since raltyMod + */ + function makeChannel() @:privateAccess { + if (_sound.__buffer == null) return; + if (_source != null) _source.dispose(); + + if (_channel == null) (_source = (_channel = new SoundChannel(null)).__source = new AudioSource(_sound.__buffer)).gain = 0; + else { + (_source = new AudioSource(_sound.__buffer)).gain = 0; + _channel.__dispose(); + _channel.__source = _source; + SoundMixer.__registerSoundChannel(_channel); + } + if (_transform == null) _transform = new SoundTransform(); + _transform.pan = 0; + _length = _source.length; + + _source.onComplete.add(stopped); + _source.onLoop.add(source_looped); + _channel.__soundTransform = _transform; + _channel.__isValid = true; + } + + /** + * Call after adjusting the volume to update the sound channel's settings. + */ + @:allow(flixel.sound.FlxSoundGroup) + function updateTransform() { + if (_transform == null) { + _transform = new SoundTransform(); + pan = pan; + } + _transform.volume = #if FLX_SOUND_SYSTEM ((FlxG.sound.muted || muted) ? 0 : 1) * FlxG.sound.volume * #end + (group != null ? group.volume : 1) * _volume * _volumeAdjust; + + if (_channel != null) _channel.soundTransform = _transform; + } + + /** + * Call this function if you want this sound's volume to change + * based on distance from a particular FlxObject. + * + * @param X The X position of the sound. + * @param Y The Y position of the sound. + * @param TargetObject The object you want to track. + * @param Radius The maximum distance this sound can travel. + * @param Pan Whether panning should be used in addition to the volume changes. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function proximity(x = 0.0, y = 0.0, ?targetObject:FlxObject, ?radius:Float, pan = true):FlxSound { + setPosition(x, y); + if (targetObject != null) this.target = targetObject; + if (radius != null) this.radius = radius; + proximityPan = pan; + + return this; + } + + /** + * Helper function to set the coordinates of this object. + * Sound positioning is used in conjunction with proximity/panning. + * + * @param x The new x position + * @param y The new y position + */ + public inline function setPosition(x = 0.0, y = 0.0):Void { + this.x = x; + this.y = y; + } + + /** + * Call this function to play the sound - also works on paused sounds. + * + * @param forceRestart Whether to start the sound over or not. + * Default value is false, meaning if the sound is already playing or was + * paused when you call play(), it will continue playing from its current + * position, NOT start again from the beginning. + * @param startTime At which point to start playing the sound, in milliseconds. + * @param endTime At which point to stop playing the sound, in milliseconds. + * If not set / `null`, the sound completes normally. + */ + public function play(forceRestart = false, startTime = 0.0, ?endTime:Null<Float>):FlxSound { + if (!exists) return this; + + if (forceRestart) cleanup(false, true); + else if (playing) return this; + + if (endTime != null) this.endTime = endTime; + if (_paused) resume(); + else startSound(startTime); + + return this; + } + + /** + * Unpause a sound. Only works on sounds that have been paused. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function resume():FlxSound { + if (_paused) startSound(_time); + return this; + } + + /** + * Call this function to pause this sound. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function pause():FlxSound { + if (!playing) return this; + + cleanup(false, false); + _paused = true; + return this; + } + + /** + * Call this function to stop this sound. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function stop():FlxSound { + cleanup(autoDestroy, true); + return this; + } + + /** + * Helper function that tweens this sound's volume. + * + * @param duration The amount of time the fade-out operation should take. + * @param to The volume to tween to, 0 by default. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function fadeOut(duration = 1.0, to = 0.0, ?onComplete:FlxTween->Void):FlxSound { + if (fadeTween != null) fadeTween.cancel(); + fadeTween = FlxTween.num(volume, to, duration, {onComplete: onComplete}, volumeTween); + + return this; + } + + /** + * Helper function that tweens this sound's volume. + * If the sound wasn't playing at all, it'll play before the tween starts. + * + * @param duration The amount of time the fade-in operation should take. + * @param from The volume to tween from, 0 by default. + * @param to The volume to tween to, 1 by default. + * @return This FlxSound instance (nice for chaining stuff together, if you're into that). + */ + public function fadeIn(duration = 1.0, from = 0.0, to = 1.0, ?onComplete:FlxTween->Void):FlxSound { + if (!playing) play(); + if (fadeTween != null) fadeTween.cancel(); + fadeTween = FlxTween.num(from, to, duration, {onComplete: onComplete}, volumeTween); + + return this; + } + + function volumeTween(f:Float) volume = f; + + /** + * Returns the currently selected "real" volume of the sound (takes fades and proximity into account). + * + * @return The adjusted volume of the sound. + */ + public inline function getActualVolume():Float + return _volume * _volumeAdjust; + + #if FLX_PITCH + /** + * Returns the currently selected "real" pitch of the sound. + * + * @return The adjusted pitch of the sound. + */ + public inline function getActualPitch():Float + return _realPitch; + #end + + /** + * Returns the actual time coming from the internal, can be used for detecting sync error. + * + * @return The actual time of the sound. + */ + public inline function getActualTime():Float { + get_time(); + return _time; + } + + /** + * An internal helper function used to attempt to start playing + * the sound and populate the _channel variable. + */ + function startSound(startTime:Float) @:privateAccess { + if (_sound == null) return; + + _paused = false; + _time = startTime; + _lastTime = FlxG.game.getTicks(); + if (_channel == null || !_channel.__isValid || _source == null #if lime_cffi || _source.__backend.disposed || _source.__backend.handle == null #end) + makeChannel(); + + if (_channel != null) { + #if FLX_PITCH + _timeScaleAdjust = timeScaleBased ? FlxG.timeScale : 1.0; + pitch = _pitch; + #end + + _channel.soundTransform = _transform; + _channel.__lastPeakTime = -10; + _channel.__leftPeak = 0; + _channel.__rightPeak = 0; + + #if lime_cffi _source.__backend.playing = true; #end + _source.offset = 0; + _source.currentTime = startTime + _offset; + #if !lime_cffi _source.play(); #end + + looped = looped; + loopTime = loopTime; + endTime = endTime; + + active = true; + } + else { + exists = false; + active = false; + } + } + + function stopped() { + onFinish.dispatch(); + + if (onComplete != null) onComplete(); + + if (looped) { + cleanup(false); + play(false, loopTime, endTime); + } + else cleanup(autoDestroy); + } + + function source_looped() { + if (onComplete != null) onComplete(); + + if (!looped) { + cleanup(autoDestroy); + _lastTime = FlxG.game.getTicks(); + _time = loopTime; + } + else _channel.loops = 999; + } + + /** + * Internal event handler for ID3 info (i.e. fetching the song name). + */ + function gotID3(_) { + name = _sound.id3.songName; + artist = _sound.id3.artist; + _sound.removeEventListener(Event.ID3, gotID3); + } + + #if FLX_SOUND_SYSTEM + @:allow(flixel.system.frontEnds.SoundFrontEnd) + function onFocus() if (!_alreadyPaused) { + resume(); + _alreadyPaused = null; + } + + @:allow(flixel.system.frontEnds.SoundFrontEnd) + function onFocusLost() if (_alreadyPaused == null && !(_alreadyPaused = _paused)) pause(); + #end + + function set_group(value:FlxSoundGroup):FlxSoundGroup { + if (value != null) value.add(this); + else group.remove(this); + return group; + } + + inline function get_playing():Bool @:privateAccess + return _channel != null && _channel.__isValid && _source.playing; + + inline function get_volume():Float + return _volume; + + inline function set_volume(v:Float):Float { + _volume = FlxMath.bound(v, 0, 10); + updateTransform(); + return _volume; + } + + inline function set_muted(v:Bool):Bool { + muted = v; + updateTransform(); + return muted; + } + + inline function get_loaded():Bool + return buffer != null; + + inline function get_streamed():Bool + @:privateAccess return #if lime_vorbis _sound != null && _sound.__buffer.__srcVorbisFile != null #else false #end; + + inline function get_buffer():AudioBuffer + @:privateAccess return _sound != null ? _sound.__buffer : null; + + function update_amplitude():Void @:privateAccess { + if (_channel == null || !_channel.__updatePeaks(get_time()) || !_amplitudeUpdate) return; + + _amplitudeUpdate = false; + _amplitudeLeft = _channel.__leftPeak; + _amplitudeRight = _channel.__rightPeak; + } + + inline function get_amplitudeLeft():Float { + update_amplitude(); + return _amplitudeLeft; + } + + inline function get_amplitudeRight():Float { + update_amplitude(); + return _amplitudeRight; + } + + inline function get_amplitude():Float { + update_amplitude(); + return Math.max(_amplitudeLeft, _amplitudeRight);//if (stereo) (_amplitudeLeft + _amplitudeRight) * 0.5; else _amplitudeLeft; + } + + inline function get_channels():Int + @:privateAccess return (buffer != null) ? buffer.channels : 0; + + inline function get_stereo():Bool + return channels > 1; + + #if FLX_PITCH + inline function get_pitch():Float + return _pitch; + + function set_pitch(v:Float):Float { + _realPitch = (_pitch = v) * _timeScaleAdjust; + if (_channel != null) _channel.pitch = _realPitch; + return _pitch; + } + #end + + function set_looped(v:Bool):Bool { + if (playing) { + if (v) _channel.loops = 999; + else _channel.loops = 0; + } + return looped = v; + } + + function set_loopTime(v:Float):Float { + if (playing) _channel.loopTime = v; + return loopTime = v; + } + + function set_endTime(v:Null<Float>):Null<Float> { + if (playing) { + if (v != null && v > 0 && v < _length) _channel.endTime = v; + else _channel.endTime = null; + } + return endTime = v; + } + + inline function get_pan():Float return _transform.pan; + inline function set_pan(pan:Float):Float return _transform.pan = pan; + + inline function getFakeTime():Float { + if (_source.playing && _realPitch > 0 && _lastTime != null) + return _time + (FlxG.game.getTicks() - _lastTime) * _realPitch * _timeInterpolation; + else + return _time; + } + function get_time():Float { + if (_channel == null || @:privateAccess !_channel.__isValid || /*AudioManager.context == null*/funkin.backend.system.Main.audioDisconnected) return _time; + + final sourceTime = _source.currentTime - _source.offset - _offset; + if (!_source.playing || _realPitch <= 0) { + _lastTime = null; + return _time = sourceTime; + } + + final fakeTime = getFakeTime(); + if (sourceTime != _time) { + _lastTime = FlxG.game.getTicks(); + if ((_timeInterpolation = 1 - Math.min(fakeTime - sourceTime, 1000) * 0.001) < 1 && _timeInterpolation > .9) + return _time = fakeTime; + else { + _timeInterpolation = 1; + return _time = sourceTime; + } + } + else + return fakeTime; + } + + function set_time(time:Float):Float @:privateAccess { + time = FlxMath.bound(time, _offset, length - 1); + if (_channel != null && _realPitch > 0) { + if (!_channel.__isValid) { + cleanup(false, true); + startSound(time); + } + else if (playing) { + _source.offset = 0; + _source.currentTime = time + _offset; + } + } + + _lastTime = null; + return _time = time; + } + + inline function get_offset():Float return _offset; + inline function set_offset(offset:Float):Float { + if (_offset == (_offset = offset)) return offset; + //time = time + _offset; + return offset; + } + + inline function get_length():Float return _length - _offset; + + /*function get_latency():Float { + if (_channel != null) return _source.latency; + return 0; + }*/ + + override function toString():String { + return FlxStringUtil.getDebugString([ + LabelValuePair.weak("playing", playing), + LabelValuePair.weak("time", _time), + LabelValuePair.weak("length", length), + LabelValuePair.weak("volume", volume), + LabelValuePair.weak("pitch", pitch) + ]); + } +} \ No newline at end of file diff --git a/source/flx3d/Flx3DCamera.hx b/source/flx3d/Flx3DCamera.hx index 129ace5d..d4f76229 100644 --- a/source/flx3d/Flx3DCamera.hx +++ b/source/flx3d/Flx3DCamera.hx @@ -73,7 +73,7 @@ class Flx3DCamera extends FlxCamera { var context = new AssetLoaderContext(); var noExt = Path.withoutExtension(assetPath); trace(noExt); - context.mapUrlToData('${Path.withoutDirectory(noExt)}.mtl', '$noExt.mtl'); + context.mapUrlToData(Path.withoutDirectory(noExt) + '.mtl', noExt + '.mtl'); var material:TextureMaterial = null; if (texturePath != null) diff --git a/source/flx3d/Flx3DUtil.hx b/source/flx3d/Flx3DUtil.hx index b90076a8..71706e35 100644 --- a/source/flx3d/Flx3DUtil.hx +++ b/source/flx3d/Flx3DUtil.hx @@ -14,7 +14,7 @@ import flixel.FlxG; * @see https://twitter.com/Ne_Eo_Twitch */ @:access(away3d.core.managers.Stage3DManager) -class Flx3DUtil +final class Flx3DUtil { #if THREE_D_SUPPORT /** diff --git a/source/flx3d/Flx3DView.hx b/source/flx3d/Flx3DView.hx index 5413fe94..59413e77 100644 --- a/source/flx3d/Flx3DView.hx +++ b/source/flx3d/Flx3DView.hx @@ -46,7 +46,7 @@ class Flx3DView extends FlxView3D { var context = new AssetLoaderContext(); var noExt = Path.withoutExtension(assetPath); trace(noExt); - context.mapUrlToData('${Path.withoutDirectory(noExt)}.mtl', '$noExt.mtl'); + context.mapUrlToData(Path.withoutDirectory(noExt) + '.mtl', noExt + '.mtl'); var material:TextureMaterial = null; if (texturePath != null) diff --git a/source/funkin/Types.hx b/source/funkin/Types.hx new file mode 100644 index 00000000..08f0b17c --- /dev/null +++ b/source/funkin/Types.hx @@ -0,0 +1,4 @@ +package funkin; + +@:dox(hide) +typedef SingleOrFloat = #if (java || hl || cpp) Single #else Float #end; \ No newline at end of file diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 00373e7b..90ac68c9 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -32,6 +32,15 @@ enum abstract XMLAnimType(Int) default: def; } } + + @:to public function toString():String { + return switch (cast this) + { + case NONE: "none"; + case BEAT: "beat"; + case LOOP: "loop"; + } + } } class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements IOffsetCompatible implements IXMLEvents @@ -42,9 +51,10 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I public var beatAnims:Array<BeatAnim> = []; public var name:String; public var zoomFactor:Float = 1; - public var initialZoom:Float = 1; public var debugMode:Bool = false; public var animDatas:Map<String, AnimData> = []; + public var animEnabled:Bool = true; + public var zoomFactorEnabled:Bool = true; public var globalCurFrame(get, set):Int; @@ -75,24 +85,34 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I moves = false; } - public static function copyFrom(source:FunkinSprite) + /** + * Gets the graphics and copies other properties from another sprite (Works both for `FlxSprite` and `FunkinSprite`!). + */ + public static function copyFrom(source:FlxSprite):FunkinSprite { var spr = new FunkinSprite(); + var casted:FunkinSprite = null; + if (source is FunkinSprite) + casted = cast source; + @:privateAccess { spr.setPosition(source.x, source.y); spr.frames = source.frames; - if (source.animateAtlas != null && source.atlasPath != null) - spr.loadSprite(source.atlasPath); + if (casted != null && casted.animateAtlas != null && casted.atlasPath != null) + spr.loadSprite(casted.atlasPath); spr.animation.copyFrom(source.animation); spr.visible = source.visible; spr.alpha = source.alpha; spr.antialiasing = source.antialiasing; spr.scale.set(source.scale.x, source.scale.y); spr.scrollFactor.set(source.scrollFactor.x, source.scrollFactor.y); - spr.skew.set(source.skew.x, source.skew.y); - spr.transformMatrix = source.transformMatrix; - spr.matrixExposed = source.matrixExposed; - spr.animOffsets = source.animOffsets.copy(); + + if (casted != null) { + spr.skew.set(casted.skew.x, casted.skew.y); + spr.transformMatrix = casted.transformMatrix; + spr.matrixExposed = casted.matrixExposed; + spr.animOffsets = casted.animOffsets.copy(); + } } return spr; } @@ -123,6 +143,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I { frames = Paths.getFrames(path, true); } + return this; } public function onPropertySet(property:String, value:Dynamic) { @@ -133,9 +154,9 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I private var countedBeat = 0; public function beatHit(curBeat:Int) { + if(!animEnabled) return; if (lastAnimContext != LOCK && beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0) { - if(skipNegativeBeats && curBeat < 0) return; // TODO: find a solution without countedBeat var anim = beatAnims[FlxMath.wrap(countedBeat++, 0, beatAnims.length - 1)]; if (anim.name != null && anim.name != "null" && anim.name != "none") @@ -151,49 +172,6 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I { } - public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect - { - __doPreZoomScaleProcedure(camera); - var r = super.getScreenBounds(newRect, camera); - __doPostZoomScaleProcedure(); - return r; - } - - public override function drawComplex(camera:FlxCamera) - { - super.drawComplex(camera); - } - - public override function doAdditionalMatrixStuff(matrix:FlxMatrix, camera:FlxCamera) - { - super.doAdditionalMatrixStuff(matrix, camera); - matrix.translate(-camera.width / 2, -camera.height / 2); - - var requestedZoom = FlxMath.lerp(1, camera.zoom, zoomFactor); - var diff = requestedZoom / camera.zoom; - matrix.scale(diff, diff); - matrix.translate(camera.width / 2, camera.height / 2); - } - - public override function getScreenPosition(?point:FlxPoint, ?Camera:FlxCamera):FlxPoint - { - if (__shouldDoScaleProcedure()) - { - __oldScrollFactor.set(scrollFactor.x, scrollFactor.y); - var requestedZoom = FlxMath.lerp(initialZoom, camera.zoom, zoomFactor); - var diff = requestedZoom / camera.zoom; - - scrollFactor.scale(1 / diff); - - var r = super.getScreenPosition(point, Camera); - - scrollFactor.set(__oldScrollFactor.x, __oldScrollFactor.y); - - return r; - } - return super.getScreenPosition(point, Camera); - } - // ANIMATE ATLAS DRAWING #if REGION public override function draw() @@ -212,7 +190,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I public function copyAtlasValues() { @:privateAccess { - animateAtlas.cameras = cameras; + animateAtlas.cameras = cameras; // investigate if we can use _cameras animateAtlas.scrollFactor = scrollFactor; animateAtlas.scale = scale; animateAtlas.offset = offset; @@ -251,33 +229,60 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I } #end - // SCALING FUNCS - #if REGION - private inline function __shouldDoScaleProcedure() - return zoomFactor != 1; + // ZOOM FACTOR + private inline function __shouldDoZoomFactor() + return zoomFactorEnabled && zoomFactor != 1; - static var __oldScrollFactor:FlxPoint = new FlxPoint(); - static var __oldScale:FlxPoint = new FlxPoint(); - var __skipZoomProcedure:Bool = false; - - private function __doPreZoomScaleProcedure(camera:FlxCamera) + public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect { - if (__skipZoomProcedure = !__shouldDoScaleProcedure()) - return; - __oldScale.set(scale.x, scale.y); - var requestedZoom = FlxMath.lerp(initialZoom, camera.zoom, zoomFactor); - var diff = requestedZoom * camera.zoom; + if (camera == null) + camera = FlxG.camera; - scale.scale(diff); + var r = super.getScreenBounds(newRect, camera); + + if(__shouldDoZoomFactor()) { + r.x -= camera.width / 2; + r.y -= camera.height / 2; + + var ratio = (camera.zoom > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.zoom, 1, zoomFactor)); + r.x *= ratio; + r.y *= ratio; + r.width *= ratio; + r.height *= ratio; + + r.x += camera.width / 2; + r.y += camera.height / 2; + } + return r; } - private function __doPostZoomScaleProcedure() + override public function isOnScreen(?camera:FlxCamera):Bool { - if (__skipZoomProcedure) - return; - scale.set(__oldScale.x, __oldScale.y); + if (forceIsOnScreen) + return true; + + if (camera == null) + camera = FlxG.camera; + + var bounds = getScreenBounds(_rect, camera); + if (bounds.width == 0 && bounds.height == 0) + return false; + return camera.containsRect(bounds); + } + + // ZOOM FACTOR RENDERING + public override function doAdditionalMatrixStuff(matrix:FlxMatrix, camera:FlxCamera) + { + super.doAdditionalMatrixStuff(matrix, camera); + if(__shouldDoZoomFactor()) { + matrix.translate(-camera.width / 2, -camera.height / 2); + + var requestedZoom = (camera.zoom >= 0 ? Math.max : Math.min)(FlxMath.lerp(1, camera.zoom, zoomFactor), 0); + var diff = requestedZoom / camera.zoom; + matrix.scale(diff, diff); + matrix.translate(camera.width / 2, camera.height / 2); + } } - #end // OFFSETTING #if REGION @@ -403,6 +408,11 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I return animateAtlas != null ? animateAtlas.anim.isAtEnd : (animation.curAnim != null ? animation.curAnim.isAtEnd : false); } + override function updateAnimation(elapsed:Float) { + if (animEnabled) + super.updateAnimation(elapsed); + } + // Backwards compat (the names used to be all different and it sucked, please lets use the same format in the future) - Nex public inline function hasAnimation(AnimName:String) return hasAnim(AnimName); public inline function removeAnimation(name:String) return removeAnim(name); diff --git a/source/funkin/backend/MusicBeatState.hx b/source/funkin/backend/MusicBeatState.hx index 5bbdab62..38d51281 100644 --- a/source/funkin/backend/MusicBeatState.hx +++ b/source/funkin/backend/MusicBeatState.hx @@ -105,6 +105,8 @@ class MusicBeatState extends FlxState implements IBeatReceiver public static var skipTransOut:Bool = false; public static var skipTransIn:Bool = false; + public static var ALLOW_DEV_RELOAD:Bool = true; + inline function get_controls():Controls return PlayerSettings.solo.controls; inline function get_controlsP1():Controls @@ -139,7 +141,9 @@ class MusicBeatState extends FlxState implements IBeatReceiver script.load(); } } + #if EXPERMENTAL_SCRIPT_RELOADING else stateScripts.reload(); + #end } } @@ -151,16 +155,20 @@ class MusicBeatState extends FlxState implements IBeatReceiver call("postUpdate", [elapsed]); } - if (_requestSubStateReset) - { + if (_requestSubStateReset) { _requestSubStateReset = false; resetSubState(); } - if (subState != null) - { - subState.tryUpdate(elapsed); + + if (/*subState == null && */(ALLOW_DEV_RELOAD && controls.DEV_RELOAD)) { + Logs.trace("Reloading Current State...", INFO, YELLOW); + FlxG.resetState(); } + + if (subState != null) + subState.tryUpdate(elapsed); } + override function create() { loadScript(); @@ -221,10 +229,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver override function update(elapsed:Float) { - // TODO: DEBUG MODE!! - if (FlxG.keys.justPressed.F5) { - loadScript(); - } call("update", [elapsed]); super.update(elapsed); diff --git a/source/funkin/backend/MusicBeatSubstate.hx b/source/funkin/backend/MusicBeatSubstate.hx index 6cd2858f..5ad900d4 100644 --- a/source/funkin/backend/MusicBeatSubstate.hx +++ b/source/funkin/backend/MusicBeatSubstate.hx @@ -122,7 +122,9 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver script.load(); } } + #if EXPERMENTAL_SCRIPT_RELOADING else stateScripts.reload(); + #end } } @@ -134,15 +136,19 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver call("postUpdate", [elapsed]); } - if (_requestSubStateReset) - { + // if (subState == null && (MusicBeatState.ALLOW_DEV_RELOAD && controls.DEV_RELOAD)) { + // Logs.trace("Reloading Current SubState...", INFO, YELLOW); + // var test = Type.createInstance(Type.getClass(this), [this.scriptsAllowed, this.scriptName]); + // parent.openSubState(test); + // } + + if (_requestSubStateReset) { _requestSubStateReset = false; resetSubState(); } + if (subState != null) - { subState.tryUpdate(elapsed); - } } override function close() { @@ -179,10 +185,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver override function update(elapsed:Float) { - // TODO: DEBUG MODE!! - if (FlxG.keys.justPressed.F5) { - loadScript(); - } call("update", [elapsed]); super.update(elapsed); } diff --git a/source/funkin/backend/MusicBeatTransition.hx b/source/funkin/backend/MusicBeatTransition.hx index 0cb44df0..e2c1d0cd 100644 --- a/source/funkin/backend/MusicBeatTransition.hx +++ b/source/funkin/backend/MusicBeatTransition.hx @@ -4,8 +4,10 @@ import flixel.FlxState; import flixel.tweens.FlxTween; import funkin.backend.scripting.Script; import funkin.backend.scripting.events.CancellableEvent; +import funkin.backend.scripting.events.ResizeEvent; import funkin.backend.scripting.events.TransitionCreationEvent; import funkin.backend.utils.FunkinParentDisabler; +import funkin.editors.ui.UIState; class MusicBeatTransition extends MusicBeatSubstate { public static var script:String = Flags.DEFAULT_TRANSITION_SCRIPT; @@ -52,23 +54,12 @@ class MusicBeatTransition extends MusicBeatSubstate { return; } - blackSpr = new FlxSprite(0, transOut ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1); - blackSpr.scale.set(transitionCamera.width, transitionCamera.height); + add(blackSpr = new FlxSprite(0, transOut ? -transitionCamera.height : transitionCamera.height).makeGraphic(1, 1, -1)); blackSpr.color = 0xFF000000; - blackSpr.updateHitbox(); - add(blackSpr); + add(transitionSprite = new FunkinSprite().loadSprite(Paths.image('menus/transitionSpr'))); + resizeDefaultSprites(); - transitionSprite = new FunkinSprite(); - transitionSprite.loadSprite(Paths.image('menus/transitionSpr')); - if (transitionSprite.animateAtlas == null) { - transitionSprite.setGraphicSize(transitionCamera.width, transitionCamera.height); - transitionSprite.updateHitbox(); - } else { - transitionSprite.screenCenter(); - } transitionCamera.flipY = !transOut; - add(transitionSprite); - transitionCamera.scroll.y = transitionCamera.height; transitionTween = FlxTween.tween(transitionCamera.scroll, {y: -transitionCamera.height}, 2/3, { ease: FlxEase.sineOut, @@ -110,6 +101,28 @@ class MusicBeatTransition extends MusicBeatSubstate { transitionScript.call('postUpdate', [elapsed]); } + public override function onResize(w:Int, h:Int) { + super.onResize(w, h); + if (FlxG.state is UIState && UIState.resolutionAware) resizeDefaultSprites(); + transitionScript.call('onResize', [EventManager.get(ResizeEvent).recycle(w, h, null, null)]); + } + + public function resizeDefaultSprites() { + if (blackSpr != null) { + blackSpr.scale.set(transitionCamera.width, transitionCamera.height); + blackSpr.updateHitbox(); + } + + if (transitionSprite != null) { + if (transitionSprite.animateAtlas == null) { + transitionSprite.setGraphicSize(transitionCamera.width, transitionCamera.height); + transitionSprite.updateHitbox(); + } else { + transitionSprite.screenCenter(); + } + } + } + public function finish() { var event = new CancellableEvent(); transitionScript.call('onFinish', [event]); diff --git a/source/funkin/backend/assets/AssetsLibraryList.hx b/source/funkin/backend/assets/AssetsLibraryList.hx index 1d7da43f..d031ae2c 100644 --- a/source/funkin/backend/assets/AssetsLibraryList.hx +++ b/source/funkin/backend/assets/AssetsLibraryList.hx @@ -81,7 +81,7 @@ class AssetsLibraryList extends AssetLibrary { if (l is IModsAssetLibrary) { var lib = cast(l, IModsAssetLibrary); for(e in lib.getFiles(folder)) - content.push(e); + content.pushOnce(e); } #end } diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx index f79c557c..81460a99 100644 --- a/source/funkin/backend/assets/ModsFolder.hx +++ b/source/funkin/backend/assets/ModsFolder.hx @@ -96,13 +96,18 @@ class ModsFolder { public static function getModsList():Array<String> { var mods:Array<String> = []; #if MOD_SUPPORT + if (!FileSystem.exists(modsPath)) { + // Mods directory does not exist yet, create it + FileSystem.createDirectory(modsPath); + } + final modsList:Array<String> = FileSystem.readDirectory(modsPath); if (modsList == null || modsList.length <= 0) return mods; for (modFolder in modsList) { - if (FileSystem.isDirectory('${modsPath}${modFolder}')) { + if (FileSystem.isDirectory(modsPath + modFolder)) { mods.push(modFolder); } else { var ext = Path.extension(modFolder).toLowerCase(); diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index a02465e2..9a2b9a97 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -71,16 +71,14 @@ class Paths inline static public function voices(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; if (ext == null) ext = Flags.SOUND_EXT; - song = song.toLowerCase(); - var diff = getPath('songs/$song/song/Voices$prefix-${difficulty.toLowerCase()}.${ext}', null); + var diff = getPath('songs/$song/song/Voices$prefix-${difficulty}.${ext}', null); return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Voices$prefix.${ext}', null); } inline static public function inst(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; if (ext == null) ext = Flags.SOUND_EXT; - song = song.toLowerCase(); - var diff = getPath('songs/$song/song/Inst$prefix-${difficulty.toLowerCase()}.${ext}', null); + var diff = getPath('songs/$song/song/Inst$prefix-${difficulty}.${ext}', null); return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Inst$prefix.${ext}', null); } @@ -100,8 +98,7 @@ class Paths if (!OpenFlAssets.exists(scriptPath)) { var p:String; for(ex in Script.scriptExtensions) { - p = '$scriptPath.$ex'; - if (OpenFlAssets.exists(p)) { + if (OpenFlAssets.exists(p = scriptPath + '.' + ex)) { scriptPath = p; break; } @@ -112,8 +109,7 @@ class Paths static public function chart(song:String, ?difficulty:String):String { - difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY).toLowerCase(); - song = song.toLowerCase(); + difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY); return getPath('songs/$song/charts/$difficulty.json', null); } @@ -191,6 +187,29 @@ class Paths return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true, ext), false, null, false, ext); } + /** + * Checks if the images needed for using getFrames() exist. + * @param key Path to the image + * @param checkAtlas Whenever to check for the Animation.json file (used in FlxAnimate) + * @param assetsPath Whenever to use the raw path or to pass it through Paths.image() + * @param library (Additional) library to load the frames from. + * @return True if the images exist, false otherwise. + **/ + public static function framesExists(key:String, checkAtlas:Bool = false, checkMulti:Bool = true, assetsPath:Bool = false, ?library:String) { + var path = assetsPath ? key : Paths.image(key, library, true); + var noExt = Path.withoutExtension(path); + if(checkAtlas && Assets.exists('$noExt/Animation.json')) + return true; + if(checkMulti && Assets.exists('$noExt/1.png')) + return true; + if(Assets.exists('$noExt.xml')) + return true; + if(Assets.exists('$noExt.txt')) + return true; + if(Assets.exists('$noExt.json')) + return true; + return false; + } /** * Loads frames from a specific image path. Supports Sparrow Atlases, Packer Atlases, and multiple spritesheets. @@ -246,14 +265,13 @@ class Paths } return content; } - - public static function getFolderContent(key:String, addPath:Bool = false, source:AssetSource = BOTH):Array<String> { + static public function getFolderContent(key:String, addPath:Bool = false, source:AssetSource = BOTH, noExtension:Bool = false):Array<String> { // designed to work both on windows and web if (!key.endsWith("/")) key += "/"; var content = assetsTree.getFiles('assets/$key', source); - if (addPath) { - for(k=>e in content) - content[k] = '$key$e'; + for (k => e in content) { + if (noExtension) e = Path.withoutExtension(e); + content[k] = addPath ? '$key$e' : e; } return content; } diff --git a/source/funkin/backend/chart/BaseGameParser.hx b/source/funkin/backend/chart/BaseGameParser.hx deleted file mode 100644 index 02c89c81..00000000 --- a/source/funkin/backend/chart/BaseGameParser.hx +++ /dev/null @@ -1,176 +0,0 @@ -package funkin.backend.chart; - -import funkin.backend.chart.ChartData.ChartMetaData; - -// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex -class BaseGameParser { - public static function parseChart(data:Dynamic, metaData:Dynamic, result:ChartData) { - // TO DO - } - - public static function parseMeta(data:Dynamic, result:ChartMetaData) { - var data:SwagMetadata = data; - result.name = data.songName; - result.artist = data.artist; - - result.customValues = {}; - result.customValues.timeChanges = data.timeChanges; - var firstTimeChange:SwagTimeChange = data.timeChanges[0]; - result.bpm = firstTimeChange.bpm; - result.beatsPerMeasure = firstTimeChange.b != null ? firstTimeChange.b : Flags.DEFAULT_BEATS_PER_MEASURE; - - result.difficulties = data.playData.difficulties.concat(data.playData.songVariations); - Reflect.deleteField(data.playData, "difficulties"); - - for (field in Reflect.fields(data.playData)) - Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field)); - } - - public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata { - var addVars:Dynamic = meta.customValues; - var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE; - var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT}; - var defTimeCh:Array<SwagTimeChange>; - - if (addVars.timeChanges != null && addVars.timeChanges.length > 0) { - defTimeCh = addVars.timeChanges; - defTimeCh[0].bpm = meta.bpm; - } - else defTimeCh = [{bpm: meta.bpm, t: -1}]; - - if (chart != null) { - defStage = chart.stage; - - var done:Array<Bool> = [false, false, false]; - for (strumLine in chart.strumLines) switch (strumLine.type) { - case OPPONENT: - if (!done[0]) { - done[0] = true; - defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0]; - } - case PLAYER: - if (!done[1]) { - done[1] = true; - defChars.player = strumLine.characters.getDefault([defChars.player])[0]; - } - case ADDITIONAL: - if (!done[3]) { - done[3] = true; - defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0]; - } - } - } - - var result:SwagMetadata = { - songName: meta.name, - timeFormat: MILLISECONDS, - artist: meta.artist, - timeChanges: defTimeCh, - looped: false, - generatedBy: 'Basegame Chart Importer (Codename Engine)', - version: Flags.BASEGAME_SONG_METADATA_VERSION, - playData: { - stage: defStage, - characters: defChars, - songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [], - difficulties: meta.difficulties, - noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.BASEGAME_DEFAULT_NOTE_STYLE, - album: addVars.album != null ? addVars.album : Flags.BASEGAME_DEFAULT_ALBUM_ID, - previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.BASEGAME_DEFAULT_PREVIEW_START, - previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.BASEGAME_DEFAULT_PREVIEW_END - }, - }; - - return result; - return null; - } - - public static function encodeChart(chart:ChartData):NewSwagSong { - // TO DO - return null; - } -} - -// METADATA STRUCTURES -typedef SwagMetadata = -{ - var timeFormat:SwagTimeFormat; - var artist:String; - var songName:String; - var playData:SwagPlayData; - var timeChanges:Array<SwagTimeChange>; - var generatedBy:String; - var looped:Bool; - var version:String; - - var ?divisions:Int; - var ?offsets:SwagSongOffsets; -} - -enum abstract SwagTimeFormat(String) from String to String -{ - var TICKS = 'ticks'; - var FLOAT = 'float'; - var MILLISECONDS = 'ms'; -} - -typedef SwagTimeChange = -{ - var ?d:Int; // Time Signature Den - var ?n:Int; // Time Signature Num - var t:Int; // Time Stamp - var ?b:Int; // Beat Time - var ?bt:Array<Int>; // Beat Tuplets - var bpm:Float; -} - -typedef SwagPlayData = -{ - var album:String; - var previewStart:Float; - var previewEnd:Float; - var stage:String; - var characters:SwagCharactersList; - var songVariations:Array<String>; - var difficulties:Array<String>; - var noteStyle:String; -} - -typedef SwagCharactersList = -{ - var player:String; - var girlfriend:String; - var opponent:String; -} - -typedef SwagSongOffsets = -{ - var ?instrumental:Float; - var ?altInstrumentals:Dynamic; - var ?vocals:Dynamic; -} - -// CHART STRUCTURE -typedef NewSwagSong = -{ - var version:String; - var scrollSpeed:Dynamic; // Map<String, Float> - var events:Array<SwagEvent>; - var notes:Dynamic; // Map<String, Array<SwagNote>> - var generatedBy:String; -} - -typedef SwagEvent = -{ - var t:Float; // Time - var e:String; // Event Kind - var v:Dynamic; // Value (Map<String, Dynamic>) -} - -typedef SwagNote = -{ - var t:Float; // Time - var d:Int; // Data - var l:Float; // Length - var k:String; // Kind -} \ No newline at end of file diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx index 842702b0..1adcb9a4 100644 --- a/source/funkin/backend/chart/Chart.hx +++ b/source/funkin/backend/chart/Chart.hx @@ -45,6 +45,8 @@ enum abstract ChartFormat(Int) { } class Chart { + public static final version:String = "1.6.0"; + public static function cleanSongData(data:Dynamic):Dynamic { if (Reflect.hasField(data, "song")) { var field:Dynamic = Reflect.field(data, "song"); @@ -70,43 +72,40 @@ class Chart { } public static function loadEventsJson(songName:String) { - var path = Paths.file('songs/${songName.toLowerCase()}/events.json'); + var path = Paths.file('songs/${songName}/events.json'); var data:Array<ChartEvent> = null; if (Assets.exists(path)) { try { data = Json.parse(Assets.getText(path)).events; - } catch(e) { - Logs.error('Failed to load song event data for ${songName} ($path): ${Std.string(e)}'); - } + for (event in data) event.global = true; + } catch(e) Logs.trace('Failed to load song event data for ${songName} ($path): ${Std.string(e)}', ERROR); } return data; } - public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true) { + public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true):ChartMetaData { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; - var songNameLower = songName.toLowerCase(); - var metaPath = Paths.file('songs/${songNameLower}/meta.json'); - var metaDiffPath = Paths.file('songs/${songNameLower}/meta-${difficulty.toLowerCase()}.json'); + var metaPath = Paths.file('songs/${songName}/meta.json'); + var metaDiffPath = Paths.file('songs/${songName}/meta-${difficulty}.json'); var data:ChartMetaData = null; var fromMods:Bool = fromMods; - for(path in [metaDiffPath, metaPath]) { - if (Assets.exists(path)) { - fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS); - try { - data = Json.parse(Assets.getText(path)); - } catch(e) { - Logs.error('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}'); - } - if (data != null) break; - } + for (path in [metaDiffPath, metaPath]) if (Assets.exists(path)) { + fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS); + try { + var tempData = Json.parse(Assets.getText(path)); + tempData.color = CoolUtil.getColorFromDynamic(tempData.color).getDefault(Flags.DEFAULT_COLOR); + data = tempData; + } catch(e) Logs.trace('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}', ERROR); + if (data != null) break; } - if (data == null) - data = { - name: songName - }; + if (data == null) data = { + name: songName, + bpm: 100 + }; + data.setFieldDefault("name", songName); data.setFieldDefault("bpm", Flags.DEFAULT_BPM); data.setFieldDefault("beatsPerMeasure", Flags.DEFAULT_BEATS_PER_MEASURE); @@ -117,24 +116,19 @@ class Chart { data.setFieldDefault("coopAllowed", Flags.DEFAULT_COOP_ALLOWED); data.setFieldDefault("opponentModeAllowed", Flags.DEFAULT_OPPONENT_MODE_ALLOWED); data.setFieldDefault("displayName", data.name); - data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(Flags.DEFAULT_COLOR)); if (data.difficulties.length <= 0) { - data.difficulties = [for(f in Paths.getFolderContent('songs/${songNameLower}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)]; + data.difficulties = [for(f in Paths.getFolderContent('songs/${songName}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f.toUpperCase()) == "JSON") Path.withoutExtension(f)]; + var tempDiffs = []; if (data.difficulties.length == 3) { - var hasHard = false, hasNormal = false, hasEasy = false; for(d in data.difficulties) { - switch(d) { - case "EASY": hasEasy = true; - case "NORMAL": hasNormal = true; - case "HARD": hasHard = true; + switch(d.toLowerCase()) { + case "easy": tempDiffs.insert(0, d); + case "normal": tempDiffs.insert(1, d); + case "hard": tempDiffs.insert(2, d); } } - if (hasHard && hasNormal && hasEasy) { - data.difficulties[0] = "EASY"; - data.difficulties[1] = "NORMAL"; - data.difficulties[2] = "HARD"; - } + data.difficulties = tempDiffs; } } @@ -164,11 +158,9 @@ class Chart { valid = false; } var data:Dynamic = null; - try { - if (valid) - data = Json.parse(Assets.getText(chartPath)); - } catch(e) { - Logs.error('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}'); + if (valid) { + try data = Json.parse(Assets.getText(chartPath)) + catch(e) Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED); } /** @@ -177,7 +169,7 @@ class Chart { #if REGION if (data != null) switch (detectChartFormat(data)) { case CODENAME: - // backward compat on events since its caused problems + // backward compat on events since it caused problems var eventTypesToString:Map<Int, String> = [ -1 => "HScript Call", 0 => "Unknown", @@ -195,7 +187,7 @@ class Chart { base = data; case PSYCH_NEW: PsychParser.parse(data, base); - case VSLICE: // TODO + case VSLICE: Logs.trace("Couldn't parse V-Slice chart because it's not supported on runtime, it MUST be imported and converted in the new song editor window.", ERROR, RED); case LEGACY: FNFLegacyParser.parse(data, base); } #end @@ -220,6 +212,14 @@ class Chart { base.events = base.events.concat(extraEvents); #end + /** + * Set defaults on strum lines + */ + for(strumLine in base.strumLines) { + if(strumLine.keyCount == null) + strumLine.keyCount = 4; + } + return base; } @@ -229,8 +229,7 @@ class Chart { return 0; default: var index = chart.noteTypes.indexOf(noteTypeName); - if (index > -1) - return index+1; + if (index > -1) return index + 1; chart.noteTypes.push(noteTypeName); return chart.noteTypes.length; } @@ -249,9 +248,10 @@ class Chart { if (saveSettings == null) saveSettings = {}; if (saveSettings.saveMetaInChart == null) saveSettings.saveMetaInChart = true; - if (saveSettings.saveEventsInChart == null) saveSettings.saveEventsInChart = true; + if (saveSettings.saveLocalEvents == null) saveSettings.saveLocalEvents = true; + if (saveSettings.saveGlobalEvents == null) saveSettings.saveGlobalEvents = false; - var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveEventsInChart); + var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveLocalEvents, saveSettings.saveGlobalEvents); var meta = filteredChart.meta; #if sys @@ -265,39 +265,56 @@ class Chart { CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null)); - // idk how null reacts to it so better be sure if (saveSettings.overrideExistingMeta == true || !FileSystem.exists(metaPath)) - CoolUtil.safeSaveFile(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null)); + CoolUtil.safeSaveFile(metaPath, makeMetaSaveable(meta)); #end return filteredChart; } - public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Null<Bool>, ?saveEventsInChart:Null<Bool>):ChartData { + public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Bool, ?saveLocalEvents:Bool, ?saveGlobalEvents:Bool):ChartData { var data = Reflect.copy(chart); // make a copy of the chart to leave the OG intact if (saveMetaInChart != true) { data.meta = null; } else { data.meta = Reflect.copy(chart.meta); // also make a copy of the metadata to leave the OG intact. - if(data.meta != null && Reflect.hasField(data.meta, "parsedColor")) Reflect.deleteField(data.meta, "parsedColor"); + if (data.meta != null && Reflect.hasField(data.meta, "parsedColor")) Reflect.deleteField(data.meta, "parsedColor"); + } + + // in this part abt the events, i gotta account that these booleans can be null - Nex + if (saveLocalEvents != true && saveGlobalEvents != true) data.events = null; + else { + data.events = []; + for (event in chart.events) if ((saveLocalEvents == true && event.global != true) || (saveGlobalEvents == true && event.global == true)) { + var copy = Reflect.copy(event); + if (saveLocalEvents == true ? event.global != true : event.global == true) Reflect.deleteField(copy, "global"); // should NOT delete the field when saving with the local events and the event should have been global - Nex + data.events.push(copy); + } + if (data.events.length == 0) data.events = null; } - data.events = saveEventsInChart != true ? null : Reflect.copy(chart.events); // same here once again data.fromMods = null; var sortedData:Dynamic = {}; - for(f in Reflect.fields(data)) { + for (f in Reflect.fields(data)) { var v = Reflect.field(data, f); if (v != null) Reflect.setField(sortedData, f, v); } return sortedData; } + + public static inline function makeMetaSaveable(meta:ChartMetaData, prettyPrint:Bool = true):String { + var data:Dynamic = Reflect.copy(meta); + if (data.color != null) data.color = new FlxColor(data.color).toWebString(); // dont even ask me - Nex + return Json.stringify(data, null, prettyPrint ? Flags.JSON_PRETTY_PRINT : null); + } } typedef ChartSaveSettings = { var ?overrideExistingMeta:Bool; var ?saveMetaInChart:Bool; - var ?saveEventsInChart:Bool; + var ?saveLocalEvents:Bool; + var ?saveGlobalEvents:Bool; var ?prettyPrint:Bool; var ?folder:String; } \ No newline at end of file diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx index 25e98597..ba1b7ae7 100644 --- a/source/funkin/backend/chart/ChartData.hx +++ b/source/funkin/backend/chart/ChartData.hx @@ -10,7 +10,9 @@ typedef ChartData = { public var stage:String; public var scrollSpeed:Float; public var noteTypes:Array<String>; + public var ?bookmarks:Array<ChartBookmark>; + public var ?chartVersion:String; public var ?fromMods:Bool; } @@ -21,16 +23,12 @@ typedef ChartMetaData = { public var ?beatsPerMeasure:Float; public var ?stepsPerBeat:Int; public var ?icon:String; - public var ?color:Dynamic; + public var ?color:FlxColor; public var ?difficulties:Array<String>; public var ?coopAllowed:Bool; public var ?opponentModeAllowed:Bool; public var ?customValues:Dynamic; public var ?needsVoices:Bool; - public var ?artist:String; - - // NOT TO BE EXPORTED - public var ?parsedColor:FlxColor; } typedef ChartStrumLine = { @@ -41,8 +39,10 @@ typedef ChartStrumLine = { var ?visible:Null<Bool>; var ?strumPos:Array<Float>; var ?strumScale:Float; + var ?strumSpacing:Float; var ?scrollSpeed:Float; var ?vocalsSuffix:String; + var ?keyCount:Int; // default=4 var ?strumLinePos:Float; // Backwards compatibility } @@ -54,10 +54,17 @@ typedef ChartNote = { var sLen:Float; // sustain length of the note (ms) } +typedef ChartBookmark = { + var time:Float; + var name:String; + var color:String; +} + typedef ChartEvent = { var name:String; var time:Float; var params:Array<Dynamic>; + var ?global:Bool; // If its from a global event file like events.json; this field might be saved rarely in the chart json directly but modders can mess with this for messing with the saving funcs too - Nex } enum abstract ChartStrumLineType(Int) from Int to Int { diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index fc3a3de3..9ef2e4f2 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -10,20 +10,75 @@ import openfl.Assets; using StringTools; class EventsData { - public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; + public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Camera Position", "Add Camera Zoom", "Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Continuous BPM Change", "Time Signature Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; public static var defaultEventsParams:Map<String, Array<EventParamInfo>> = [ "HScript Call" => [ {name: "Function Name", type: TString, defValue: "myFunc"}, {name: "Function Parameters (String split with commas)", type: TString, defValue: ""} ], - "Camera Movement" => [{name: "Camera Target", type: TStrumLine, defValue: 0}], + "Camera Movement" => [ + {name: "Camera Target", type: TStrumLine, defValue: 0}, + {name: "Tween Movement?", type: TBool, defValue: true, saveIfDefault: false}, + {name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4, saveIfDefault: false}, + { // since its the most used event even by default, we'll set saveIfDefault false to avoid filling up with unnecessary parameters the files - Nex + name: "Tween Ease (ex: circ, quad, cube)", + type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']), + defValue: "CLASSIC", + saveIfDefault: false + }, + { + name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)", + type: TDropDown(['In', 'Out', 'InOut']), + defValue: "In", + saveIfDefault: false + } + ], + "Camera Position" => [ + {name: "X", type: TFloat(null, null, 10, 3), defValue: 0}, + {name: "Y", type: TFloat(null, null, 10, 3), defValue: 0}, + {name: "Tween Movement?", type: TBool, defValue: true, saveIfDefault: false}, + {name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4, saveIfDefault: false}, + { + name: "Tween Ease (ex: circ, quad, cube)", + type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']), + defValue: "CLASSIC", + saveIfDefault: false + }, + { + name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)", + type: TDropDown(['In', 'Out', 'InOut']), + defValue: "In", + saveIfDefault: false + }, + {name: "Is Offset?", type: TBool, defValue: false, saveIfDefault: false} + ], "Add Camera Zoom" => [ {name: "Amount", type: TFloat(-10, 10, 0.01, 2), defValue: 0.05}, {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"} ], + "Camera Zoom" => [ + {name: "Tween Zoom?", type: TBool, defValue: true}, + {name: "New Zoom", type: TFloat(-10, 10, 0.01, 2), defValue: 1}, + {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"}, + {name: "Tween Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}, + { + name: "Tween Ease (ex: circ, quad, cube)", + type: TDropDown(['linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']), + defValue: "linear" + }, + { + name: "Tween Type (excluded if linear, ex: InOut)", + type: TDropDown(['In', 'Out', 'InOut']), + defValue: "In" + }, + {name: "Mode", type: TDropDown(['direct', 'stage']), defValue: "direct"}, + {name: "Multiplicative?", type: TBool, defValue: true} + ], "Camera Modulo Change" => [ - {name: "Modulo Interval (Beats)", type: TInt(1, 9999999, 1), defValue: 4}, - {name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1} + {name: "Modulo Interval", type: TInt(1, 9999999, 1), defValue: 4}, + {name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1}, + {name: "Every Beat Type", type: TDropDown(['BEAT', 'MEASURE', 'STEP']), defValue: 'BEAT'}, + {name: "Beat Offset", type: TFloat(-10, 10, 0.25, 2), defValue: 0} ], "Camera Flash" => [ {name: "Reversed?", type: TBool, defValue: false}, @@ -31,7 +86,9 @@ class EventsData { {name: "Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}, {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camHUD"} ], - "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, null, 0.001, 3), defValue: 100}], + "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}], + "Continuous BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}, {name: "Time (steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}], + "Time Signature Change" => [{name: "Target Numerator", type: TFloat(1), defValue: 4}, {name: "Target Denominator", type: TFloat(1), defValue: 4}, {name: "Denominator is Steps Per Beat", type: TBool, defValue: false}], "Scroll Speed Change" => [ {name: "Tween Speed?", type: TBool, defValue: true}, {name: "New Speed", type: TFloat(0.01, 99, 0.01, 2), defValue: 1.}, @@ -42,13 +99,23 @@ class EventsData { defValue: "linear" }, { - name: "Tween Type (ex: InOut)", + name: "Tween Type (excluded if linear, ex: InOut)", type: TDropDown(['In', 'Out', 'InOut']), defValue: "In" - } + }, + {name: "Multiplicative?", type: TBool, defValue: false} ], "Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}], - "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}], + "Play Animation" => [ + {name: "Character", type: TStrumLine, defValue: 0}, + {name: "Animation", type: TString, defValue: "animation"}, + {name: "Is forced?", type: TBool, defValue: true}, + { + name: "Animation Context", + type: TDropDown(["NONE", "SING", "DANCE", "MISS", "LOCK"]), + defValue: "NONE" + } + ], ]; public static var eventsList:Array<String> = defaultEventsList.copy(); @@ -135,6 +202,7 @@ typedef EventParamInfo = { var name:String; var type:EventParamType; var defValue:Dynamic; + var ?saveIfDefault:Bool; } enum EventParamType { diff --git a/source/funkin/backend/chart/FNFLegacyParser.hx b/source/funkin/backend/chart/FNFLegacyParser.hx index 2ba3e809..84e96aa2 100644 --- a/source/funkin/backend/chart/FNFLegacyParser.hx +++ b/source/funkin/backend/chart/FNFLegacyParser.hx @@ -55,6 +55,18 @@ class FNFLegacyParser { continue; // Yoshi Engine charts crash fix } + var newBeatsPerMeasure:Float = section.sectionBeats != null ? section.sectionBeats : data.beatsPerMeasure.getDefault(4); // Default to 4 if sectionBeats is null or undefined (oops :3) + + if (newBeatsPerMeasure != beatsPerMeasure) { + beatsPerMeasure = newBeatsPerMeasure; + + result.events.push({ + time: curTime, + name: "Time Signature Change", + params: [newBeatsPerMeasure, 4] + }); + } + if (camFocusedBF != (camFocusedBF = section.mustHitSection)) { result.events.push({ time: curTime, @@ -178,7 +190,8 @@ class FNFLegacyParser { mustHitSection: notes[section-1] != null ? notes[section-1].mustHitSection : false, bpm: notes[section-1] != null ? notes[section-1].bpm : chart.meta.bpm, changeBPM: false, - altAnim: notes[section-1] != null ? notes[section-1].altAnim : false + altAnim: notes[section-1] != null ? notes[section-1].altAnim : false, + sectionBeats: notes[section-1] != null ? notes[section-1].sectionBeats : chart.meta.beatsPerMeasure.getDefault(4) }; var sectionEndTime:Float = Conductor.getTimeForStep(Conductor.getMeasureLength() * (section+1)); @@ -192,6 +205,8 @@ class FNFLegacyParser { case "BPM Change": baseSection.changeBPM = true; baseSection.bpm = event.params[0]; + case "Time Signature Change": + baseSection.sectionBeats = event.params[0]; } } notes[section] = baseSection; diff --git a/source/funkin/backend/chart/VSliceParser.hx b/source/funkin/backend/chart/VSliceParser.hx new file mode 100644 index 00000000..b58ab9c7 --- /dev/null +++ b/source/funkin/backend/chart/VSliceParser.hx @@ -0,0 +1,362 @@ +package funkin.backend.chart; + +import funkin.backend.chart.ChartData.ChartMetaData; + +typedef ChartDataWithInfo = { + var diffName:String; + var chart:ChartData; +} + +// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex +class VSliceParser { + public static function parse(metaData:Dynamic, chartData:Dynamic, resultMeta:ChartMetaData, resultCharts:Array<ChartDataWithInfo>, ?songID:String) { + var metaData:SwagMetadata = metaData; + var chartData:NewSwagSong = chartData; + + parseMeta(metaData, resultMeta, songID); + + for (diff in Reflect.fields(chartData.notes)) + { + var base:ChartData = { + strumLines: [], + noteTypes: [], + events: [], + meta: {name: null}, + scrollSpeed: Reflect.field(chartData.scrollSpeed, diff), + stage: Flags.DEFAULT_STAGE, + codenameChart: true + }; + parseChart(Reflect.field(chartData.notes, diff), metaData, chartData.events, base); + resultCharts.push({diffName: diff, chart: base}); + } + } + + public static function parseChart(data:Dynamic, metaData:Dynamic, events:Dynamic, result:ChartData) { + // vslice chart parsing + var data:Array<SwagNote> = data; + var metadata:SwagMetadata = metaData; + var events:Array<SwagEvent> = events; + + result.stage = metadata.playData.stage; + + var p2isGF:Bool = false; + result.strumLines.push({ + characters: [metadata.playData.characters.opponent], + type: 0, + position: (p2isGF = metadata.playData.characters.opponent.startsWith("gf")) ? "girlfriend" : "dad", + notes: [], + vocalsSuffix: "-Opponent" + }); + result.strumLines.push({ + characters: [metadata.playData.characters.player], + type: 1, + position: "boyfriend", + notes: [], + vocalsSuffix: "-Player" + }); + var gfName = metadata.playData.characters.girlfriend; + if (!p2isGF && gfName != "none") { + result.strumLines.push({ + characters: [gfName], + type: 2, + position: "girlfriend", + notes: [], + visible: false, + }); + } + + var timeChanges = metadata.timeChanges; + result.meta.bpm = timeChanges[0].bpm; + result.meta.needsVoices = true; + + for (note in data) + { + var daNoteType:Null<Int> = null; + if (note.k != null) + daNoteType = Chart.addNoteType(result, note.k == "alt" ? "Alt Anim Note" : note.k); // they hardcoded "alt" for converting old charts BUT THEN WHY THE HELL WOULD YOU CALL "MOM" THE NOTE KIND IN WEEK5 GRAHHH - Nex + + var daNoteData:Int = Std.int(note.d % 8); + var isMustHit:Bool = Math.floor(daNoteData / 4) == 0; + + result.strumLines[isMustHit ? 1 : 0].notes.push({ + time: note.t, + id: daNoteData % 4, + type: daNoteType, + sLen: note.l + }); + } + + var curBPM = result.meta.bpm; + for (i in 1...timeChanges.length) // starting from 1 on purpose - Nex + { + var curChange = timeChanges[i]; + if (curBPM != curChange.bpm) + { + curBPM = curChange.bpm; + result.events.push({ + time: curChange.t, + name: "BPM Change", + params: [curBPM] + }); + } + } + + for (event in events) + { + var values = event.v; + switch (event.e) + { + case "FocusCamera": + var isPosOnly = false; + var arr:Array<Dynamic> = [switch(values.char) { + // more cases here in the future if they add em? (i hope not) - Nex + case 0: 1; + case 1: 0; + case -1: + isPosOnly = true; + -1; + default: 2; + }]; + if (values.ease != "CLASSIC" && values.ease != null) { + if (values.ease == "INSTANT") arr = arr.concat([false]); + else { + var cneEase = parseEase(values.ease); + arr = arr.concat([true, values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1]]); + } + } + if (isPosOnly || ((values.x != null && values.x != 0) || (values.y != null && values.y != 0))) { + var useNull = arr.length <= 2; + result.events.push({ + time: event.t, + name: "Camera Position", + params: [values.x, values.y, useNull ? null : arr[1], useNull ? null : arr[2], useNull ? null : arr[3], useNull ? null : arr[4], true] + }); + } + if (!isPosOnly) result.events.push({ + time: event.t, + name: "Camera Movement", + params: arr + }); + case "PlayAnimation": + result.events.push({ + time: event.t, + name: "Play Animation", + params: [switch(values.target) { + case 'boyfriend' | 'bf' | 'player': 1; + case 'dad' | 'opponent': 0; + default /*case 'girlfriend' | 'gf'*/: 2; // usually the default should be the stage prop but we dont have that sooo - Nex + }, values.anim, values.force == null ? false : values.force] + }); + case "ScrollSpeed": + var cneEase = values.ease == null || values.ease == "INSTANT" ? ["linear", null] : parseEase(values.ease); + result.events.push({ + time: event.t, + name: "Scroll Speed Change", // we dont support the strumline value and also i will put the whole ease name into a single parameter since it works anyways - Nex + params: [values.ease != "INSTANT", values.scroll == null ? 1 : values.scroll, values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1], values.absolute != true] + }); + case "SetCameraBop": + result.events.push({ + time: event.t, + name: "Camera Modulo Change", + params: [values.rate == null ? 4 : values.rate, values.intensity == null ? 1 : values.intensity] + }); + case "ZoomCamera": + var cneEase = values.ease == null || values.ease == "INSTANT" ? ["linear", null] : parseEase(values.ease); + result.events.push({ + time: event.t, + name: "Camera Zoom", // we dont support the direct mode since welp, its kind of useless here - Nex + params: [values.ease != "INSTANT", values.zoom == null ? 1 : values.zoom, "camGame", values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1], values.mode, false] + }); + } + } + } + + public static function parseMeta(data:Dynamic, result:ChartMetaData, ?songID:String) { + var data:SwagMetadata = data; + var songName = data.songName; + var songID:String = songID == null ? songName.toLowerCase().replace(" ", "-") : songID; + var firstTimeChange:SwagTimeChange = data.timeChanges[0]; + + result.name = songID; + result.bpm = firstTimeChange.bpm.getDefault(Flags.DEFAULT_BPM); + result.beatsPerMeasure = firstTimeChange.n.getDefault(Flags.DEFAULT_BEATS_PER_MEASURE); + result.stepsPerBeat = firstTimeChange.d.getDefault(Flags.DEFAULT_STEPS_PER_BEAT); + result.displayName = songName; + result.icon = Flags.DEFAULT_HEALTH_ICON; + result.color = 0xFFFFFF; + result.opponentModeAllowed = true; + result.coopAllowed = true; + result.difficulties = data.playData.difficulties.concat(data.playData.songVariations.getDefault([])); + + if (result.customValues == null) result.customValues = {}; + result.customValues.artist = data.artist; + if (data.charter != null) result.customValues.charter = data.charter; + for (field in Reflect.fields(data.playData)) if (field != "difficulties" && field != "songVariations" && field != "characters" && field != "stage") + Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field)); + } + + public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata { + var addVars:Dynamic = meta.customValues; + var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE; + var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT, playerVocals: [], opponentVocals: [], instrumental: '', altInstrumentals: []}; + var defTimeCh:Array<SwagTimeChange>; + + if (addVars.timeChanges != null && addVars.timeChanges.length > 0) { + defTimeCh = addVars.timeChanges; + defTimeCh[0].bpm = meta.bpm; + } + else defTimeCh = [{bpm: meta.bpm, t: -1}]; + + if (chart != null) { + defStage = chart.stage; + + var done:Array<Bool> = [false, false, false]; + for (strumLine in chart.strumLines) switch (strumLine.type) { + case OPPONENT: + if (!done[0]) { + done[0] = true; + defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0]; + } + case PLAYER: + if (!done[1]) { + done[1] = true; + defChars.player = strumLine.characters.getDefault([defChars.player])[0]; + } + case ADDITIONAL: + if (!done[3]) { + done[3] = true; + defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0]; + } + } + } + + var result:SwagMetadata = { + songName: meta.name, + timeFormat: MILLISECONDS, + artist: "", + timeChanges: defTimeCh, + looped: false, + generatedBy: 'V-Slice Chart Importer (Codename Engine)', + version: Flags.VSLICE_SONG_METADATA_VERSION, + playData: { + stage: defStage, + characters: defChars, + songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [], + difficulties: meta.difficulties, + noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.VSLICE_DEFAULT_NOTE_STYLE, + album: addVars.album != null ? addVars.album : Flags.VSLICE_DEFAULT_ALBUM_ID, + previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.VSLICE_DEFAULT_PREVIEW_START, + previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.VSLICE_DEFAULT_PREVIEW_END + }, + }; + + return result; + } + + public static function encodeChart(chart:ChartData):NewSwagSong { + // TO DO + return null; + } + + public static function parseEase(vsliceEase:String):Array<String> { + for (key in ['InOut', 'In', 'Out']) if (vsliceEase.endsWith(key)) return [vsliceEase.substr(0, vsliceEase.length - key.length), key]; + return [vsliceEase]; + } +} + +// METADATA STRUCTURES +typedef SwagMetadata = +{ + var version:String; + var songName:String; + var artist:String; + var ?charter:String; + var ?divisions:Int; + var ?looped:Bool; + var ?offsets:SwagSongOffsets; + var playData:SwagPlayData; + var generatedBy:String; + var ?timeFormat:SwagTimeFormat; + var timeChanges:Array<SwagTimeChange>; +} + +enum abstract SwagTimeFormat(String) from String to String +{ + var TICKS = 'ticks'; + var FLOAT = 'float'; + var MILLISECONDS = 'ms'; +} + +typedef SwagTimeChange = +{ + var t:Int; // Time Stamp + var ?b:Int; // Beat Time + var bpm:Float; + var ?n:Int; // Time Signature Num + var ?d:Int; // Time Signature Den + var ?bt:Array<Int>; // Beat Tuplets +} + +typedef SwagSongOffsets = +{ + var ?instrumental:Float; + var ?altInstrumentals:Dynamic; + var ?vocals:Dynamic; + var ?altVocals:Dynamic; +} + +typedef SwagPlayData = +{ + var ?songVariations:Array<String>; + var difficulties:Array<String>; + var characters:SwagCharactersList; + var stage:String; + var noteStyle:String; + var ?ratings:Dynamic; + var ?album:String; + var ?previewStart:Int; + var ?previewEnd:Int; +} + +typedef SwagCharactersList = +{ + var ?player:String; + var ?girlfriend:String; + var ?opponent:String; + var ?instrumental:String; + var ?altInstrumentals:Array<String>; + var ?opponentVocals:Array<String>; + var ?playerVocals:Array<String>; +} + +// CHART STRUCTURE +typedef NewSwagSong = +{ + var version:String; + var scrollSpeed:Dynamic; // Map<String, Float> + var events:Array<SwagEvent>; + var notes:Dynamic; // Map<String, Array<SwagNote>> + var generatedBy:String; +} + +typedef SwagEvent = +{ + var t:Float; // Time + var e:String; // Event Kind + var ?v:Dynamic; // Value (Map<String, Dynamic>) +} + +typedef SwagNote = +{ + var t:Float; // Time + var d:Int; // Data + var ?l:Float; // Length + var ?k:String; // Kind + var ?p:Array<SwagNoteParamsData>; // Params +} + +typedef SwagNoteParamsData = +{ + var n:String; // Name + var v:Dynamic; // Value +} \ No newline at end of file diff --git a/source/funkin/backend/scripting/EventManager.hx b/source/funkin/backend/scripting/EventManager.hx index 34c1a473..162d72fa 100644 --- a/source/funkin/backend/scripting/EventManager.hx +++ b/source/funkin/backend/scripting/EventManager.hx @@ -3,7 +3,7 @@ package funkin.backend.scripting; import flixel.FlxState; import funkin.backend.scripting.events.*; -class EventManager { +final class EventManager { // map doesn't work for that public static var eventValues:Array<CancellableEvent> = []; public static var eventKeys:Array<Class<CancellableEvent>> = []; diff --git a/source/funkin/backend/scripting/GlobalScript.hx b/source/funkin/backend/scripting/GlobalScript.hx index 887aea76..a604c457 100644 --- a/source/funkin/backend/scripting/GlobalScript.hx +++ b/source/funkin/backend/scripting/GlobalScript.hx @@ -4,6 +4,8 @@ import flixel.FlxState; import funkin.backend.assets.ModsFolder; import funkin.backend.scripting.events.CancellableEvent; import funkin.backend.system.Conductor; +import funkin.options.PlayerSettings; + #if GLOBAL_SCRIPT /** * Class for THE Global Script, aka script that runs in the background at all times. @@ -12,6 +14,8 @@ class GlobalScript { public static var scripts:ScriptPack; private static var initialized:Bool = false; + private static var reloading:Bool = false; + private static var _lastAllow_Reload:Bool = false; public static function init() { if(initialized) return; @@ -46,17 +50,13 @@ class GlobalScript { }); FlxG.signals.postUpdate.add(function() { call("postUpdate", [FlxG.elapsed]); - if (FlxG.keys.justPressed.F5) { - if (scripts.scripts.length > 0) { - Logs.trace('Reloading global script...', WARNING, YELLOW); - scripts.reload(); - Logs.trace('Global script successfully reloaded.', WARNING, GREEN); - } else { - Logs.trace('Loading global script...', WARNING, YELLOW); - onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end); - } + + if (reloading) { + reloading = false; + MusicBeatState.ALLOW_DEV_RELOAD = _lastAllow_Reload; } - if (FlxG.keys.justPressed.F2) + + if (PlayerSettings.solo.controls.DEV_CONSOLE) NativeAPI.allocConsole(); }); FlxG.signals.preDraw.add(function() { @@ -74,13 +74,26 @@ class GlobalScript { FlxG.signals.preStateSwitch.add(function() { call("preStateSwitch", []); }); + FlxG.signals.preUpdate.add(function() { call("preUpdate", [FlxG.elapsed]); call("update", [FlxG.elapsed]); - }); - // Commented this out, because it might be the cause why global runs twice - Neo - //onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end); + if (FlxG.keys.pressed.SHIFT) { + // If we want, we could just make reseting GlobalScript it's own keybind, but for now this works. + if (PlayerSettings.solo.controls.DEV_RELOAD) { + reloading = true; + Logs.trace("Reloading Global Scripts...", INFO, YELLOW); + + // yeah its a bit messy, sorry. This just prevents actually reloading the actual state. + _lastAllow_Reload = MusicBeatState.ALLOW_DEV_RELOAD; + MusicBeatState.ALLOW_DEV_RELOAD = false; + + // Would be better to just re-initalize GlobalScript so there aren't any lose ends. + onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end); + } + } + }); } public static function event<T:CancellableEvent>(name:String, event:T):T { @@ -93,6 +106,7 @@ class GlobalScript { if (scripts != null) scripts.call(name, args); } + public static function onModSwitch(newMod:String) { call("destroy"); scripts = FlxDestroyUtil.destroy(scripts); diff --git a/source/funkin/backend/scripting/HScript.hx b/source/funkin/backend/scripting/HScript.hx index a1fbac54..81ea4669 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -128,6 +128,10 @@ class HScript extends Script { interp.execute(expr); call("new", []); } + + #if GLOBAL_SCRIPT + funkin.backend.scripting.GlobalScript.call("onScriptSetup", [this, "hscript"]); + #end } public override function reload() { diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx index 5fe597b9..1e0db90d 100644 --- a/source/funkin/backend/scripting/Script.hx +++ b/source/funkin/backend/scripting/Script.hx @@ -300,7 +300,7 @@ class Script extends FlxBasic implements IFlxDestroyable { if(remappedNames.exists(fileName)) fileName = remappedNames.get(fileName); Logs.traceColored([ - Logs.logText('${fileName}: ', GREEN), + Logs.logText(fileName + ': ', GREEN), Logs.logText(Std.string(v)) ], TRACE); } diff --git a/source/funkin/backend/scripting/ScriptPack.hx b/source/funkin/backend/scripting/ScriptPack.hx index 4e9af4f7..dfd8e491 100644 --- a/source/funkin/backend/scripting/ScriptPack.hx +++ b/source/funkin/backend/scripting/ScriptPack.hx @@ -83,7 +83,7 @@ class ScriptPack extends Script { **/ public override function call(func:String, ?parameters:Array<Dynamic>):Dynamic { for(e in scripts) - if(e.active) + if (e.active) e.call(func, parameters); return null; } diff --git a/source/funkin/backend/scripting/events/gameplay/RatingUpdateEvent.hx b/source/funkin/backend/scripting/events/gameplay/RatingUpdateEvent.hx index 59a3305d..06a8a53a 100644 --- a/source/funkin/backend/scripting/events/gameplay/RatingUpdateEvent.hx +++ b/source/funkin/backend/scripting/events/gameplay/RatingUpdateEvent.hx @@ -2,12 +2,12 @@ package funkin.backend.scripting.events.gameplay; final class RatingUpdateEvent extends CancellableEvent { /** - New combo + New combo (may be null if no ratings were found) **/ - public var rating:ComboRating; + public var rating:Null<ComboRating>; /** Old combo (may be null) **/ - public var oldRating:ComboRating; + public var oldRating:Null<ComboRating>; } \ No newline at end of file diff --git a/source/funkin/backend/scripting/events/healthicon/HealthIconChangeEvent.hx b/source/funkin/backend/scripting/events/healthicon/HealthIconChangeEvent.hx index dbb4cb68..95ab6a8d 100644 --- a/source/funkin/backend/scripting/events/healthicon/HealthIconChangeEvent.hx +++ b/source/funkin/backend/scripting/events/healthicon/HealthIconChangeEvent.hx @@ -1,10 +1,12 @@ package funkin.backend.scripting.events.healthicon; +import flixel.util.typeLimit.OneOfTwo; + final class HealthIconChangeEvent extends CancellableEvent { /** - * Amount + * Animation State */ - public var amount:Int; + public var anim:OneOfTwo<String, Int>; /** * The health icon diff --git a/source/funkin/backend/scripting/events/menu/storymenu/WeekSelectEvent.hx b/source/funkin/backend/scripting/events/menu/storymenu/WeekSelectEvent.hx index c9afe956..d9431b66 100644 --- a/source/funkin/backend/scripting/events/menu/storymenu/WeekSelectEvent.hx +++ b/source/funkin/backend/scripting/events/menu/storymenu/WeekSelectEvent.hx @@ -1,6 +1,6 @@ package funkin.backend.scripting.events.menu.storymenu; -import funkin.menus.StoryMenuState.WeekData; +import funkin.backend.week.WeekData; final class WeekSelectEvent extends CancellableEvent { /** diff --git a/source/funkin/backend/scripting/events/note/NoteHitEvent.hx b/source/funkin/backend/scripting/events/note/NoteHitEvent.hx index 5614b75c..c9f8c4c9 100644 --- a/source/funkin/backend/scripting/events/note/NoteHitEvent.hx +++ b/source/funkin/backend/scripting/events/note/NoteHitEvent.hx @@ -1,6 +1,7 @@ package funkin.backend.scripting.events.note; import funkin.game.Character; +import funkin.game.HealthIcon; import funkin.game.Note; final class NoteHitEvent extends CancellableEvent { @@ -113,6 +114,10 @@ final class NoteHitEvent extends CancellableEvent { * Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one). */ public var forceAnim:Null<Bool> = true; + /** + * The attached healthIcon used distinction for icons amongst others + */ + public var healthIcon:HealthIcon; /** * Prevents the default sing animation from being played. diff --git a/source/funkin/backend/scripting/events/splash/SplashShowEvent.hx b/source/funkin/backend/scripting/events/splash/SplashShowEvent.hx index 6cdea0ee..1973f3c2 100644 --- a/source/funkin/backend/scripting/events/splash/SplashShowEvent.hx +++ b/source/funkin/backend/scripting/events/splash/SplashShowEvent.hx @@ -1,5 +1,6 @@ package funkin.backend.scripting.events.splash; +import funkin.game.Splash; import funkin.game.SplashGroup; import funkin.game.Strum; @@ -11,7 +12,7 @@ final class SplashShowEvent extends CancellableEvent { /** Splash that is shown **/ - public var splash:FunkinSprite; + public var splash:Splash; /** Strum that the splash is shown on **/ diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx index fb3f9201..281616b0 100644 --- a/source/funkin/backend/shaders/FunkinShader.hx +++ b/source/funkin/backend/shaders/FunkinShader.hx @@ -1,5 +1,6 @@ package funkin.backend.shaders; +import flixel.graphics.FlxGraphic; import flixel.system.FlxAssets.FlxShader; import flixel.util.FlxSignal.FlxTypedSignal; import haxe.Exception; @@ -555,34 +556,44 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { return val; } - var field:Dynamic = Reflect.field(data, name); - var cl:String = Type.getClassName(Type.getClass(field)); + var field = Reflect.field(data, name); + var cl = Type.getClassName(Type.getClass(field)); + var isNotNull = val != null; // cant do "field is ShaderInput" for some reason if (cl.startsWith("openfl.display.ShaderParameter")) { if (field.__length <= 1) { - if ((val is Array)) - return field.value = val; // that means we wait for a single number, instead of an array - if (field.__isInt && !(val is Int)) + if (field.__isInt && isNotNull && !(val is Int)) { throw new ShaderTypeException(name, Type.getClass(val), 'Int'); - else if (field.__isBool && !(val is Bool)) + return null; + } else + if (field.__isBool && isNotNull && !(val is Bool)) { throw new ShaderTypeException(name, Type.getClass(val), 'Bool'); - else if (field.__isFloat && !(val is Float)) + return null; + } else + if (field.__isFloat && isNotNull && !(val is Float)) { throw new ShaderTypeException(name, Type.getClass(val), 'Float'); - - return field.value = [val]; + return null; + } + return field.value = isNotNull ? [val] : null; } else { - if (!(val is Array)) + if (isNotNull && !(val is Array)) { throw new ShaderTypeException(name, Type.getClass(val), Array); - + return null; + } return field.value = val; } } else if (cl.startsWith("openfl.display.ShaderInput")) { // shader input!! - if (!(val is BitmapData)) { + var bitmap:BitmapData; + if (!isNotNull) bitmap = null; + else if (val is BitmapData) bitmap = val; + else if (val is FlxGraphic) bitmap = val.bitmap; + else { throw new ShaderTypeException(name, Type.getClass(val), BitmapData); + return null; } - field.input = cast val; + field.input = bitmap; } return val; diff --git a/source/funkin/backend/system/CommandLineHandler.hx b/source/funkin/backend/system/CommandLineHandler.hx index 1545108f..e6d74007 100644 --- a/source/funkin/backend/system/CommandLineHandler.hx +++ b/source/funkin/backend/system/CommandLineHandler.hx @@ -2,7 +2,7 @@ package funkin.backend.system; #if sys import sys.FileSystem; -class CommandLineHandler { +final class CommandLineHandler { public static function parseCommandLine(cmd:Array<String>) { var i:Int = 0; while(i < cmd.length) { diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index aaf47774..e1794ac4 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -4,56 +4,58 @@ import flixel.FlxState; import flixel.util.FlxSignal.FlxTypedSignal; import funkin.backend.chart.ChartData; import funkin.backend.system.interfaces.IBeatReceiver; +import funkin.editors.charter.Charter; -typedef BPMChangeEvent = -{ - var stepTime:Float; - var songTime:Float; - var bpm:Float; +enum BeatType { + BEAT; + MEASURE; + STEP; } -class Conductor +@:structInit +class BPMChangeEvent { + public var songTime:Float; + public var bpm:Float; + public var beatsPerMeasure:Float = 4; + public var stepsPerBeat:Int = 4; + + public var endSongTime:Float = 0; + public var endStepTime:Float = 0; + public var continuous:Bool = false; + + public var stepTime:Float; + public var beatTime:Float; + public var measureTime:Float; +} + +final class Conductor +{ + public static function getBeats(?every:BeatType, interval:Float, offset:Float = 0):Float { + final beat = switch(every) { + case MEASURE: curMeasureFloat; + case STEP: curStepFloat; + default: curBeatFloat; + } + if (interval <= 0) return beat - offset; + else return Math.floor((beat - offset) / interval) * interval; + } + /** * FlxSignals */ public static var onMeasureHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal(); public static var onBeatHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal(); public static var onStepHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal(); - public static var onBPMChange:FlxTypedSignal<Float->Void> = new FlxTypedSignal(); - - /** - * Current BPM - */ - public static var bpm:Float = 100; - - /** - * Current Crochet (time per beat), in milliseconds. - */ - public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds - - /** - * Current StepCrochet (time per step), in milliseconds. - */ - public static var stepCrochet:Float = crochet / 4; // steps in milliseconds - - /** - * Number of beats per measure (top number in time signature). Defaults to 4. - */ - public static var beatsPerMeasure:Float = 4; - - /** - * Number of steps per beat (bottom number in time signature). Defaults to 4. - */ - public static var stepsPerBeat:Int = 4; - + public static var onBPMChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); + public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); /** * Current position of the song, in milliseconds. */ public static var songPosition(get, default):Float; private static function get_songPosition() { - if (songOffset != Options.songOffset) trace(songOffset = Options.songOffset); + if (songOffset != Options.songOffset) songOffset = Options.songOffset; return songPosition - songOffset; } @@ -62,6 +64,88 @@ class Conductor */ public static var songOffset:Float = 0; + + /** + * Current bpmChangeMap index + */ + public static var curChangeIndex:Int = 0; + + /** + * Current bpmChangeMap + */ + public static var curChange(get, never):BPMChangeEvent; + private static var dummyChange:BPMChangeEvent = {bpm: 100, beatsPerMeasure: 4, stepsPerBeat: 4, songTime: 0, stepTime: 0, beatTime: 0, measureTime: 0}; + private static function get_curChange() + return bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]; + + /** + * Current BPM + */ + public static var bpm(get, never):Float; + private static function get_bpm() + return (curChangeIndex == 0 || bpmChangeMap.length == 0) ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex); + + /** + * Starting BPM + */ + public static var startingBPM(get, never):Float; + private static function get_startingBPM() + return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[0]).bpm; + + /** + * Current Crochet (time per beat), in milliseconds. + * It should be crotchet but ehhh, now it's there for backward compatibility. + */ + public static var crochet(get, never):Float; + private static function get_crochet() return 15000 * stepsPerBeat / bpm; + + /** + * Current StepCrochet (time per step), in milliseconds. + */ + public static var stepCrochet(get, never):Float; + private static function get_stepCrochet() return 15000 / bpm; + + /** + * Number of beats per mesure (top number in time signature). Defaults to 4. + */ + public static var beatsPerMeasure(get, never):Float; + private static function get_beatsPerMeasure() + return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).beatsPerMeasure; + + /** + * Number of steps per beat. Defaults to 4. + * Not a divisor number for time signature, it does the complete opposite. + * It's because CNE Conductor is based in sixteenth note instead of beat. + */ + public static var stepsPerBeat(get, never):Int; + private static function get_stepsPerBeat() + return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).stepsPerBeat; + + /** + * How much value notes to divide for beat (bottom or divisor number in time signature). + * Only for a convinient way to access divisor instead of multiply by steps per beat. + */ + public static var denominator(get, never):Float; + private static function get_denominator() return FlxMath.roundDecimal(16 / stepsPerBeat, 2); + + /** + * Last step from BPM Change + */ + public static var lastStepChange(get, never):Float; + private static function get_lastStepChange() return curChange.stepTime; + + /** + * Last beat from BPM Change + */ + public static var lastBeatChange(get, never):Float; + private static function get_lastBeatChange() return curChange.beatTime; + + /** + * Last measure from BPM Change + */ + public static var lastMeasureChange(get, never):Float; + private static function get_lastMeasureChange() return curChange.measureTime; + /** * Current step */ @@ -77,7 +161,6 @@ class Conductor */ public static var curMeasure:Int = 0; - /** * Current step, as a `Float` (ex: 4.94, instead of 4) */ @@ -100,54 +183,164 @@ class Conductor /** * Array of all BPM changes that have been mapped. */ - public static var bpmChangeMap:Array<BPMChangeEvent> = []; + public static var bpmChangeMap:Array<BPMChangeEvent>; + + /** + * Array of all events that have been rejected by the Conductor. + */ + public static var invalidEvents:Array<ChartEvent> = []; + + private static var validEventNames:Array<String> = ["BPM Change", "Time Signature Change", "Continuous BPM Change"]; @:dox(hide) public function new() {} public static function reset() { songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0; - bpmChangeMap = []; - changeBPM(0); + curChangeIndex = 0; + changeBPM(); } + public static function changeBPM(bpm:Float = 100, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4) + bpmChangeMap = [{bpm: bpm, beatsPerMeasure: beatsPerMeasure, stepsPerBeat: stepsPerBeat, songTime: 0, stepTime: 0, beatTime: 0, measureTime: 0}]; + public static function setupSong(SONG:ChartData) { reset(); mapBPMChanges(SONG); - changeBPM(SONG.meta.bpm, cast SONG.meta.beatsPerMeasure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4)); } + + private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float):BPMChangeEvent { + var beatTime:Float, measureTime:Float, stepTime:Float; + stepTime = (curChange.continuous ? curChange.endStepTime : curChange.stepTime) + (time - (curChange.continuous ? curChange.endSongTime : curChange.songTime)) / (15000 / curChange.bpm); + + beatTime = curChange.beatTime + (stepTime - curChange.stepTime) / curChange.stepsPerBeat; + measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure; + + bpmChangeMap.push(curChange = { + songTime: time, + stepTime: stepTime, + beatTime: beatTime, + measureTime: measureTime, + bpm: bpm, + beatsPerMeasure: curChange.beatsPerMeasure, + stepsPerBeat: curChange.stepsPerBeat + }); + return curChange; + } + /** * Maps BPM changes from a song. * @param song Song to map BPM changes from. */ - public static function mapBPMChanges(song:ChartData) - { - bpmChangeMap = [ - { - stepTime: 0, - songTime: 0, - bpm: song.meta.bpm - } - ]; - + public static function mapBPMChanges(song:ChartData) { + var curChange:BPMChangeEvent = { + songTime: 0, + stepTime: 0, + beatTime: 0, + measureTime: 0, + bpm: song.meta.bpm, + beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4), + stepsPerBeat: CoolUtil.floorInt(song.meta.stepsPerBeat.getDefault(4)) + }; + curChangeIndex = 0; + bpmChangeMap = [curChange]; + invalidEvents = []; if (song.events == null) return; - var curBPM:Float = song.meta.bpm; - var songTime:Float = 0; - var stepTime:Float = 0; + // fix the sort first... + var events:Array<ChartEvent> = [for (e in song.events) if (e.params != null && validEventNames.contains(e.name)) e]; + events.sort((a, b) -> MathUtil.equal(a.time, b.time) ? (a.name == "Time Signature Change" ? -1 : 1) : Std.int(a.time - b.time)); - for(e in song.events) if (e.name == "BPM Change" && e.params != null && e.params[0] is Float) { - if (e.params[0] == curBPM) continue; - var steps = (e.time - songTime) / ((60 / curBPM) * 1000 / 4); - stepTime += steps; - songTime = e.time; - curBPM = e.params[0]; + for (e in events) curChange = mapEvent(e, curChange); + } - bpmChangeMap.push({ - stepTime: stepTime, - songTime: songTime, - bpm: curBPM - }); + private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) { + var name = e.name, params = e.params, time = e.time; + if (curChange.continuous && MathUtil.lessThan(time, curChange.endSongTime)) { //ensure that you cannot place any conductor events during a continuous change + invalidEvents.push(e); + Logs.trace('Invalid Conductor event "${e.name}" at ${e.time} (Intersecting continuous change!)', WARNING); + return curChange; } + + if (name == "BPM Change" && params[0] is Float && curChange.bpm != params[0]) + curChange = mapBPMChange(curChange, time, params[0]); + else if (name == "Time Signature Change") { + //if (beatsPerMeasure == curChange.beatsPerMeasure && stepsPerBeat == curChange.stepsPerBeat) continue; + /* TODO: make so time sigs doesnt stop the bpm change if its in the duration of bpm change */ + + if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm); + curChange.beatsPerMeasure = params[0]; + + if (params[2]) curChange.stepsPerBeat = params[1]; + else curChange.stepsPerBeat = Math.floor(16 / params[1]); // convert from denominator to stepsPerBeat + + curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998); + curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998); + curChange.measureTime = CoolUtil.floorInt(curChange.measureTime + .99998); + } + else if (name == "Continuous BPM Change") { + var prevBPM = curChange.bpm; + if (curChange.bpm == params[0]) { + invalidEvents.push(e); + return curChange; //DO NOT!!!! + } + + curChange = mapBPMChange(curChange, time, params[0]); + curChange.endSongTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000; + curChange.endStepTime = curChange.stepTime + params[1]; + curChange.continuous = true; + } + return curChange; + } + + public static function mapCharterBPMChanges(song:ChartData) { + var curChange:BPMChangeEvent = { + songTime: 0, + stepTime: 0, + beatTime: 0, + measureTime: 0, + bpm: song.meta.bpm, + beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4), + stepsPerBeat: CoolUtil.floorInt(song.meta.stepsPerBeat.getDefault(4)) + }; + bpmChangeMap = [curChange]; + invalidEvents = []; + + var grpEvents = Charter.instance.leftEventsGroup.members.concat(Charter.instance.rightEventsGroup.members); + var n = grpEvents.length; + + if (n != 0) { + grpEvents.sort((a, b) -> a.step < b.step ? -1 : 1); + curChangeIndex = 0; + + var i = 0, i2 = 0, j = 0, x = 0, events = [], ce, e; + while (i < n) { + x += (ce = grpEvents[i]).events.length; + if (++i < n && grpEvents[i].step == ce.step) continue; + + var eventTime = Conductor.getStepsInTime(ce.step, curChangeIndex); + if (Math.isNaN(eventTime)) eventTime = 0; + + events.resize(x); + while (i2 < i) { + x = 0; + ce = grpEvents[i2++]; + while (x < ce.events.length) if (validEventNames.contains((e = ce.events[x++]).name)) { + events[j++] = e; + if (eventTime != 0) e.time = eventTime; + } + } + events.resize(x = j); + events.sort((a, b) -> MathUtil.equal(a.time, b.time) ? (a.name == "Time Signature Change" ? -1 : 1) : 0); + + j = 0; + while (j < x) curChange = mapEvent(events[j++], curChange); + curChangeIndex = bpmChangeMap.length - 1; + + x = j = 0; + } + } + + curChangeIndex = 0; } private static var elapsed:Float; @@ -159,16 +352,9 @@ class Conductor } private static function __updateSongPos(elapsed:Float) { - if (FlxG.sound.music == null || !FlxG.sound.music.playing) { - lastSongPos = FlxG.sound.music != null ? FlxG.sound.music.time - songOffset : -songOffset; - return; - } - - if (lastSongPos != (lastSongPos = FlxG.sound.music.time - songOffset)) { - // update conductor - songPosition = lastSongPos; - } else { - songPosition += songOffset + elapsed * 1000; + if (FlxG.sound.music != null) { // CNE FlxSound is Interpolated. + lastSongPos = FlxG.sound.music.time - songOffset; + if (FlxG.sound.music.playing) songPosition = FlxG.sound.music.time; } } @@ -177,137 +363,247 @@ class Conductor reset(); } private static var __lastChange:BPMChangeEvent; + private static var __updateStep:Bool; private static var __updateBeat:Bool; private static var __updateMeasure:Bool; private static function update() { - if (FlxG.state != null && FlxG.state is MusicBeatState) { - var state:MusicBeatState = cast FlxG.state; - if(state.cancelConductorUpdate) - return; - } + if (FlxG.state != null && FlxG.state is MusicBeatState && cast(FlxG.state, MusicBeatState).cancelConductorUpdate) return; __updateSongPos(FlxG.elapsed); - if (bpm > 0) { - // updates curBeat and stuff - __lastChange = { - stepTime: 0, - songTime: 0, - bpm: 0 - }; - for (change in Conductor.bpmChangeMap) - { - if (Conductor.songPosition >= change.songTime) - __lastChange = change; + var oldStep = curStep, oldBeat = curBeat, oldMeasure = curMeasure, oldChangeIndex = curChangeIndex; + + if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) { + var change = curChange; + curStepFloat = getTimeWithBPMInSteps(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex)); + curBeatFloat = change.beatTime + (curStepFloat - change.stepTime) / change.stepsPerBeat; + curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / change.beatsPerMeasure; + } + else + curMeasureFloat = (curBeatFloat = (curStepFloat = songPosition / stepCrochet) / stepsPerBeat) / beatsPerMeasure; + + if (curChangeIndex != oldChangeIndex) { + var prev = bpmChangeMap[oldChangeIndex]; + if (prev != null) { + if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat) + onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); + + if (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); } + else { + onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); + onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); + } + } - if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) changeBPM(__lastChange.bpm); + if (__updateStep = (curStep != (curStep = CoolUtil.floorInt(curStepFloat)))) { + if (curStep > oldStep) for (i in oldStep...curStep) onStepHit.dispatch(i + 1); + else onStepHit.dispatch(curStep); + } - curStepFloat = __lastChange.stepTime + ((Conductor.songPosition - __lastChange.songTime) / Conductor.stepCrochet); - curBeatFloat = curStepFloat / stepsPerBeat; - curMeasureFloat = curBeatFloat / beatsPerMeasure; + if (__updateBeat = (curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat)))) { + if (curBeat > oldBeat) for (i in oldBeat...curBeat) onBeatHit.dispatch(i + 1); + else onBeatHit.dispatch(curBeat); + } - var oldStep = curStep; - var oldBeat = curBeat; - var oldMeasure = curMeasure; - if (curStep != (curStep = CoolUtil.floorInt(curStepFloat))) { - if (curStep < oldStep && oldStep - curStep < 2) return; - // updates step - __updateBeat = curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat)); - __updateMeasure = __updateBeat && (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat))); + if (__updateMeasure = (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat)))) { + if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) onMeasureHit.dispatch(i + 1); + else onMeasureHit.dispatch(curMeasure); + } - if (curStep > oldStep) { - for(i in oldStep...curStep) { - onStepHit.dispatch(i+1); + if (__updateStep || __updateBeat || __updateMeasure) { + var state = FlxG.state; + while (state != null) { + if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) { + var st = cast(state, IBeatReceiver); + + if (__updateStep) { + if (curStep > oldStep) for (i in oldStep...curStep) st.stepHit(i + 1); + else st.stepHit(curStep); + } + + if (__updateBeat) { + if (curBeat > oldBeat) for (i in oldBeat...curBeat) st.beatHit(i + 1); + else st.beatHit(curBeat); + } + + if (__updateMeasure) { + if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) st.measureHit(i + 1); + else st.measureHit(curMeasure); } } - if (__updateBeat && curBeat > oldBeat) { - for(i in oldBeat...curBeat) { - onBeatHit.dispatch(i+1); - } - } - if (__updateMeasure && curMeasure > oldMeasure) { - for(i in oldMeasure...curMeasure) { - onMeasureHit.dispatch(i+1); - } - } - - if (FlxG.state is IBeatReceiver) { - var state = FlxG.state; - while(state != null) { - if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) { - var st:IBeatReceiver = cast state; - if (curStep > oldStep) { - for(i in oldStep...curStep) { - st.stepHit(i+1); - } - } - if (__updateBeat && curBeat > oldBeat) { - for(i in oldBeat...curBeat) { - st.beatHit(i+1); - } - } - if (__updateMeasure && curMeasure > oldMeasure) { - for(i in oldMeasure...curMeasure) { - st.measureHit(i+1); - } - } - } - state = state.subState; - } - } - + state = state.subState; } } } - public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4) - { - bpm = newBpm; - - crochet = ((60 / bpm) * 1000); - stepCrochet = crochet / stepsPerBeat; - - Conductor.beatsPerMeasure = beatsPerMeasure; - Conductor.stepsPerBeat = stepsPerBeat; - - onBPMChange.dispatch(bpm); + public static function getTimeInChangeIndex(time:Float, index:Int = 0):Int { + if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1; + else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].songTime > time) { + while (--index > 0) if (time > bpmChangeMap[index].songTime) return index; + return 0; + } + else { + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].songTime > time) return i - 1; + return bpmChangeMap.length - 1; + } } - public static function getTimeForStep(step:Float) { - var bpmChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: bpm - }; - - for(change in bpmChangeMap) - if (change.stepTime < step && change.stepTime >= bpmChange.stepTime) - bpmChange = change; - - return bpmChange.songTime + ((step - bpmChange.stepTime) * ((60 / bpmChange.bpm) * (1000/stepsPerBeat))); + public static function getStepsInChangeIndex(stepTime:Float, index:Int = 0):Int { + if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1; + else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].stepTime > stepTime) { + while (--index > 0) if (stepTime > bpmChangeMap[index].stepTime) return index; + return 0; + } + else { + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].stepTime > stepTime) return i - 1; + return bpmChangeMap.length - 1; + } } - public static function getStepForTime(time:Float) { - var bpmChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: bpm - }; - - for(change in bpmChangeMap) - if (change.songTime < time && change.songTime >= bpmChange.songTime) - bpmChange = change; - - return bpmChange.stepTime + ((time - bpmChange.songTime) / ((60 / bpmChange.bpm) * (1000/stepsPerBeat))); + public static function getBeatsInChangeIndex(beatTime:Float, index:Int = 0):Int { + if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1; + else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].beatTime > beatTime) { + while (--index > 0) if (beatTime > bpmChangeMap[index].beatTime) return index; + return 0; + } + else { + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].beatTime > beatTime) return i - 1; + return bpmChangeMap.length - 1; + } } + public static function getMeasuresInChangeIndex(measureTime:Float, index:Int = 0):Int { + if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1; + else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].measureTime > measureTime) { + while (--index > 0) if (measureTime > bpmChangeMap[index].measureTime) return index; + return 0; + } + else { + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].measureTime > measureTime) return i - 1; + return bpmChangeMap.length - 1; + } + } + + public static function getTimeWithIndexInBPM(time:Float, index:Int):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && time < bpmChange.endSongTime && index > 0) { + var prevBPM = bpmChangeMap[index - 1].bpm; + if (time <= bpmChange.songTime) return prevBPM; + + var ratio = (time - bpmChange.songTime) / (bpmChange.endSongTime - bpmChange.songTime); + return Math.pow(prevBPM, 1 - ratio) * Math.pow(bpmChange.bpm, ratio); + } + return bpmChange.bpm; + } + + public static function getStepsWithIndexInBPM(stepTime:Float, index:Int):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && index > 0) { + var prevBPM = bpmChangeMap[index - 1].bpm; + if (stepTime <= bpmChange.stepTime) return prevBPM; + + var endStepTime = bpmChange.stepTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM) / Math.log(bpmChange.bpm / prevBPM) / 15000; + if (stepTime < endStepTime) return FlxMath.remapToRange(stepTime, bpmChange.stepTime, endStepTime, prevBPM, bpmChange.bpm); + } + return bpmChange.bpm; + } + + public static function getTimeInBPM(time:Float):Float { + if (bpmChangeMap.length == 0) return dummyChange.bpm; + return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time)); + } + + public static function getTimeWithBPMInSteps(time:Float, index:Int, bpm:Float):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && time > bpmChange.songTime && index > 0) { + var prevBPM = bpmChangeMap[index - 1].bpm; + if (time > bpmChange.endSongTime) + return bpmChange.stepTime + (((bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM)) + / Math.log(bpmChange.bpm / prevBPM) + (time - bpmChange.endSongTime) * bpm) / 15000; + else + return bpmChange.stepTime + (time - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 15000; + } + else { + return bpmChange.stepTime + (time - bpmChange.songTime) / (15000 / bpm); + } + } + + public static function getTimeInBeats(time:Float, from:Int = 0):Float { + var index = getTimeInChangeIndex(time, from); + if (index == -1) return time / (60000 / dummyChange.bpm); + else if (index == 0) return time / (15000 / bpmChangeMap[index].bpm) / bpmChangeMap[index].stepsPerBeat; + else { + var change = bpmChangeMap[index]; + return change.beatTime + (getTimeWithBPMInSteps(time, index, getTimeWithIndexInBPM(time, index)) - change.stepTime) / change.stepsPerBeat; + } + } + + public static function getTimeInSteps(time:Float, from:Int = 0):Float { + var index = getTimeInChangeIndex(time, from); + return index < 1 ? time / (15000 / getTimeInBPM(time)) : getTimeWithBPMInSteps(time, index, getTimeWithIndexInBPM(time, index)); + } + + @:noCompletion + @:haxe.warning("-WDeprecated") + public static inline function getStepForTime(time:Float):Float return getTimeInSteps(time); + + public static function getStepsWithBPMInTime(stepTime:Float, index:Int, bpm:Float):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && stepTime > bpmChange.stepTime && index > 0) { + var prevBPM = bpmChangeMap[index - 1].bpm; + var time = bpmChange.songTime + (stepTime - bpmChange.stepTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 15000; + if (time > bpmChange.endSongTime) + return (15000 * (stepTime - bpmChange.stepTime) - ((bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM)) + / Math.log(bpm / prevBPM)) / bpm + bpmChange.endSongTime; + else + return time; + } + else { + return bpmChange.songTime + (stepTime - bpmChange.stepTime) * (15000 / bpm); + } + } + + public static function getMeasuresInTime(measureTime:Float, from:Int = 0):Float { + var index = getMeasuresInChangeIndex(measureTime, from); + if (index == -1) return measureTime * (15000 / dummyChange.bpm); + else if (index == 0) return measureTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat * bpmChangeMap[index].beatsPerMeasure; + else { + var change = bpmChangeMap[index]; + var stepTime = change.stepTime + (measureTime - change.measureTime) * change.stepsPerBeat * change.beatsPerMeasure; + return getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index)); + } + } + + public static function getBeatsInTime(beatTime:Float, from:Int = 0):Float { + var index = getBeatsInChangeIndex(beatTime, from); + if (index == -1) return beatTime * (15000 / dummyChange.bpm); + else if (index == 0) return beatTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat; + else { + var change = bpmChangeMap[index]; + var stepTime = change.stepTime + (beatTime - change.beatTime) * change.stepsPerBeat; + return getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index)); + } + } + + public static function getStepsInTime(stepTime:Float, from:Int = 0):Float { + var index = getStepsInChangeIndex(stepTime, from); + return index < 1 ? stepTime * (15000 / bpmChangeMap[index].bpm) : getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index)); + } + + @:noCompletion + @:haxe.warning("-WDeprecated") + public static inline function getTimeForStep(steps:Float):Float return getStepsInTime(steps); + public static inline function getMeasureLength() return stepsPerBeat * beatsPerMeasure; public static inline function getMeasuresLength() { if (FlxG.sound.music == null) return 0.0; - return getStepForTime(FlxG.sound.music.length) / getMeasureLength(); + var length = FlxG.sound.music.length; + var index = getTimeInChangeIndex(length, bpmChangeMap.length - 1); + var change = bpmChangeMap[index]; + return change.measureTime + (getTimeInBeats(length, index) - change.beatTime) / change.beatsPerMeasure; } -} +} \ No newline at end of file diff --git a/source/funkin/backend/system/Controls.hx b/source/funkin/backend/system/Controls.hx index 72cd2c46..0778469d 100644 --- a/source/funkin/backend/system/Controls.hx +++ b/source/funkin/backend/system/Controls.hx @@ -24,6 +24,11 @@ enum Control PAUSE; //CHEAT; SWITCHMOD; + + // Debugs + DEV_ACCESS; + DEV_CONSOLE; + DEV_RELOAD; } enum KeyboardScheme @@ -150,6 +155,25 @@ class Controls extends FlxActionSet @:pressed("switchmod") public var SWITCHMOD_HOLD(get, set): Bool; @:justReleased("switchmod") public var SWITCHMOD_R(get, set): Bool; + @:devModeOnly + @:gamepad([]) + @:justPressed("dev-access") public var DEV_ACCESS(get, set): Bool; + @:pressed("dev-access") public var DEV_ACCESS_HOLD(get, set): Bool; + @:justReleased("dev-access") public var DEV_ACCESS_R(get, set): Bool; + + @:devModeOnly + @:gamepad([]) + @:justPressed("dev-console") public var DEV_CONSOLE(get, set): Bool; + @:pressed("dev-console") public var DEV_CONSOLE_HOLD(get, set): Bool; + @:justReleased("dev-console") public var DEV_CONSOLE_R(get, set): Bool; + + @:devModeOnly + @:gamepad([]) + @:justPressed("dev-reload") public var DEV_RELOAD(get, set): Bool; + @:pressed("dev-reload") public var DEV_RELOAD_HOLD(get, set): Bool; + @:justReleased("dev-reload") public var DEV_RELOAD_R(get, set): Bool; + + @:allow(funkin.backend.utils.ControlsUtil) var byName:Map<String, FlxActionDigital> = []; public var gamepadsAdded:Array<Int> = []; @@ -219,13 +243,13 @@ class Controls extends FlxActionSet macro_forEachBound(control, (action, _) -> removeKeys(action, keys)); } - inline static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState) + public inline static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState) { for (key in keys) action.addKey(key, state); } - static function removeKeys(action:FlxActionDigital, keys:Array<FlxKey>) + public static function removeKeys(action:FlxActionDigital, keys:Array<FlxKey>) { var i = action.inputs.length; while (i-- > 0) @@ -302,13 +326,13 @@ class Controls extends FlxActionSet macro_forEachBound(control, (action, _) -> removeButtons(action, gamepadID, buttons)); } - inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id) + public inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id) { for (button in buttons) action.addGamepad(button, state, id); } - static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array<FlxGamepadInputID>) + public static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array<FlxGamepadInputID>) { var i = action.inputs.length; while (i-- > 0) @@ -319,8 +343,21 @@ class Controls extends FlxActionSet } } - inline static function isGamepad(input:FlxActionInput, deviceID:Int) + public inline static function isGamepad(input:FlxActionInput, deviceID:Int) { return input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID); } -} + + @:nullSafety(Off) + public inline function getJustPressed(name:String) { + return ControlsUtil.getJustPressed(this, name); + } + @:nullSafety(Off) + public inline function getJustReleased(name:String) { + return ControlsUtil.getJustReleased(this, name); + } + @:nullSafety(Off) + public inline function getPressed(name:String) { + return ControlsUtil.getPressed(this, name); + } +} \ No newline at end of file diff --git a/source/funkin/backend/system/FakeCamera.hx b/source/funkin/backend/system/FakeCamera.hx new file mode 100644 index 00000000..99d0eb1b --- /dev/null +++ b/source/funkin/backend/system/FakeCamera.hx @@ -0,0 +1,50 @@ +package funkin.backend.system; + +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxDrawTrianglesItem; +import flixel.math.FlxMatrix; +import flixel.math.FlxPoint; +import flixel.system.FlxAssets.FlxShader; +import openfl.display.BitmapData; +import openfl.display.BlendMode; +import openfl.geom.ColorTransform; +import openfl.geom.Point; +import openfl.geom.Rectangle; + +class FakeCamera extends FlxCamera { + public static final instance = new FakeCamera(); + + public function new() { + super(); + + visible = false; + } + + override function startTrianglesBatch(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader) { return null;} + override function startQuadBatch(graphic:FlxGraphic, colored:Bool, hasColorOffsets:Bool = false, ?blend:BlendMode, smooth:Bool = false, ?shader:FlxShader) { return null;} + override function clearDrawStack() {} + override function render() {} + + public override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {} + public override function copyPixels(?frame:FlxFrame, ?pixels:BitmapData, ?sourceRect:Rectangle, destPoint:Point, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {} + public override function drawTriangles(graphic:FlxGraphic, vertices:DrawData<Float>, indices:DrawData<Int>, uvtData:DrawData<Float>, ?colors:DrawData<Int>, ?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader):Void {} + + public override function update(elapsed:Float) {} + + // override function set_zoom(Zoom:Float):Float return zoom = (Zoom == 0) ? FlxCamera.defaultZoom : Zoom; + + public override function setScale(X:Float, Y:Float):Void {} +} + +class FakeCallCamera extends FakeCamera { + public static final instance = new FakeCallCamera(); + public var ignoreDraws:Bool = false; + + public dynamic function onDraw(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) { + } + + override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) { + if (!ignoreDraws) onDraw(frame, pixels, matrix, transform, blend, smoothing, shader); + } +} \ No newline at end of file diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx index b00c2001..94e21604 100644 --- a/source/funkin/backend/system/Flags.hx +++ b/source/funkin/backend/system/Flags.hx @@ -1,34 +1,40 @@ package funkin.backend.system; -import lime.utils.AssetLibrary as LimeAssetLibrary; -import lime.utils.AssetType; import flixel.util.FlxColor; - -import lime.app.Application; -import funkin.backend.system.macros.GitCommitMacro; +import funkin.backend.assets.ModsFolder; import funkin.backend.assets.IModsAssetLibrary; import funkin.backend.assets.ScriptedAssetLibrary; +import funkin.backend.system.macros.GitCommitMacro; +import funkin.backend.utils.IniUtil; +import lime.app.Application; +import lime.utils.AssetLibrary as LimeAssetLibrary; +import lime.utils.AssetType; /** * A class that reads the `flags.ini` file, allowing to read settable Flags (customs too). */ @:build(funkin.backend.system.macros.FlagMacro.build()) class Flags { + // -- Codename's Addon Config -- + @:bypass public static var addonFlags:Map<String, Dynamic> = []; + // -- Codename's Mod Config -- - public static var MOD_NAME:String; - public static var MOD_DESCRIPTION:String; - public static var MOD_API_VERSION:Int; - public static var MOD_DOWNLOADED_LINK:String; + public static var MOD_NAME:String = ""; + public static var MOD_DESCRIPTION:String = ""; + public static var MOD_API_VERSION:Int = 1; + public static var MOD_DOWNLOAD_LINK:String = ""; public static var MOD_DEPENDENCIES:Array<String> = []; - public static var MOD_ICON64:String; - public static var MOD_ICON32:String; - public static var MOD_ICON16:String; - public static var MOD_ICON:String; + public static var MOD_ICON64:String = ""; + public static var MOD_ICON32:String = ""; + public static var MOD_ICON16:String = ""; + public static var MOD_ICON:String = ""; - public static var MOD_DISCORD_CLIENT_ID:String; - public static var MOD_DISCORD_LOGO_KEY:String; + public static var MOD_DISCORD_CLIENT_ID:String = ""; + public static var MOD_DISCORD_LOGO_KEY:String = ""; + public static var MOD_DISCORD_LOGO_TEXT:String = ""; // -- Codename's Default Flags -- + public static var CURRENT_API_VERSION:Int = 1; public static var COMMIT_NUMBER:Int = GitCommitMacro.commitNumber; public static var COMMIT_HASH:String = GitCommitMacro.commitHash; public static var COMMIT_MESSAGE:String = 'Commit $COMMIT_NUMBER ($COMMIT_HASH)'; @@ -42,10 +48,6 @@ class Flags { public static var REPO_OWNER:String = "CodenameCrew"; public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME'; - // make this empty once you guys are done with the project. - // good luck /gen <3 @crowplexus - public static var RELEASE_CYCLE:String = "Beta"; - /** * Preferred sound extension for the game's audio files. * Currently is set to `mp3` for web targets, and `ogg` for other targets. @@ -55,7 +57,7 @@ class Flags { public static var IMAGE_EXT:String = "png"; // we also support jpg public static var DEFAULT_DISCORD_LOGO_KEY:String = "icon"; - public static var DEFAULT_DISCORD_CLIENT_ID:String = "1027994136193810442"; + public static var DEFAULT_DISCORD_CLIENT_ID:String = "1383853614589673472"; public static var DEFAULT_DISCORD_LOGO_TEXT:String = "Codename Engine"; @:also(funkin.game.Character.FALLBACK_CHARACTER) @@ -88,23 +90,25 @@ class Flags { // Internal stuff public static var DEFAULT_BPM:Float = 100.0; - public static var DEFAULT_BEATS_PER_MEASURE:Float = 4; + public static var DEFAULT_BEATS_PER_MEASURE:Int = 4; public static var DEFAULT_STEPS_PER_BEAT:Int = 4; + public static var DEFAULT_LOOP_TIME:Float = 0.0; public static var SUPPORTED_CHART_RUNTIME_FORMATS:Array<String> = ["Legacy", "Psych Engine"]; public static var SUPPORTED_CHART_FORMATS:Array<String> = ["BaseGame"]; - public static var BASEGAME_SONG_METADATA_VERSION:String = "2.2.2"; - public static var BASEGAME_SONG_CHART_DATA_VERSION:String = "2.0.0"; - public static var BASEGAME_DEFAULT_NOTE_STYLE:String = 'funkin'; - public static var BASEGAME_DEFAULT_ALBUM_ID:String = 'volume1'; - public static var BASEGAME_DEFAULT_PREVIEW_START:Float = 0; - public static var BASEGAME_DEFAULT_PREVIEW_END:Float = 15000; + public static var VSLICE_SONG_METADATA_VERSION:String = "2.2.2"; + public static var VSLICE_SONG_CHART_DATA_VERSION:String = "2.0.0"; + public static var VSLICE_DEFAULT_NOTE_STYLE:String = 'funkin'; + public static var VSLICE_DEFAULT_ALBUM_ID:String = 'volume1'; + public static var VSLICE_DEFAULT_PREVIEW_START:Int = 0; + public static var VSLICE_DEFAULT_PREVIEW_END:Int = 15000; /** - * Default background colors for songs without bg color + * Default background colors for songs or more without bg color */ public static var DEFAULT_COLOR:FlxColor = 0xFF9271FD; + public static var DEFAULT_WEEK_COLOR:FlxColor = 0xFFF9CF51; public static var DEFAULT_COOP_ALLOWED:Bool = false; public static var DEFAULT_OPPONENT_MODE_ALLOWED:Bool = false; @@ -122,11 +126,13 @@ class Flags { public static var DEFAULT_GAMEOVER_LOSS_SFX:String = "gameOverSFX"; public static var DEFAULT_GAMEOVER_RETRY_SFX:String = "gameOverEnd"; - public static var DEFAULT_CAM_ZOOM_INTERVAL:Int = 4; + public static var DEFAULT_CAM_ZOOM_INTERVAL:Int = 1; + public static var DEFAULT_CAM_ZOOM_OFFSET:Float = 0; + //public static var DEFAULT_CAM_ZOOM_EVERY:BeatType = MEASURE; public static var DEFAULT_CAM_ZOOM_STRENGTH:Int = 1; public static var DEFAULT_CAM_ZOOM:Float = 1.05; // what zoom level it defaults to public static var DEFAULT_HUD_ZOOM:Float = 1.0; - public static var MAX_CAMERA_ZOOM:Float = 1.35; + public static var MAX_CAMERA_ZOOM_MULT:Float = 1.35; // to translate these you need to convert them into ids // Resume -> pause.resume @@ -196,40 +202,19 @@ class Flags { // -- End of Codename's Default Flags -- /** - * Flags that Codename couldn't recognize as it's own defaults (they can only be `string`!). + * Flags that Codename couldn't recognize as it's own defaults (they can only be `string`! due to them being unparsed). */ @:bypass public static var customFlags:Map<String, String> = []; public static function loadFromData(flags:Map<String, String>, data:String) { - var trimmed:String; - var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed]; + var res = IniUtil.parseString(data); - for(line in splitContent) { - if(line.startsWith(";")) continue; - if(line.startsWith("#")) continue; - if(line.startsWith("//")) continue; - if(line.length == 0) continue; - if(line.charAt(0) == "[" && line.charAt(line.length-1) == "]") continue; - - var index = line.indexOf("="); - if(index == -1) continue; - var name = line.substr(0, index).trim(); - var value = line.substr(index+1).trim(); - - var wasQuoted = value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code; - if(wasQuoted) value = value.substr(1, value.length - 2); - if((!wasQuoted && value.length == 0) || name.length == 0) - continue; - - if(!flags.exists(name)) - flags[name] = value; - } + for (section in res) for (key => value in section) flags[key] = value; } - public static function loadFromDatas(files:Array<String>) { + public static function loadFromDatas(datas:Array<String>) { var flags:Map<String, String> = []; - for(file in files) { - var data:String = Assets.getText(file); + for(data in datas) { if(data != null) loadFromData(flags, data); } @@ -248,20 +233,46 @@ class Flags { * Loads the flags from the assets. **/ public static function load(?libs:Array<LimeAssetLibrary> = null) { - if (libs == null) - libs = Paths.assetsTree.libraries; - final flagsPath = Paths.getPath("flags.ini"); - var datas:Array<String> = [ - for(lib in libs) - #if TRANSLATIONS_SUPPORT + if (libs == null) { + libs = Paths.assetsTree.libraries.copy(); + libs.reverse(); + } + for(lib in libs) { + var l = lib; + if (l is openfl.utils.AssetLibrary) { + @:privateAccess + l = cast(l, openfl.utils.AssetLibrary).__proxy; + } + if(lib is funkin.backend.assets.TranslatedAssetLibrary) { // skip translations since it would be useless, if you wanna modify it set the flags inside of global.hx - if(!(lib is funkin.backend.assets.TranslatedAssetLibrary)) - #end - if(lib.exists(flagsPath, AssetType.TEXT)) - lib.getAsset(flagsPath, AssetType.TEXT) - ]; + continue; + } - var flags:Map<String, String> = loadFromDatas(datas); - parseFlags(flags); + if (l is IModsAssetLibrary) { + var flagsTxt = ""; + if (l.exists(Paths.ini("config/modpack"), AssetType.TEXT)) + flagsTxt = l.getAsset(Paths.ini("config/modpack"), AssetType.TEXT); + if (cast(l, IModsAssetLibrary).modName == "assets") continue; + + if (cast(l, IModsAssetLibrary).modName == ModsFolder.currentModFolder) { + var flags:Map<String, String> = []; + loadFromData(flags, flagsTxt); + parseFlags(flags); + } + else { + var flags:Map<String, String> = []; + loadFromData(flags, flagsTxt); + addonFlags.set(cast(l, IModsAssetLibrary).modName.toLowerCase().replace(" ", "").trim(), flags); + } + } + else { + var flagsTxt = ""; + if (l.exists(Paths.getPath("data/config/flags.ini"), AssetType.TEXT)) + flagsTxt = l.getAsset(Paths.getPath("data/config/flags.ini"), AssetType.TEXT); + var flags:Map<String, String> = []; + loadFromData(flags, flagsTxt); + parseFlags(flags); + } + } } } \ No newline at end of file diff --git a/source/funkin/backend/system/Logs.hx b/source/funkin/backend/system/Logs.hx index 07759a60..1913e4ab 100644 --- a/source/funkin/backend/system/Logs.hx +++ b/source/funkin/backend/system/Logs.hx @@ -6,7 +6,7 @@ import funkin.backend.utils.NativeAPI.ConsoleColor; import funkin.backend.utils.NativeAPI; import haxe.Log; -class Logs { +final class Logs { private static var __showing:Bool = false; public static var nativeTrace = Log.trace; diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx index fc5fccdb..f5844278 100644 --- a/source/funkin/backend/system/Main.hx +++ b/source/funkin/backend/system/Main.hx @@ -1,6 +1,7 @@ package funkin.backend.system; -import funkin.backend.utils.translations.FormatUtil; +import sys.io.File; +import sys.FileSystem; import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond; import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.TransitionData; @@ -32,7 +33,7 @@ class Main extends Sprite public static var instance:Main; public static var modToLoad:String = null; - public static var forceGPUOnlyBitmapsOff:Bool = #if windows false #else true #end; + public static var forceGPUOnlyBitmapsOff:Bool = #if desktop false #else true #end; public static var noTerminalColor:Bool = false; public static var verbose:Bool = false; @@ -86,12 +87,12 @@ class Main extends Sprite public static var audioDisconnected:Bool = false; public static var changeID:Int = 0; - public static var pathBack = #if windows + public static var pathBack = #if (windows || linux) "../../../../" #elseif mac "../../../../../../../" #else - "" + "../../../../" #end; public static var startedFromSource:Bool = #if TEST_BUILD true #else false #end; @@ -174,6 +175,9 @@ class Main extends Sprite ModsFolder.init(); #if MOD_SUPPORT + if (FileSystem.exists("mods/autoload.txt")) + modToLoad = File.getContent("mods/autoload.txt").trim(); + ModsFolder.switchMod(modToLoad.getDefault(Options.lastLoadedMod)); #end @@ -181,6 +185,8 @@ class Main extends Sprite } public static function refreshAssets() @:privateAccess { + FunkinCache.instance.clearSecondLayer(); + var game = FlxG.game; var daSndTray = Type.createInstance(game._customSoundTray = funkin.menus.ui.FunkinSoundTray, []); var index:Int = game.numChildren - 1; @@ -239,9 +245,9 @@ class Main extends Sprite Sys.setCwd(haxe.io.Path.directory(Sys.programPath())); } #elseif android - Sys.setCwd(Path.addTrailingSlash(VERSION.SDK_INT > 30 ? Context.getObbDir() : Context.getExternalFilesDir())); + Sys.setCwd(haxe.io.Path.addTrailingSlash(VERSION.SDK_INT > 30 ? Context.getObbDir() : Context.getExternalFilesDir())); #elseif (ios || switch) - Sys.setCwd(Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath)); + Sys.setCwd(haxe.io.Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath)); #end } diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx index f3334147..64876449 100644 --- a/source/funkin/backend/system/MainState.hx +++ b/source/funkin/backend/system/MainState.hx @@ -5,17 +5,24 @@ import sys.FileSystem; #end import flixel.FlxState; import funkin.backend.assets.ModsFolder; +import funkin.backend.assets.ModsFolderLibrary; import funkin.backend.chart.EventsData; -import funkin.menus.BetaWarningState; -import funkin.menus.TitleState; import funkin.backend.system.framerate.Framerate; +import funkin.editors.ModConfigWarning; +import funkin.menus.TitleState; +import haxe.io.Path; + +@dox(hide) +typedef AddonInfo = { + var name:String; + var path:String; +} /** * Simple state used for loading the game */ class MainState extends FlxState { public static var initiated:Bool = false; - public static var betaWarningShown:Bool = false; public override function create() { super.create(); if (!initiated) { @@ -29,31 +36,71 @@ class MainState extends FlxState { #end Options.save(); + ControlsUtil.resetCustomControls(); FlxG.bitmap.reset(); FlxG.sound.destroy(true); Paths.assetsTree.reset(); #if MOD_SUPPORT - var _lowPriorityAddons:Array<String> = []; - var _highPriorityAddons:Array<String> = []; - var _noPriorityAddons:Array<String> = []; - if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)) { - for(i=>addon in [for(dir in FileSystem.readDirectory(ModsFolder.addonsPath)) if (FileSystem.isDirectory('${ModsFolder.addonsPath}$dir')) dir]) { - if (addon.startsWith("[LOW]")) _lowPriorityAddons.insert(0, addon); - else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, addon); - else _noPriorityAddons.insert(0, addon); - } - for (addon in _lowPriorityAddons) - Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.addonsPath}$addon', StringTools.ltrim(addon.substr("[LOW]".length)))); - } - if (ModsFolder.currentModFolder != null) - Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.modsPath}${ModsFolder.currentModFolder}', ModsFolder.currentModFolder)); + inline function isDirectory(path:String):Bool + return FileSystem.exists(path) && FileSystem.isDirectory(path); - if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)){ - for (addon in _noPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.addonsPath}$addon', addon)); - for (addon in _highPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.addonsPath}$addon', StringTools.ltrim(addon.substr("[HIGH]".length)))); + inline function ltrim(str:String, prefix:String):String + return str.substr(prefix.length).ltrim(); + + inline function loadLib(path:String, name:String) + Paths.assetsTree.addLibrary(ModsFolder.loadModLib(path, name)); + + var _lowPriorityAddons:Array<AddonInfo> = []; + var _highPriorityAddons:Array<AddonInfo> = []; + var _noPriorityAddons:Array<AddonInfo> = []; + + var addonPaths = [ + ModsFolder.addonsPath, + ( + ModsFolder.currentModFolder != null ? + ModsFolder.modsPath + ModsFolder.currentModFolder + "/addons/" : + null + ) + ]; + + for(path in addonPaths) { + if (path == null) continue; + if (!isDirectory(path)) continue; + + for(addon in FileSystem.readDirectory(path)) { + if(!FileSystem.isDirectory(path + addon)) { + switch(Path.extension(addon).toLowerCase()) { + case 'zip': + addon = Path.withoutExtension(addon); + default: + continue; + } + } + + var data:AddonInfo = { + name: addon, + path: path + addon + }; + + if (addon.startsWith("[LOW]")) _lowPriorityAddons.insert(0, data); + else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, data); + else _noPriorityAddons.insert(0, data); + } } + + for (addon in _lowPriorityAddons) + loadLib(addon.path, ltrim(addon.name, "[LOW]")); + + if (ModsFolder.currentModFolder != null) + loadLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder); + + for (addon in _noPriorityAddons) + loadLib(addon.path, addon.name); + + for (addon in _highPriorityAddons) + loadLib(addon.path, ltrim(addon.name, "[HIGH]")); #end Flags.load(); @@ -65,22 +112,29 @@ class MainState extends FlxState { Main.refreshAssets(); DiscordUtil.init(); EventsData.reloadEvents(); + ControlsUtil.loadCustomControls(); TitleState.initialized = false; - if(Framerate.isLoaded) + if (Framerate.isLoaded) Framerate.instance.reload(); - FlxG.switchState(new funkin.editors.alphabet.AlphabetEditor("normal")); - /* - if (Flags.DISABLE_BETA_WARNING_SCREEN || betaWarningShown) - FlxG.switchState(new TitleState()); - else - FlxG.switchState(new BetaWarningState()); - betaWarningShown = true; - */ - #if sys CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN); #end + + if (Options.devMode) { + var lib:ModsFolderLibrary; + for (e in Paths.assetsTree.libraries) { + @:privateAccess if (!(e is openfl.utils.AssetLibrary) || !((lib = cast cast(e, openfl.utils.AssetLibrary).__proxy) is ModsFolderLibrary)) continue; + if (lib.modName == ModsFolder.currentModFolder) { + if (lib.exists(Paths.ini("config/modpack"), lime.utils.AssetType.TEXT)) break; + + FlxG.switchState(new ModConfigWarning(lib)); + return; + } + } + } + + FlxG.switchState(new TitleState()); } } \ No newline at end of file diff --git a/source/funkin/backend/system/framerate/CodenameBuildField.hx b/source/funkin/backend/system/framerate/CodenameBuildField.hx index aa7a9b7b..314479ea 100644 --- a/source/funkin/backend/system/framerate/CodenameBuildField.hx +++ b/source/funkin/backend/system/framerate/CodenameBuildField.hx @@ -13,5 +13,5 @@ class CodenameBuildField extends TextField { } public function reload() - text = 'Codename Engine ${Flags.RELEASE_CYCLE}\n${Flags.COMMIT_MESSAGE}'; + text = '${Flags.VERSION_MESSAGE}\n${Flags.COMMIT_MESSAGE}'; } diff --git a/source/funkin/backend/system/framerate/ConductorInfo.hx b/source/funkin/backend/system/framerate/ConductorInfo.hx index c13e026f..e615bbba 100644 --- a/source/funkin/backend/system/framerate/ConductorInfo.hx +++ b/source/funkin/backend/system/framerate/ConductorInfo.hx @@ -12,7 +12,7 @@ class ConductorInfo extends FramerateCategory { _text += '\n - ${Conductor.curStep} steps'; _text += '\n - ${Conductor.curMeasure} measures'; _text += '\nCurrent BPM: ${Conductor.bpm}'; - _text += '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}'; + _text += '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.denominator}'; this.text.text = _text; super.__enterFrame(t); diff --git a/source/funkin/backend/system/framerate/FlixelInfo.hx b/source/funkin/backend/system/framerate/FlixelInfo.hx index 297fcde7..c485468c 100644 --- a/source/funkin/backend/system/framerate/FlixelInfo.hx +++ b/source/funkin/backend/system/framerate/FlixelInfo.hx @@ -12,9 +12,7 @@ class FlixelInfo extends FramerateCategory { @:privateAccess { - var c:Int = 0; - for(_ in FlxG.bitmap._cache.keys()) - c++; + var c:Int = Lambda.count(FlxG.bitmap._cache); if((FlxG.state is ModState)) { var state:ModState = cast FlxG.state; @@ -27,6 +25,7 @@ class FlixelInfo extends FramerateCategory { _text += '\nBitmaps Count: ${c}'; _text += '\nSounds Count: ${FlxG.sound.list.length}'; _text += '\nFlxG.game Childs Count: ${FlxG.game.numChildren}'; + if(FlxG.renderBlit) _text += '\nBlitting Render: true'; // _text += '\nCached objects count: ${cachedObjects}'; #if FLX_POINT_POOL //var points = flixel.math.FlxPoint.FlxBasePoint.pool; diff --git a/source/funkin/backend/system/framerate/Framerate.hx b/source/funkin/backend/system/framerate/Framerate.hx index 237762dc..2acc7601 100644 --- a/source/funkin/backend/system/framerate/Framerate.hx +++ b/source/funkin/backend/system/framerate/Framerate.hx @@ -20,7 +20,7 @@ class Framerate extends Sprite { public static var codenameBuildField:CodenameBuildField; #end - public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_sans" #end; + public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_typewriter" #end; /** * 0: FPS INVISIBLE @@ -46,7 +46,7 @@ class Framerate extends Sprite { super(); if (instance != null) throw "Cannot create another instance"; instance = this; - textFormat = new TextFormat("Consolas", 12, -1); + textFormat = new TextFormat(fontName, 12, -1); isLoaded = true; @@ -118,7 +118,7 @@ class Framerate extends Sprite { x = 10 + offset.x; y = 2 + offset.y; - var width = Math.max(fpsCounter.width, #if SHOW_BUILD_ON_FPS Math.max(memoryCounter.width, codenameBuildField.width) #else memoryCounter.width #end) + (x*2); + var width = MathUtil.maxSmart(fpsCounter.width, memoryCounter.width #if SHOW_BUILD_ON_FPS , codenameBuildField.width #end) + (x*2); var height = #if SHOW_BUILD_ON_FPS codenameBuildField.y + codenameBuildField.height #else memoryCounter.y + memoryCounter.height #end; bgSprite.x = -x; bgSprite.y = offset.x; diff --git a/source/funkin/backend/system/framerate/SystemInfo.hx b/source/funkin/backend/system/framerate/SystemInfo.hx index a4f71d76..9bf44f41 100644 --- a/source/funkin/backend/system/framerate/SystemInfo.hx +++ b/source/funkin/backend/system/framerate/SystemInfo.hx @@ -3,6 +3,10 @@ package funkin.backend.system.framerate; import funkin.backend.system.Logs; import funkin.backend.utils.MemoryUtil; import funkin.backend.utils.native.HiddenProcess; +#if cpp +import cpp.Float64; +import cpp.UInt64; +#end using StringTools; @@ -17,7 +21,7 @@ class SystemInfo extends FramerateCategory { static var __formattedSysText:String = ""; - public static inline function init() { + public static function init() { #if linux var process = new HiddenProcess("cat", ["/etc/os-release"]); if (process.exitCode() != 0) Logs.error('Unable to grab OS Label'); @@ -49,6 +53,25 @@ class SystemInfo extends FramerateCategory { if (osName != "") osInfo = '${osName} ${osVersion}'.trim(); } + #elseif windows + var windowsCurrentVersionPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + var buildNumber = Std.parseInt(RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "CurrentBuildNumber")); + var edition = RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "ProductName"); + + var lcuKey = "WinREVersion"; // Last Cumulative Update Key On Older Windows Versions + if (buildNumber >= 22000) { // Windows 11 Initial Release Build Number + edition = edition.replace("Windows 10", "Windows 11"); + lcuKey = "LCUVer"; // Last Cumulative Update Key On Windows 11 + } + + var lcuVersion = RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, lcuKey); + + osInfo = edition; + + if (lcuVersion != null && lcuVersion != "") + osInfo += ' ${lcuVersion}'; + else if (lime.system.System.platformVersion != null && lime.system.System.platformVersion != "") + osInfo += ' ${lime.system.System.platformVersion}'; #else if (lime.system.System.platformLabel != null && lime.system.System.platformLabel != "" && lime.system.System.platformVersion != null && lime.system.System.platformVersion != "") osInfo = '${lime.system.System.platformLabel.replace(lime.system.System.platformVersion, "").trim()} ${lime.system.System.platformVersion}'; @@ -58,10 +81,7 @@ class SystemInfo extends FramerateCategory { try { #if windows - var process = new HiddenProcess("wmic", ["cpu", "get", "name"]); - if (process.exitCode() != 0) throw 'Could not fetch CPU information'; - - cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim(); + cpuName = RegistryUtil.get(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString"); #elseif mac var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isn't able to use the args but it still works if (process.exitCode() != 0) throw 'Could not fetch CPU information'; @@ -82,7 +102,7 @@ class SystemInfo extends FramerateCategory { Logs.error('Unable to grab CPU Name: $e'); } - @:privateAccess { + @:privateAccess if(FlxG.renderTile) { // Blit doesn't enable the gpu. Idk if we should fix this if (flixel.FlxG.stage.context3D != null && flixel.FlxG.stage.context3D.gl != null) { gpuName = Std.string(flixel.FlxG.stage.context3D.gl.getParameter(flixel.FlxG.stage.context3D.gl.RENDERER)).split("/")[0].trim(); #if !flash @@ -91,11 +111,13 @@ class SystemInfo extends FramerateCategory { #end if(openfl.display3D.Context3D.__glMemoryTotalAvailable != -1) { - var vRAMBytes:UInt = cast flixel.FlxG.stage.context3D.gl.getParameter(openfl.display3D.Context3D.__glMemoryTotalAvailable); + var vRAMBytes:Int = cast flixel.FlxG.stage.context3D.gl.getParameter(openfl.display3D.Context3D.__glMemoryTotalAvailable); if (vRAMBytes == 1000 || vRAMBytes == 1 || vRAMBytes <= 0) - Logs.error('Unable to grab GPU VRAM'); - else - vRAM = CoolUtil.getSizeString(vRAMBytes * 1000); + Logs.trace('Unable to grab GPU VRAM', ERROR, RED); + else { + var vRAMBytesFloat:#if cpp Float64 #else Float #end = vRAMBytes*1024; + vRAM = CoolUtil.getSizeString64(vRAMBytesFloat); + } } } else Logs.error('Unable to grab GPU Info'); diff --git a/source/funkin/backend/system/macros/BuildInfo.hx b/source/funkin/backend/system/macros/BuildInfo.hx index 193d3bb9..a22511b8 100644 --- a/source/funkin/backend/system/macros/BuildInfo.hx +++ b/source/funkin/backend/system/macros/BuildInfo.hx @@ -7,6 +7,8 @@ class BuildInfo { public static function printBuildInfo() { if(haxe.macro.Context.defined("display") || haxe.macro.Context.defined("display-details")) return; var haxeVersion = haxe.macro.Context.definedValue("haxe").trim(); + var sleepChangeVersion = false; + Sys.println('[ BUILD INFO ]'); Sys.println('Haxe Version: ${haxeVersion}'); try { @@ -33,8 +35,10 @@ class BuildInfo { } } - if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion) + if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion) { Sys.println('Last Built With Haxe: ${lastBuiltWith} [!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!]'); + sleepChangeVersion = true; + } } catch(e) {} var targetPlatform = #if html5 "Web (HTML5)" #elseif ios "iOS" @@ -51,6 +55,14 @@ class BuildInfo { Sys.println('Target Platform: ' + targetPlatform); Sys.println('Build Date: ${Date.now().toString()}'); Sys.println(''); + + if(sleepChangeVersion) { + Sys.println('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + Sys.println('!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!'); + Sys.println('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + Sys.println(''); + Sys.sleep(5); + } } } #end \ No newline at end of file diff --git a/source/funkin/backend/system/macros/ControlsMacro.macro.hx b/source/funkin/backend/system/macros/ControlsMacro.macro.hx index 2219a677..93c927cc 100644 --- a/source/funkin/backend/system/macros/ControlsMacro.macro.hx +++ b/source/funkin/backend/system/macros/ControlsMacro.macro.hx @@ -14,6 +14,7 @@ class ControlsMacro { static var _allControls: Array<String> = null; static var _allInternalControls: Array<String> = null; + static var _allDevModeOnlyControls: Array<String> = null; static var _currentControls: Map<String, Array<Expr>> = null; static var _currentGamepadControls: Map<String, Expr> = null; static var _keySet: Map<String, String> = null; @@ -29,6 +30,7 @@ class ControlsMacro _allControls = []; _allInternalControls = []; + _allDevModeOnlyControls = []; _currentControls = []; _currentGamepadControls = []; _keySet = []; @@ -154,6 +156,7 @@ class ControlsMacro _allControls = null; _allInternalControls = null; + _allDevModeOnlyControls = null; _currentControls = null; _currentGamepadControls = null; _keySet = null; @@ -289,6 +292,9 @@ class ControlsMacro case ":justReleased": keyset = extractString(meta.params[0]); expr = macro func($i{internalName}, JUST_RELEASED); + case ":devModeOnly": + if (!_allDevModeOnlyControls.contains(shortName)) + _allDevModeOnlyControls.push(shortName); default: shouldRemove = false; } @@ -326,17 +332,16 @@ class ControlsMacro // Generated Code: // inline function get_UI_UP(): Bool - // return _uiUp.check(); + // return _uiUp.check(); or return Options.devMode && _uiUp.check(); depending if its dev mode var getField: Field = { name: "get_" + name, access: [APrivate, AInline], kind: FFun({ ret: macro : Bool, params: [], - expr: macro - { - return $i{internalName}.check(); - }, + expr: _allDevModeOnlyControls.contains(shortName) ? + (macro return Options.devMode && $i{internalName}.check()) : + (macro return $i{internalName}.check()), args: [] }), pos: field.pos, diff --git a/source/funkin/backend/system/modules/AudioSwitchFix.hx b/source/funkin/backend/system/modules/AudioSwitchFix.hx index 941846e0..3e89e373 100644 --- a/source/funkin/backend/system/modules/AudioSwitchFix.hx +++ b/source/funkin/backend/system/modules/AudioSwitchFix.hx @@ -4,6 +4,8 @@ import flixel.FlxState; import flixel.sound.FlxSound; import funkin.backend.utils.NativeAPI; import lime.media.AudioManager; +import lime.media.AudioSource; +import haxe.Timer; /** * if you are stealing this keep this comment at least please lol @@ -12,58 +14,56 @@ import lime.media.AudioManager; */ @:dox(hide) class AudioSwitchFix { - @:noCompletion - private static function onStateSwitch(state:FlxState):Void { - #if windows - if (Main.audioDisconnected) { + public static function onAudioDisconnected() @:privateAccess { + var sources:Array<{source:AudioSource, playing:Bool, time:Float, gain:Float, pitch:Float, position:lime.math.Vector4}> = []; + for (source in AudioSource.activeSources) { + var wasPlaying = source.playing; + sources.push({ + source: source, + playing: wasPlaying, + time: source.currentTime, + gain: source.gain, + pitch: source.pitch, + position: source.position + }); - var playingList:Array<PlayingSound> = []; - for(e in FlxG.sound.list) { - if (e.playing) { - playingList.push({ - sound: e, - time: e.time - }); - e.stop(); - } - } - if (FlxG.sound.music != null) - FlxG.sound.music.stop(); + source.__backend.dispose(); + if (wasPlaying) source.__backend.playing = true; + } - AudioManager.shutdown(); - AudioManager.init(); - // #if !lime_doc_gen - // if (AudioManager.context.type == OPENAL) - // { - // var alc = AudioManager.context.openal; + AudioManager.shutdown(); + AudioManager.init(); + // #if !lime_doc_gen + // if (AudioManager.context.type == OPENAL) + // { + // var alc = AudioManager.context.openal; - // var device = alc.openDevice(); - // var ctx = alc.createContext(device); - // alc.makeContextCurrent(ctx); - // alc.processContext(ctx); - // } - // #end - Main.changeID++; + // var device = alc.openDevice(); + // var ctx = alc.createContext(device); + // alc.makeContextCurrent(ctx); + // alc.processContext(ctx); + // } + // #end + + for (d in sources) { + d.source.__backend.init(); + d.source.currentTime = d.time; + d.source.gain = d.gain; + d.source.pitch = d.pitch; + d.source.position = d.position; - for(e in playingList) { - e.sound.play(e.time); - } + if (d.playing) d.source.play(); + } - Main.audioDisconnected = false; - } - #end + Main.changeID++; + Main.audioDisconnected = false; } + private static var timer:Timer; + + private static function onRun() if (Main.audioDisconnected) onAudioDisconnected(); public static function init() { - #if windows NativeAPI.registerAudio(); - FlxG.signals.preStateCreate.add(onStateSwitch); - #end + if (timer == null) (timer = new Timer(1000)).run = onRun; } -} - -@:dox(hide) -typedef PlayingSound = { - var sound:FlxSound; - var time:Float; } \ No newline at end of file diff --git a/source/funkin/backend/system/modules/CrashHandler.hx b/source/funkin/backend/system/modules/CrashHandler.hx index 1debede9..61c5029d 100644 --- a/source/funkin/backend/system/modules/CrashHandler.hx +++ b/source/funkin/backend/system/modules/CrashHandler.hx @@ -7,7 +7,7 @@ import openfl.errors.Error; import openfl.events.ErrorEvent; import openfl.events.UncaughtErrorEvent; -class CrashHandler { +final class CrashHandler { public static function init() { Lib.current.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError); #if cpp diff --git a/source/funkin/backend/system/net/Socket.hx b/source/funkin/backend/system/net/Socket.hx index 4aefdabe..19ebf9a1 100644 --- a/source/funkin/backend/system/net/Socket.hx +++ b/source/funkin/backend/system/net/Socket.hx @@ -18,7 +18,7 @@ class Socket implements IFlxDestroyable { public function read():String { try { - return this.socket.input.readUntil(('\n').charCodeAt(0)).replace("\\n", "\n"); + return this.socket.input.readUntil('\n'.code).replace("\\n", "\n"); } catch(e) { } diff --git a/source/funkin/backend/system/updating/UpdateAvailableScreen.hx b/source/funkin/backend/system/updating/UpdateAvailableScreen.hx index a4cf4d6b..fd13b2cc 100644 --- a/source/funkin/backend/system/updating/UpdateAvailableScreen.hx +++ b/source/funkin/backend/system/updating/UpdateAvailableScreen.hx @@ -104,7 +104,7 @@ class UpdateAvailableScreen extends MusicBeatState { public override function update(elapsed:Float) { super.update(elapsed); - destY = FlxMath.bound(destY - (FlxG.mouse.wheel * 75), 0, Math.max(0, changeLogText.height - FlxG.height + versionCheckBG.height + 20 + optionsBG.height)); + destY = CoolUtil.bound(destY - (FlxG.mouse.wheel * 75), 0, Math.max(0, changeLogText.height - FlxG.height + versionCheckBG.height + 20 + optionsBG.height)); FlxG.camera.scroll.y = lerp(FlxG.camera.scroll.y, destY, 1/3); if (controls.LEFT_P || controls.RIGHT_P) { diff --git a/source/funkin/backend/utils/AudioAnalyzer.hx b/source/funkin/backend/utils/AudioAnalyzer.hx index 03fc3967..0ad9af3d 100644 --- a/source/funkin/backend/utils/AudioAnalyzer.hx +++ b/source/funkin/backend/utils/AudioAnalyzer.hx @@ -3,38 +3,184 @@ package funkin.backend.utils; import flixel.sound.FlxSound; import lime.media.AudioBuffer; -// Thank you yosh :DDD -lunar -// (https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx) +#if lime_vorbis +import lime.media.vorbis.Vorbis; +import lime.media.vorbis.VorbisFile; +import lime.utils.ArrayBuffer; +#end + +// ORIGINAL CODES FROM YOSH & LUNAR https://github.com/CodenameCrew/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx +// REWRITTEN BY RALTYRO class AudioAnalyzer { + public var sound:FlxSound; public var buffer:AudioBuffer; - var __peakByte:Float = 0; - var __timeMulti:Float = 0; + var __toBits:Float; + var __wordSize:Int; + var __bitUnsignedSize:Int; + var __bitSize:Int; + + #if lime_vorbis + var __vorbis:VorbisFile; + #end public function new(sound:FlxSound) { - @:privateAccess buffer = sound._sound.__buffer; - - __peakByte = Math.pow(2, buffer.bitsPerSample-1)-1; - - __timeMulti = 1 / (1 / buffer.sampleRate); - __timeMulti *= buffer.bitsPerSample; - __timeMulti -= __timeMulti % buffer.bitsPerSample; + this.sound = sound; + __check(); } - public function analyze(startPos:Float, endPos:Float):Float { - var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample; - var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample; + inline function __check() if (sound.buffer != buffer) { + __bitSize = 1 << ((buffer = sound.buffer).bitsPerSample - 1); + __bitUnsignedSize = 1 << buffer.bitsPerSample; + __toBits = buffer.sampleRate / 1000 * buffer.channels * (__wordSize = buffer.bitsPerSample >> 3); + __vorbis = null; + } - bytesStartPos -= bytesStartPos % buffer.bitsPerSample; - bytesEndPos -= bytesEndPos % buffer.bitsPerSample; + public function analyze(startPos:Float, endPos:Float, ?outSeperate:Array<Float>):Float { + __check(); + if (buffer.data != null) return __analyze(startPos, endPos, outSeperate); + #if lime_vorbis + else if (__prepareVorbis()) return __analyzeVorbis(startPos, endPos, outSeperate); + #end + return 0; + } - var maxByte:Int = 0; - for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) { - var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample)) - | (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8); - if (byte > 256 * 128) byte -= 256 * 256; - if (maxByte < byte) maxByte = byte; + inline function __analyze(startPos:Float, endPos:Float, ?outSeperate:Array<Float>):Float { + var pos = Math.floor(startPos * __toBits), end = Math.floor(endPos * __toBits), buf = buffer.data #if !js .buffer #end; + var max = 0, b = 0, w = 0, c = 0, useSeperate = outSeperate != null; + if (useSeperate) { + while (c < outSeperate.length) outSeperate[c++] *= __bitSize; + while (c++ < buffer.channels) outSeperate.push(0); + c = Math.floor((pos % (__wordSize * buffer.channels)) / __wordSize); } - return maxByte/__peakByte; + pos -= pos % __wordSize; + end -= end % __wordSize; + + while (pos < end) { + // 8-bit audio data is unsigned (0 is 128, 128 is 256, -128 is 0) + if (__wordSize == 1) b = #if js buf[pos++] #else buf.get(pos++) #end - __bitSize; + else { + while (w < buffer.bitsPerSample) { + b |= #if js buf[pos] #else buf.get(pos) #end << w; + w += 8; + pos++; + } + if (b > __bitSize) b -= __bitUnsignedSize; + } + if (max < b) max = b; + if (useSeperate) { + if (outSeperate[c] < b) outSeperate[c] = b; + if (++c > buffer.channels) c = 0; + } + w = b = 0; + } + + return max / __bitSize; } + + #if lime_vorbis // As far i know, only native supports vorbis + var __buffer:ArrayBuffer; + var __bufferSize:Int; + function __prepareVorbis():Bool @:privateAccess { + if (buffer.__srcVorbisFile == null) return __vorbis != null; + if (__vorbis != null) return true; + if ((__vorbis = buffer.__srcVorbisFile.clone()) != null) { // IM HOPING IT HAVE A GC CLOSURE. + __buffer = new ArrayBuffer(__bufferSize = 0x400 * buffer.channels * (buffer.bitsPerSample >> 3)); + return true; + } + return false; + } + + inline function __analyzeVorbis(startPos:Float, endPos:Float, ?outSeperate:Array<Float>):Float @:privateAccess { + var n = Math.floor((endPos - startPos) * __toBits); + if ((n -= n % __wordSize) < __wordSize) return 0; + + var pos = 0, max = 0, b = 0, w = 0, c = 0, useSeperate = outSeperate != null; + if (useSeperate) { + while (c < outSeperate.length) outSeperate[c++] *= __bitSize; + while (c++ < buffer.channels) outSeperate.push(0); + } + + // IT SHOULD BE ALWAYS BACKEND.STREAMED IF THIS GETS CALLED. + var backend = sound._source != null ? sound._source.__backend : null; + if (backend != null && backend.streamTimer != null) { + var i = backend.bufferSizes.length - backend.queuedBuffers; + var time = backend.bufferTimes[i] * 1000; + + if (startPos >= time && startPos < backend.bufferTimes[backend.bufferSizes.length - 1] * 1000) { + var buf = backend.bufferDatas[i].buffer, size = backend.bufferSizes[i]; + pos = Math.floor((startPos - time) * __toBits); + c = Math.floor((pos % (__wordSize * buffer.channels)) / __wordSize); + pos -= pos % __wordSize; + + while (n > 0) { + if (__wordSize == 1) { + b = buf.get(pos++) - __bitSize; + n--; + } + else { + while (w < buffer.bitsPerSample) { + b |= buf.get(pos) << w; + w += 8; + pos++; + n--; + } + if (b > __bitSize) b -= __bitUnsignedSize; + } + if (max < b) max = b; + if (useSeperate) { + if (outSeperate[c] < b) outSeperate[c] = b; + if (++c > buffer.channels) c = 0; + } + if (pos >= size) { + if (++i >= backend.bufferDatas.length) break; + size = backend.bufferSizes[i]; + buf = backend.bufferDatas[i].buffer; + pos = 0; + } + w = b = 0; + } + + startPos = pos / __toBits + time; + // Cannot inline a not final return + } + } + if (n == 0) return max / __bitSize; + + if (Math.abs(startPos - __vorbis.timeTell() * 1000) > 4) { + if (startPos < 1) __vorbis.rawSeek(0); + else __vorbis.timeSeek(startPos / 1000); + } + + pos = c = 0; + var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN, result; + while (n > 0) { + n -= (result = __vorbis.read(__buffer, 0, n < __bufferSize ? n : __bufferSize, isBigEndian, __wordSize, true)); + + if (result == Vorbis.HOLE) continue; + else if (result <= 0) break; + + while (pos < result) { + if (__wordSize == 1) b = __buffer.get(pos++) - __bitSize; + else { + while (w < buffer.bitsPerSample) { + b |= __buffer.get(pos) << w; + w += 8; + pos++; + } + if (b > __bitSize) b -= __bitUnsignedSize; + } + if (max < b) max = b; + if (useSeperate) { + if (outSeperate[c] < b) outSeperate[c] = b; + if (++c > buffer.channels) c = 0; + } + w = b = 0; + } + pos = 0; + } + + return max / __bitSize; + } + #end } \ No newline at end of file diff --git a/source/funkin/backend/utils/BitmapUtil.hx b/source/funkin/backend/utils/BitmapUtil.hx index 776e9330..e4f7596c 100644 --- a/source/funkin/backend/utils/BitmapUtil.hx +++ b/source/funkin/backend/utils/BitmapUtil.hx @@ -2,8 +2,10 @@ package funkin.backend.utils; import flixel.util.FlxColor; import openfl.display.BitmapData; +import openfl.geom.Point; +import openfl.geom.Rectangle; -class BitmapUtil { +final class BitmapUtil { /** * Returns the most present color in a Bitmap. * @param bmap Bitmap @@ -18,7 +20,7 @@ class BitmapUtil { for(y in 0...bmap.height) { for(x in 0...bmap.width) { color = bmap.getPixel32(x, y); - fixedColor = 0xFF000000 + (color % 0x1000000); + fixedColor = 0xFF000000 | (color & 0xFFFFFF); if (!colorMap.exists(fixedColor)) colorMap[fixedColor] = 0; colorMap[fixedColor] += color.alphaFloat; @@ -49,7 +51,7 @@ class BitmapUtil { for(y in 0...bmap.height) { for(x in 0...bmap.width) { color = bmap.getPixel32(x, y); - fixedColor = 0xFF000000 + (color % 0x1000000); + fixedColor = 0xFF000000 | (color & 0xFFFFFF); if (!colorMap.exists(fixedColor)) colorMap[fixedColor] = 0; colorMap[fixedColor] += color.alphaFloat * 0.33 + (0.67 * (color.saturation * (2 * (color.lightness > 0.5 ? 0.5 - (color.lightness) : color.lightness)))); @@ -66,4 +68,49 @@ class BitmapUtil { } return mostPresentColor; } + + /** + * Returns a new bitmap without any empty transperent space on the edges + * @param bitmap The bitmap to be cropped + */ + public static function crop(bitmap:BitmapData) { + var bitmapBounds:Rectangle = BitmapUtil.bounds(bitmap); + + var croppedBitmap:BitmapData = new BitmapData(Std.int(bitmapBounds.width), Std.int(bitmapBounds.height), true, 0x00000000); + croppedBitmap.copyPixels(bitmap, bitmapBounds, new Point(0,0)); + return croppedBitmap; + } + + /** + * Get bounds of non empty pixels in the bitmap + * @param bitmap + * @return + */ + public static function bounds(bitmap:BitmapData, ?limit:Rectangle = null):Rectangle { + var hasLimit:Bool = limit != null; + // Searching bounds + var startX:Int = hasLimit ? Std.int(limit.x) : 0; + var startY:Int = hasLimit ? Std.int(limit.y) : 0; + var endX:Int = hasLimit ? Std.int(limit.width) : bitmap.width; + var endY:Int = hasLimit ? Std.int(limit.height) : bitmap.height; + + // Detected bounds + var minX:Int = endX; + var minY:Int = endY; + var maxX:Int = startX; + var maxY:Int = startY; + + for (y in startY...endY) { + for (x in startX...endX) { + if (bitmap.getPixel32(x, y) != 0x00000000) { + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + } + } + + return new Rectangle(minX, minY, maxX-minX+1, maxY-minY+1); + } } \ No newline at end of file diff --git a/source/funkin/backend/utils/ControlsUtil.hx b/source/funkin/backend/utils/ControlsUtil.hx new file mode 100644 index 00000000..a3e2d5f8 --- /dev/null +++ b/source/funkin/backend/utils/ControlsUtil.hx @@ -0,0 +1,166 @@ +package funkin.backend.utils; + +import funkin.options.PlayerSettings; +import flixel.input.FlxInput; +import flixel.input.actions.FlxAction; +import funkin.backend.system.Controls; +import flixel.input.actions.FlxActionInput; +import flixel.input.actions.FlxActionManager; +import flixel.input.actions.FlxActionSet; +import flixel.input.gamepad.FlxGamepadInputID; +import flixel.input.keyboard.FlxKey; +import haxe.xml.Access; + +class ControlsUtil { + + private static var storedCustomControls:Array<String> = []; + + public static function initCustomControl(controls:Controls, name:String) { + if (controls.byName.exists(name)) return; + + controls.byName.set(name, new FlxActionDigital(name)); + controls.byName.set(name+"_P", new FlxActionDigital(name+"_P")); + controls.byName.set(name+"_R", new FlxActionDigital(name+"_R")); + storedCustomControls.push(name); + } + + public static function addKeysToCustomControl(controls:Controls, name:String, keys:Array<FlxKey>) { + if (!controls.byName.exists(name)) return; + + var pressed:FlxActionDigital = controls.byName.get(name); + if (pressed != null) Controls.addKeys(pressed, keys, PRESSED); + + var justPressed:FlxActionDigital = controls.byName.get(name+"_P"); + if (justPressed != null) Controls.addKeys(justPressed, keys, JUST_PRESSED); + + var justReleased:FlxActionDigital = controls.byName.get(name+"_R"); + if (justReleased != null) Controls.addKeys(justReleased, keys, JUST_RELEASED); + } + + public static function removeKeysFromCustomControl(controls:Controls, name:String, keys:Array<FlxKey>) { + if (!controls.byName.exists(name)) return; + + var pressed:FlxActionDigital = controls.byName.get(name); + if (pressed != null) Controls.removeKeys(pressed, keys); + + var justPressed:FlxActionDigital = controls.byName.get(name+"_P"); + if (justPressed != null) Controls.removeKeys(justPressed, keys); + + var justReleased:FlxActionDigital = controls.byName.get(name+"_R"); + if (justReleased != null) Controls.removeKeys(justReleased, keys); + } + + public static function resetCustomControls() { + removeCustomControls(PlayerSettings.solo.controls); + removeCustomControls(PlayerSettings.player1.controls); + removeCustomControls(PlayerSettings.player2.controls); + storedCustomControls = []; + } + + private static inline function removeCustomControls(controls:Controls) { + for (name in storedCustomControls) { + var pressed:FlxActionDigital = controls.byName.get(name); + if (pressed != null) pressed.destroy(); + + var justPressed:FlxActionDigital = controls.byName.get(name+"_P"); + if (justPressed != null) justPressed.destroy(); + + var justReleased:FlxActionDigital = controls.byName.get(name+"_R"); + if (justReleased != null) justReleased.destroy(); + + controls.byName.remove(name); + controls.byName.remove(name+"_P"); + controls.byName.remove(name+"_R"); + } + } + + + private static inline function checkControl(controls:Controls, name:String) { + var control = getControl(controls, name); + if (control != null) return control.check(); + return false; + } + + public static inline function getControl(controls:Controls, name:String) : FlxActionDigital { + return controls.byName.get(name); + } + + public static inline function getJustPressed(controls:Controls, name:String) { + return checkControl(controls, name + "_P"); + } + public static inline function getJustReleased(controls:Controls, name:String) { + return checkControl(controls, name + "_R"); + } + public static inline function getPressed(controls:Controls, name:String) { + return checkControl(controls, name); + } + + public static function loadCustomControls() { + var xmlPath = Paths.xml("config/controls"); + for(source in [funkin.backend.assets.AssetSource.SOURCE, funkin.backend.assets.AssetSource.MODS]) { + if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { + var access:Access = null; + try { + access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source)).firstElement()); + } catch(e) { + Logs.trace('Error while parsing controls.xml: ${Std.string(e)}', ERROR); + } + + if (access != null) { + parseControlsXml(access); + } + } + } + } + + private static inline function parseControlsXml(xml:Access) { + for (category in xml.elements) { + for (control in category.elements) { + if (!control.has.name) { + continue; + } + + var name = control.getAtt("name"); + var saveName = control.getAtt("saveName").getDefault(""); + var defaultKeyP1 = control.getAtt("keyP1").getDefault(""); + var defaultKeyP2 = control.getAtt("keyP2").getDefault(""); + + var keyP1:Null<FlxKey> = 0; + var keyP2:Null<FlxKey> = 0; + + ControlsUtil.initCustomControl(PlayerSettings.solo.controls, name); + ControlsUtil.initCustomControl(PlayerSettings.player1.controls, name); + ControlsUtil.initCustomControl(PlayerSettings.player2.controls, name); + + if (saveName != "") { + var p1:Null<FlxKey> = Reflect.getProperty(FlxG.save.data, "P1_"+saveName); + var p2:Null<FlxKey> = Reflect.getProperty(FlxG.save.data, "P2_"+saveName); + + //check for save data (and load default if it doesnt exist) + if (p1 != null) { + keyP1 = p1; + } else { + if (defaultKeyP1 != "") keyP1 = FlxKey.fromString(defaultKeyP1); + Reflect.setProperty(FlxG.save.data, "P1_"+saveName, keyP1); + } + + if (p2 != null) { + keyP2 = p2; + } else { + if (defaultKeyP2 != "") keyP2 = FlxKey.fromString(defaultKeyP2); + Reflect.setProperty(FlxG.save.data, "P2_"+saveName, keyP2); + } + + } else { + //if no save data just load the default + if (defaultKeyP1 != "") keyP1 = FlxKey.fromString(defaultKeyP1); + if (defaultKeyP2 != "") keyP2 = FlxKey.fromString(defaultKeyP2); + } + + ControlsUtil.addKeysToCustomControl(PlayerSettings.solo.controls, name, [keyP1, keyP2]); + ControlsUtil.addKeysToCustomControl(PlayerSettings.player1.controls, name, [keyP1, 0]); + ControlsUtil.addKeysToCustomControl(PlayerSettings.player2.controls, name, [0, keyP2]); + } + } + } +} \ No newline at end of file diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index bfb845e5..bf91299c 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -1,5 +1,13 @@ package funkin.backend.utils; +#if cpp +import cpp.Float64; +#end +import flxanimate.data.AnimationData.AnimAtlas; +import flixel.graphics.FlxGraphic; +import openfl.display.BitmapData; +import flixel.graphics.frames.FlxAtlasFrames; +import flixel.graphics.frames.FlxFramesCollection; #if sys import sys.FileSystem; #end @@ -37,6 +45,9 @@ import haxe.io.Path; import haxe.xml.Access; import lime.utils.Assets; import openfl.geom.ColorTransform; +import flixel.math.FlxPoint; +import haxe.Constraints.IMap; +import haxe.EnumTools.EnumValueTools; using StringTools; @@ -44,7 +55,7 @@ using StringTools; * Various utilities, that have no specific Util class. **/ @:allow(funkin.game.PlayState) -class CoolUtil +final class CoolUtil { /** * Gets the last exception stack. Useful for debugging. @@ -166,7 +177,7 @@ class CoolUtil if(result.isNothing) Logs.trace('The file where it has been tried to get the attributes from, might be corrupted or inexistent (code: ${result.getValue()})', WARNING); return result; #else - return 0; + return new FileAttributeWrapper(0); #end } @@ -204,7 +215,7 @@ class CoolUtil addMissingFolders(Path.directory(path)); var result = NativeAPI.addFileAttributes(path, attrib, useAbsolute); - if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING); + if (result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING); return result; #else return 0; @@ -265,10 +276,8 @@ class CoolUtil * Whenever a value is NaN or not. * @param v Value */ - public static inline function isNaN(v:Dynamic) { - if (v is Float || v is Int) - return Math.isNaN(cast(v, Float)); - return false; + public static inline function isNaN(v:Dynamic):Bool { + return (v is Float) ? Math.isNaN(cast(v, Float)) : false; } /** @@ -326,22 +335,41 @@ class CoolUtil return str; } + private static var sizeLabels:Array<String> = ["B", "KB", "MB", "GB", "TB"]; + /** * Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB` * @param size Size to convert to string * @return String Result string representation */ public static function getSizeString(size:Float):String { - var labels = ["B", "KB", "MB", "GB", "TB"]; var rSize:Float = size; var label:Int = 0; - while(rSize > 1024 && label < labels.length-1) { + var len = sizeLabels.length; + while(rSize >= 1024 && label < len-1) { label++; rSize /= 1024; } - return '${Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)}${labels[label]}'; + return Std.int(rSize) + ((label <= 1) ? "" : "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)) + sizeLabels[label]; } + /** + * Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB`, using Float64 on cpp targets + * @param size Size to convert to string + * @return String Result string representation + */ + public static function getSizeString64(size: #if cpp Float64 #else Float #end):String { + var rSize: #if cpp Float64 #else Float #end = size; + var label:Int = 0; + var len = sizeLabels.length; + while(rSize >= 1024 && label < len-1) { + label++; + rSize /= 1024; + } + return Std.int(rSize) + ((label <= 1) ? "" : "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)) + sizeLabels[label]; + } + + /** * Replaces in a string any kind of IP with `[Your IP]` making the string safer to trace. * @param msg String to check and edit @@ -380,7 +408,7 @@ class CoolUtil * @return FPS-Modified Ratio */ @:noUsing public static inline function getFPSRatio(ratio:Float):Float { - return FlxMath.bound(ratio * 60 * FlxG.elapsed, 0, 1); + return CoolUtil.bound(ratio * 60 * FlxG.elapsed, 0.0, 1.0); } /** * Tries to get a color from a `Dynamic` variable. @@ -427,7 +455,6 @@ class CoolUtil if (FlxG.sound.music == null || !FlxG.sound.music.playing) { playMusic(Paths.music('freakyMenu'), true, fadeIn ? 0 : 1, true, 102); - FlxG.sound.music.persist = true; if (fadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7); } @@ -457,32 +484,34 @@ class CoolUtil */ @:noUsing public static function playMusic(path:String, Persist:Bool = false, Volume:Float = 1, Looped:Bool = true, DefaultBPM:Float = 102, ?Group:FlxSoundGroup) { Conductor.reset(); - FlxG.sound.playMusic(path, Volume, Looped, Group); - if (FlxG.sound.music != null) { - FlxG.sound.music.persist = Persist; - } + if (FlxG.sound.music == null) FlxG.sound.music = new FlxSound(); + else if (FlxG.sound.music.active) FlxG.sound.music.stop(); + FlxG.sound.music.loadEmbedded(path, Looped); + FlxG.sound.music.volume = Volume; + FlxG.sound.music.persist = Persist; + FlxG.sound.defaultMusicGroup.add(FlxG.sound.music); - var infoPath = '${Path.withoutExtension(path)}.ini'; - if (Assets.exists(infoPath)) { - var musicInfo = IniUtil.parseAsset(infoPath, [ - "BPM" => null, - "TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT - ]); + var iniPath = '${Path.withoutExtension(path)}.ini'; + var musicData = Assets.exists(iniPath) ? IniUtil.parseAsset(iniPath)["Global"] : null; + if (musicData != null) { + if (musicData["LoopTime"] != null) FlxG.sound.music.loopTime = Std.parseFloat(musicData["LoopTime"]) * 1000; + if (musicData["EndTime"] != null) FlxG.sound.music.endTime = Std.parseFloat(musicData["EndTime"]) * 1000; + if (musicData["Offset"] != null) FlxG.sound.music.offset = Std.parseFloat(musicData["Offset"]) * 1000; - var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)]; - var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE; - var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT; - - // Check later, i don't think timeSignParsed can contain null, only nan - if (timeSignParsed.length == 2 && !timeSignParsed.contains(null)) { - beatsPerMeasure = timeSignParsed[0] == null || timeSignParsed[0] <= 0 ? Flags.DEFAULT_BEATS_PER_MEASURE : cast timeSignParsed[0]; - stepsPerBeat = timeSignParsed[1] == null || timeSignParsed[1] <= 0 ? Flags.DEFAULT_STEPS_PER_BEAT : cast timeSignParsed[1]; + var timeSignParsed:Array<Null<Float>> = musicData["TimeSignature"] == null ? [] : [for(s in musicData["TimeSignature"].split("/")) Std.parseFloat(s)]; + var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE, stepsPerBeat:Float = Flags.DEFAULT_STEPS_PER_BEAT; + if (timeSignParsed.length == 2) { + beatsPerMeasure = Math.isNaN(timeSignParsed[0]) ? Flags.DEFAULT_BEATS_PER_MEASURE : timeSignParsed[0]; + stepsPerBeat = Math.isNaN(timeSignParsed[1]) ? Flags.DEFAULT_STEPS_PER_BEAT : (16 / timeSignParsed[1]); // from denominator } - var bpm:Null<Float> = Std.parseFloat(musicInfo["BPM"]).getDefaultFloat(DefaultBPM); - Conductor.changeBPM(bpm, beatsPerMeasure, stepsPerBeat); - } else + var bpm:Float = Std.parseFloat(musicData["BPM"]).getDefault(DefaultBPM); + Conductor.changeBPM(bpm, beatsPerMeasure, Math.floor(stepsPerBeat)); + } + else Conductor.changeBPM(DefaultBPM); + + FlxG.sound.music.play(); } /** @@ -525,6 +554,23 @@ class CoolUtil * @return Array<Int> Final array */ @:noUsing public static inline function numberArray(max:Int, ?min:Int = 0):Array<Int> + { + /*return [for (i in min...max) i];*/ + // Reason for change: + // Old one uses push instead of creating an array with the wanted size. + var len:Int = CoolUtil.maxInt(max - min, 0); + #if cpp + var arr:Array<Int> = untyped __cpp__("::Array_obj< int >::__new({0})", len); + #else + var arr:Array<Int> = []; + arr.resize(len); + #end + for(i in 0...len) + arr[i] = i + min; + return arr; + } + + @:noUsing public static inline function numberArrayOld(max:Int, ?min:Int = 0):Array<Int> { return [for (i in min...max) i]; } @@ -557,6 +603,21 @@ class CoolUtil sprite.scale.set(nScale, nScale); } + public static function setGraphicSizeFloat(sprite:FlxSprite, Width:Float = 0, Height:Float = 0):Void + { + if (Width <= 0 && Height <= 0) + return; + + var newScaleX:Float = Width / sprite.frameWidth; + var newScaleY:Float = Height / sprite.frameHeight; + sprite.scale.set(newScaleX, newScaleY); + + if (Width <= 0) + sprite.scale.x = newScaleY; + else if (Height <= 0) + sprite.scale.y = newScaleX; + } + /** * Returns a simple string representation of a FlxKey. Used in Controls options. * @param key Key @@ -614,7 +675,6 @@ class CoolUtil case Y: obj.y = (cam.height - obj.height) / 2; case NONE: - } } @@ -636,8 +696,16 @@ class CoolUtil * @param name Name of the attribute */ public static inline function getAtt(xml:Access, name:String) { - if (!xml.has.resolve(name)) return null; - return xml.att.resolve(name); + /*if (!xml.has.resolve(name)) return null; + return xml.att.resolve(name);*/ + // Reason for change: + // Old one has error checking 4 times, this one has it once. + var xml:Xml = xml.x; + if (xml.nodeType != Element) { + throw 'Bad node type, expected Element but found ${xml.nodeType}'; + } + @:privateAccess + return xml.attributeMap.exists(name) ? xml.attributeMap.get(name) : null; } /** @@ -686,11 +754,11 @@ class CoolUtil * @param spr Sprite to load the graphic for * @param path Path to the graphic */ - public static function loadAnimatedGraphic(spr:FlxSprite, path:String) { + public static function loadAnimatedGraphic(spr:FlxSprite, path:String, fps:Float = 24.0) { spr.frames = Paths.getFrames(path, true); if (spr.frames != null && spr.frames.frames != null) { - spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true); + spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], fps, true); spr.animation.play("idle"); } @@ -736,7 +804,7 @@ class CoolUtil * Gets the macro class created by hscript-improved for an abstract / enum */ @:noUsing public static inline function getMacroAbstractClass(className:String) { - return Type.resolveClass('${className}_HSC'); + return Type.resolveClass(className + '_HSC'); } /** @@ -753,6 +821,8 @@ class CoolUtil i--; } return i;*/ + // Reason for change: + // Old one was made because at the time we didnt know the lastIndexOf function. return array.lastIndexOf(element); } @@ -793,6 +863,24 @@ class CoolUtil #end } + /** + * Browse a path in the operating system's explorer + * @param path + */ + public static inline function browsePath(path:String) { + var formattedPath:String = Path.normalize(path); + + #if windows + formattedPath = formattedPath.replace("/", "\\"); + Sys.command("explorer", [formattedPath]); + #elseif mac + Sys.command("open", [formattedPath]); + #elseif linux + var cmd = Sys.command("xdg-open", [formattedPath]); + if (cmd != 0) cmd = Sys.command("/usr/bin/xdg-open", [formattedPath]); + #end + } + /** * Converts a timestamp to a readable format such as `01:22` (`mm:ss`) */ @@ -818,6 +906,15 @@ class CoolUtil @:noUsing public static inline function maxInt(p1:Int, p2:Int) return p1 < p2 ? p2 : p1; + /** + * Equivalent of `Math.min`, except doesn't require a Int -> Float -> Int conversion. + * @param p1 + * @param p2 + * @return return p1 > p2 ? p2 : p1 + */ + @:noUsing public static inline function minInt(p1:Int, p2:Int) + return p1 > p2 ? p2 : p1; + /** * Equivalent of `Math.floor`, except doesn't require a Int -> Float -> Int conversion. * @param e Value to get the floor of. @@ -849,10 +946,11 @@ class CoolUtil * @param music Music */ public static inline function setMusic(frontEnd:SoundFrontEnd, music:FlxSound) { - if (frontEnd.music != null) - @:privateAccess frontEnd.destroySound(frontEnd.music); + if (frontEnd.music == music) return; + + if (frontEnd.music != null) @:privateAccess frontEnd.destroySound(frontEnd.music); frontEnd.list.remove(music); - frontEnd.music = music; + frontEnd.defaultMusicGroup.add(frontEnd.music = music); } /** @@ -860,8 +958,67 @@ class CoolUtil * @param mainEase Main ease * @param suffix Suffix (Ignored if `mainEase` is `linear`) */ - @:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String) - return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix)); + @:noUsing public static inline function flxeaseFromString(mainEase:String, ?suffix:String) + return Reflect.field(FlxEase, mainEase + (mainEase == "linear" || suffix == null ? "" : suffix)); + + /* + * Returns the filename of a path, without the extension. + * @param path Path to get the filename from + * @return Filename + */ + @:noUsing public static inline function getFilename(file:String) { + var file = new haxe.io.Path(file); + return file.file; + } + + @:noUsing public static function getClosestAngle(angle:Float, targetAngle:Float):Float { + var diff:Float = angle - targetAngle; + if (diff < -180) diff += 360; + else if (diff > 180) diff -= 360; + return angle - diff; + } + + /** + * Returns the screen position of an object, while taking the camera zoom into account. + * + * @param object Any `FlxObject` + * @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used. + * @param result Optional arg for the returning point + * @return The screen position of the object. + */ + public static function worldToScreenPosition(object:FlxObject, ?camera:FlxCamera, ?result:FlxPoint) { + if (result == null) + result = FlxPoint.get(); + if (camera == null) + camera = FlxG.camera; + + result.set(object.x, object.y); + result.x = (((result.x - camera.scroll.x * object.scrollFactor.x) * camera.zoom) - ((camera.width * 0.5) * (camera.zoom - camera.initialZoom))); + result.y = (((result.y - camera.scroll.y * object.scrollFactor.y) * camera.zoom) - ((camera.height * 0.5) * (camera.zoom - camera.initialZoom))); + return result; + } + + /** + * Returns the screen position of an point, while taking the camera zoom into account. + * + * @param object Any `FlxObject` + * @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used. + * @param result Optional arg for the returning point + * @return The screen position of the object. + */ + public static function pointToScreenPosition(object:FlxPoint, ?camera:FlxCamera, ?result:FlxPoint) { + if (result == null) + result = FlxPoint.get(); + if (camera == null) + camera = FlxG.camera; + + result.set(object.x, object.y); + result.x = (((result.x - camera.scroll.x) * camera.zoom) - ((camera.width * 0.5) * (camera.zoom - camera.initialZoom))); + result.y = (((result.y - camera.scroll.y) * camera.zoom) - ((camera.height * 0.5) * (camera.zoom - camera.initialZoom))); + + object.putWeak(); + return result; + } /** * Sorts an array alphabetically. @@ -913,15 +1070,35 @@ class CoolUtil r.add(str); return r.toString(); } + + public static inline function bound(Value:Float, Min:Float, Max:Float):Float { + #if cpp + var _hx_tmp1:Float = Value; + var _hx_tmp2:Float = Min; + var _hx_tmp3:Float = Max; + return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3); + #else + return (Value < Min) ? Min : (Value > Max) ? Max : Value; + #end + } - /* - * Returns the filename of a path, without the extension. - * @param path Path to get the filename from - * @return Filename - */ - @:noUsing public static inline function getFilename(file:String) { - var file = new haxe.io.Path(file); - return file.file; + public static inline function boundInt(Value:Int, Min:Int, Max:Int):Int { + #if cpp + var _hx_tmp1:Int = Value; + var _hx_tmp2:Int = Min; + var _hx_tmp3:Int = Max; + return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3); + #else + return (Value < Min) ? Min : (Value > Max) ? Max : Value; + #end + } + + public static inline function boolToInt(b:Bool):Int { + #if cpp + return untyped __cpp__("(({0}) ? 1 : 0)", b); + #else + return b ? 1 : 0; + #end } /** @@ -1028,6 +1205,301 @@ class CoolUtil } return result; } + + /** + * Gets the luminance of the given color + * @param color Color to use + * @return Number between 0 and 1 + */ + public static function getLuminance(color:FlxColor):Float { + return (0.2126*color.redFloat + 0.7152*color.greenFloat + 0.0722*color.blueFloat); + } + + /** + * ! REQUIRES FULL PATH!!! + * @param path + * @return Bool + */ + public static function imageHasFrameData(path:String):String { + if (FileSystem.exists(Path.withExtension(path, "xml"))) return "xml"; + if (FileSystem.exists(Path.withExtension(path, "txt"))) return "txt"; + if (FileSystem.exists(Path.withExtension(path, "json"))) return "json"; + + return null; + } + + // loads frames with no image, but with data, so you can parse it + public static function loadFramesFromData(data:String, ext:String = null):FlxFramesCollection { + var frames:FlxFramesCollection = null; + var tempBitmap:BitmapData = new BitmapData(1, 1, false); + + // Worlds hackiest work around alert??? + var graphic:FlxGraphic = FlxG.bitmap.add(tempBitmap); + @:privateAccess { + // prevent errors from being shown, such as Size is too small + graphic.width = 9999999; + graphic.height = 9999999; + } + + try { + switch (ext) { + case "xml": frames = FlxAtlasFrames.fromSparrow(graphic, Xml.parse(data)); + case "txt": frames = FlxAtlasFrames.fromSpriteSheetPacker(graphic, data); + case "json": frames = FlxAtlasFrames.fromAseprite(graphic, data); + } + } catch (e) {trace(e);} + + return frames; + } + + public static function removeBOM(str:String):String { + return StringTools.replace(str, String.fromCharCode(0xFEFF), ""); + } + + public static function getAnimsListFromFrames(frames:FlxFramesCollection, ext:String = null):Array<String> { + if (frames == null) return []; + + var animsList:Array<String> = []; + for (frame in frames.frames) { + var animName:String = ext == "txt" ? frame.name.split("_")[0] : frame.name.substr(0, frame.name.length-4); + if (!animsList.contains(animName)) + animsList.push(animName); + } + + return animsList; + } + + public static function getAnimsListFromAtlas(atlas:AnimAtlas):Array<String> { + if (atlas == null) return []; + + var animsList:Array<String> = []; + if (atlas.AN.SN != null) animsList.push(atlas.AN.SN); + if (atlas.SD != null) + for (symbol in atlas.SD.S) + if (symbol.SN != null) animsList.push(symbol.SN); + + return animsList; + } + + public static function getAnimsListFromSprite(spr:FunkinSprite):Array<String> { + if (spr.animateAtlas != null) { + return [for (symbol => timeline in spr.animateAtlas.anim.symbolDictionary) symbol]; + } else + return getAnimsListFromFrames(spr.frames); + } + + // TODO: check this for bugs + // Code from https://github.com/elnabo/equals/blob/master/src/equals/Equal.hx, (MIT License), but updated to work with haxe 4 + public static function deepEqual<T> (a:T, b:T) : Bool { + if (a == b) { return true; } // if physical equality + if (isNull(a) || isNull(b)) { + return false; + } + + switch (Type.typeof(a)) { + case TNull, TInt, TBool, TUnknown: + return a == b; + case TFloat: + return Math.isNaN(cast a) && Math.isNaN(cast b); // only valid true result remaining + case TFunction: + return Reflect.compareMethods(a, b); // only physical equality can be tested for function + case TEnum(_): + if (EnumValueTools.getIndex(cast a) != EnumValueTools.getIndex(cast b)) { + return false; + } + var a_args = EnumValueTools.getParameters(cast a); + var b_args = EnumValueTools.getParameters(cast b); + return deepEqual(a_args, b_args); + case TClass(_): + if ((a is String) && (b is String)) { + return a == b; + } + if ((a is Array) && (b is Array)) { + var a = cast(a, Array<Dynamic>); + var b = cast(b, Array<Dynamic>); + if (a.length != b.length) { return false; } + for (i in 0...a.length) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + return true; + } + + if ((a is IMap) && (b is IMap)) { + var a = cast(a, IMap<Dynamic, Dynamic>); + var b = cast(b, IMap<Dynamic, Dynamic>); + var a_keys = [ for (key in a.keys()) key ]; + var b_keys = [ for (key in b.keys()) key ]; + a_keys.sort(Reflect.compare); + b_keys.sort(Reflect.compare); + if (!deepEqual(a_keys, b_keys)) { return false; } + for (key in a_keys) { + if (!deepEqual(a.get(key), b.get(key))) { + return false; + } + } + return true; + } + + if ((a is Date) && (b is Date)) { + return cast(a, Date).getTime() == cast(b, Date).getTime(); + } + + if ((a is haxe.io.Bytes) && (b is haxe.io.Bytes)) { + return deepEqual(cast(a, haxe.io.Bytes).getData(), cast(b, haxe.io.Bytes).getData()); + } + + case TObject: + } + + for (field in Reflect.fields(a)) { + var pa = Reflect.field(a, field); + var pb = Reflect.field(b, field); + if (isFunction(pa)) { + // ignore function as only physical equality can be tested, unless null + if (isNull(pa) != isNull(pb)) { + return false; + } + continue; + } + if (!deepEqual(pa, pb)) { + return false; + } + } + + return true; + } + + static inline function isNull(a:Dynamic):Bool { + return Type.enumEq(Type.typeof(a), TNull); + } + + static inline function isFunction(a:Dynamic):Bool { + return Type.enumEq(Type.typeof(a), TFunction); + } + + public static inline function isMapEmpty<K, V>(map: Map<K, V>): Bool { + return !map.keys().hasNext(); + } + + public inline static function parsePropertyString(fieldPath:String):Array<OneOfTwo<String, Int>> { + return FlxTween.parseFieldString(fieldPath); + } + + public static function stringifyFieldsPath(fields:Array<OneOfTwo<String, Int>>):String { + var str = new StringBuf(); + var first = true; + for (field in fields) { + if (Type.typeof(field) == TInt) { + str.add('[${field}]'); + } else { + if (!first) + str.add('.'); + str.add(field); + } + first = false; + } + return str.toString(); + } + + public static function parseProperty(target:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>):Dynamic { + var fields:Array<OneOfTwo<String, Int>> = { + if((fields is String)) CoolUtil.parsePropertyString(fields); + else fields; + } + + var field = CoolUtil.last(fields); + for (i in 0...fields.length - 1) { + var component = fields[i]; + if (Type.typeof(component) == TInt) { + if ((target is Array)) { + var index:Int = cast component; + var arr:Array<Dynamic> = cast target; + target = arr[index]; + } + } else { // TClass(String) + target = Reflect.getProperty(target, component); + } + if (!Reflect.isObject(target) && !(target is Array)) + throw 'The object does not have the property "$component" in "${stringifyFieldsPath(fields)}"'; + } + return new PropertyInfo(target, field); + } + + public static function cloneProperty(toTarget:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>, fromTarget:Dynamic):Dynamic { + var fields:Array<OneOfTwo<String, Int>> = { + if((fields is String)) CoolUtil.parsePropertyString(fields); + else fields; + } + + var toProperty = CoolUtil.parseProperty(toTarget, fields); + var fromProperty = CoolUtil.parseProperty(fromTarget, fields); + + return toProperty.setValue(fromProperty.getValue()); + } +} + +class PropertyInfo { + public var object:Dynamic; + public var field:OneOfTwo<String, Int>; + public var typeOfField:Type.ValueType; + #if hscript_improved + public var isCustom:Bool = false; + public var custom:hscript.IHScriptCustomBehaviour; + #end + + public function new(object:Dynamic, field:OneOfTwo<String, Int>) { + this.object = object; + this.field = field; + #if hscript_improved + if (object is hscript.IHScriptCustomBehaviour) + { + isCustom = true; + custom = cast object; + } + #end + + typeOfField = Type.typeof(field); + } + + public function getValue():Dynamic + { + if (typeOfField == TInt) + { + var index:Int = cast field; + var arr:Array<Dynamic> = cast object; + return arr[index]; + } + else + { + #if hscript_improved + if (isCustom) + return custom.hget(field); + else + #end + return Reflect.getProperty(object, field); + } + } + + public function setValue(value:Dynamic):Void + { + if (typeOfField == TInt) + { + var index:Int = cast field; + var arr:Array<Dynamic> = cast object; + arr[index] = value; + } + else + { + #if hscript_improved + if (isCustom) + custom.hset(field, value); + else + #end + Reflect.setProperty(object, field, value); + } + } } /** diff --git a/source/funkin/backend/utils/DiscordUtil.hx b/source/funkin/backend/utils/DiscordUtil.hx index 42735a9c..282e5b2b 100644 --- a/source/funkin/backend/utils/DiscordUtil.hx +++ b/source/funkin/backend/utils/DiscordUtil.hx @@ -17,7 +17,7 @@ import lime.app.Application; import sys.thread.Thread; #end -class DiscordUtil +final class DiscordUtil { public static var currentID(default, set):String = null; public static var discordThread:#if DISCORD_RPC Thread #else Dynamic #end = null; @@ -89,6 +89,10 @@ class DiscordUtil config.logoKey = config.logoKey.getDefault(Flags.DEFAULT_DISCORD_LOGO_KEY); config.logoText = config.logoText.getDefault(Flags.DEFAULT_DISCORD_LOGO_TEXT); config.clientID = config.clientID.getDefault(Flags.DEFAULT_DISCORD_CLIENT_ID); + + if (Flags.MOD_DISCORD_CLIENT_ID.length > 0) config.clientID = Flags.MOD_DISCORD_CLIENT_ID; + if (Flags.MOD_DISCORD_LOGO_KEY.length > 0) config.logoKey = Flags.MOD_DISCORD_LOGO_KEY; + if (Flags.MOD_DISCORD_LOGO_TEXT.length > 0) config.logoText = Flags.MOD_DISCORD_LOGO_TEXT; currentID = config.clientID; #end } diff --git a/source/funkin/backend/utils/DrawUtil.hx b/source/funkin/backend/utils/DrawUtil.hx new file mode 100644 index 00000000..c21830c6 --- /dev/null +++ b/source/funkin/backend/utils/DrawUtil.hx @@ -0,0 +1,105 @@ +package funkin.backend.utils; + +import flixel.math.FlxRect; +import flixel.util.FlxColor; +import flixel.math.FlxAngle; +import flixel.math.FlxPoint; + +final class DrawUtil { + public static var line:FlxSprite = null; + public static var dot:FlxSprite = null; + public static var square:FlxSprite = null; + + public static inline function drawSquare(x:Float, y:Float, ?scale:Float = 1) { + if (square == null) createDrawers(); + + square.setPosition(x, y); + + square.scale.set(scale, scale); + square.updateHitbox(); + + square.x -= square.width / 2; + square.y -= square.height / 2; + square.draw(); + } + + public static inline function drawDot(x:Float, y:Float, ?scale:Float = 1) { + if (dot == null) createDrawers(); + + dot.setPosition(x, y); + + dot.scale.set(scale, scale); + dot.updateHitbox(); + + dot.x -= dot.width / 2; + dot.y -= dot.height / 2; + dot.draw(); + } + + public static inline function drawLine(point1:FlxPoint, point2:FlxPoint, thickness:Float = 1, ?color:Null<FlxColor>) { + if (line == null) createDrawers(); + + var dx:Float = point2.x - point1.x; + var dy:Float = point2.y - point1.y; + + var angle:Float = Math.atan2(dy, dx); + var distance:Float = Math.sqrt(dx * dx + dy * dy); + + // Math.ceil to prevent flickering + line.setPosition(point1.x, point1.y); + line.angle = angle * FlxAngle.TO_DEG; + line.origin.set(0, line.frameHeight / 2); + line.scale.x = distance / line.frameWidth; + line.scale.y = thickness; + line.y -= line.height / 2; + if (color != null) line.color = color; + line.draw(); + + line.angle = 0; + line.scale.x = line.scale.y = 1; + line.updateHitbox(); + + point1.putWeak(); point2.putWeak(); + } + + public static inline function drawRect(rect:FlxRect, thickness:Float = 1, ?color:Null<FlxColor>) { + if (rect.width <= 0 || rect.height <= 0) return; + DrawUtil.drawLine(FlxPoint.weak(rect.x, rect.y), FlxPoint.weak(rect.x + rect.width, rect.y), thickness, color); + DrawUtil.drawLine(FlxPoint.weak(rect.x, rect.y), FlxPoint.weak(rect.x, rect.y + rect.height), thickness, color); + DrawUtil.drawLine(FlxPoint.weak(rect.x + rect.width, rect.y), FlxPoint.weak(rect.x + rect.width, rect.y + rect.height), thickness, color); + DrawUtil.drawLine(FlxPoint.weak(rect.x, rect.y + rect.height), FlxPoint.weak(rect.x + rect.width, rect.y + rect.height), thickness, color); + } + + public static inline function createDrawers() { + dot = new FlxSprite().loadGraphic(Paths.image("editors/stage/selectionDot"), true, 32, 32); + dot.antialiasing = true; + dot.animation.add("default", [0], 0, false); + dot.animation.add("hollow", [1], 0, false); + dot.animation.play("default"); + dot.camera = FlxG.camera; + dot.forceIsOnScreen = true; + + line = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); + line.camera = FlxG.camera; + line.forceIsOnScreen = true; + + square = new FlxSprite().makeGraphic(32, 32, FlxColor.WHITE); + square.camera = FlxG.camera; + square.forceIsOnScreen = true; + } + + public static function destroyDrawers() { + if(dot != null) { + dot.destroy(); + dot = null; + } + if(line != null) { + line.destroy(); + line = null; + } + if(square != null) { + square.destroy(); + square = null; + } + } +} \ No newline at end of file diff --git a/source/funkin/backend/utils/EngineUtil.hx b/source/funkin/backend/utils/EngineUtil.hx index 9f0e9157..ebfe916c 100644 --- a/source/funkin/backend/utils/EngineUtil.hx +++ b/source/funkin/backend/utils/EngineUtil.hx @@ -3,7 +3,7 @@ package funkin.backend.utils; import funkin.backend.scripting.MultiThreadedScript; import funkin.backend.scripting.Script; -class EngineUtil { +final class EngineUtil { /** * Starts a new multithreaded script. * This script will share all the variables with the current one, which means already existing callbacks will be replaced by new ones on conflict. diff --git a/source/funkin/backend/utils/HttpUtil.hx b/source/funkin/backend/utils/HttpUtil.hx index 2daa96e2..be9d8ac3 100644 --- a/source/funkin/backend/utils/HttpUtil.hx +++ b/source/funkin/backend/utils/HttpUtil.hx @@ -2,7 +2,7 @@ package funkin.backend.utils; import haxe.Http; -class HttpUtil +final class HttpUtil { public static var userAgent:String = Flags.USER_AGENT; public static function requestText(url:String) diff --git a/source/funkin/backend/utils/IniUtil.hx b/source/funkin/backend/utils/IniUtil.hx index 5f13640b..22cecee4 100644 --- a/source/funkin/backend/utils/IniUtil.hx +++ b/source/funkin/backend/utils/IniUtil.hx @@ -1,42 +1,33 @@ package funkin.backend.utils; -typedef IniMap = Map<String, String>; - -/** - * Parses an ini file and returns a map of its contents. - * WARNING: This is not a full ini parser, it only supports the basics. So no sections - */ class IniUtil { - public static inline function parseAsset(assetPath:String, ?defaultVariables:IniMap):IniMap - return parseString(Assets.getText(assetPath), defaultVariables); + public static inline function parseAsset(assetPath:String) + return parseString(Assets.getText(assetPath)); - public static function parseString(data:String, ?defaultVariables:IniMap):IniMap { - var trimmed:String; - var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed]; + public static function parseString(data:String):Map<String, Map<String, String>> { + var res:Map<String, Map<String, String>> = []; + parseStringToMap(res, data); + return res; + } - var finalMap:IniMap = []; - if (defaultVariables != null) - for(k=>e in defaultVariables) - finalMap[k] = e; + public static function parseStringToMap(map:Map<String, Map<String, String>>, data:String) { + var section:Map<String, String> = null; - for(line in splitContent) { - // comment - if (line.startsWith(";")) continue; - // sections; not supported yet - if (line.startsWith("[") && line.endsWith("]")) continue; + var regexSec = ~/\\[(.+)\\]/g, regexVal = ~/(.+)=(.+)/g, quote = ~/[\\'"](.+)[\\'"]/g, iln = 0; + do { + var line = data.substring(iln, ((iln = data.indexOf('\n', iln) + 1) == 0 ? data.length : iln - 1)); + if (regexSec.match(line)) { + if ((section = map.get(regexSec.matched(1))) == null) map.set(regexSec.matched(1), section = []); + } + else if (regexVal.match(line)) { + var s = regexVal.matched(2).trim(); + if (quote.match(s)) s = quote.matched(1); - var index = line.indexOf("="); - var name = line.substr(0, index).trim(); - var value = line.substr(index+1).trim(); - - if (value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code) - value = value.substr(1, value.length - 2); - - if (value.length == 0 || name.length == 0) - continue; - - finalMap[name] = value; - } - return finalMap; + if (section == null) { + if ((section = map.get("Global")) == null) map.set("Global", section = []); + } + section.set(regexVal.matched(1).trim(), s); + } + } while (iln != 0); } } \ No newline at end of file diff --git a/source/funkin/backend/utils/MarkdownUtil.hx b/source/funkin/backend/utils/MarkdownUtil.hx index c3692f38..e481198d 100644 --- a/source/funkin/backend/utils/MarkdownUtil.hx +++ b/source/funkin/backend/utils/MarkdownUtil.hx @@ -3,7 +3,7 @@ package funkin.backend.utils; import flixel.text.FlxText; import flixel.util.FlxColor; -class MarkdownUtil { +final class MarkdownUtil { public static function applyMarkdownText(text:FlxText, str:String) { text.textField.htmlText = Markdown.markdownToHtml(prepareMarkdown(str)); var changes:Array<FlxTextFormatMarkerPair> = [ diff --git a/source/funkin/backend/utils/MathUtil.hx b/source/funkin/backend/utils/MathUtil.hx new file mode 100644 index 00000000..2ca2f5a2 --- /dev/null +++ b/source/funkin/backend/utils/MathUtil.hx @@ -0,0 +1,202 @@ +package funkin.backend.utils; + +import haxe.macro.Expr; + +final class MathUtil { + /** + * Returns the maximum value in the arguments. + * @param args Array of values + * + * @return The maximum value + **/ + public static function maxInt(...args:Int):Int { + var max = args[0]; + for(i in 1...args.length) { + var arg = args[i]; + if(arg > max) + max = arg; + } + return max; + } + + /** + * Returns the minimum value in the arguments. + * @param args Array of values + * + * @return The minimum value + **/ + public static function minInt(...args:Int):Int { + var min = args[0]; + for(i in 1...args.length) { + var arg = args[i]; + if(arg < min) + min = arg; + } + return min; + } + + /** + * Returns the maximum value in the arguments. + * + * NOTE: If you are using this in compile time, you should use `MathUtil.maxSmart` instead of this for better performance. + * + * @param args Array of values + * + * @return The maximum value + **/ + public static function max(...args:Float):Float { + var max = args[0]; + for(i in 1...args.length) { + var arg = args[i]; + if(arg > max) + max = arg; + } + return max; + } + + /** + * Returns the minimum value in the arguments. + * + * NOTE: If you are using this in compile time, you should use `MathUtil.minSmart` instead of this for better performance. + * + * @param args Array of values + * + * @return The minimum value + **/ + public static function min(...args:Float):Float { + var min = args[0]; + for(i in 1...args.length) { + var arg = args[i]; + if(arg < min) + min = arg; + } + return min; + } + + /** + * Checks if a is less than b with considering a margin of error. + * + * @param a Float + * @param b Float + * @param margin Float (Default: EPSILON) + * + * @return Bool + **/ + public static function lessThan(a:Float, b:Float, margin:Float = 0.0000001):Bool { + return a < b - margin; + } + + /** + * Checks if a is less than or equally b with considering a margin of error. + * + * @param a Float + * @param b Float + * @param margin Float (Default: EPSILON) + * + * @return Bool + **/ + public static function lessThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool { + return a <= b - margin; + } + + /** + * Checks if a is greater than b with considering a margin of error. + * + * @param a Float + * @param b Float + * @param margin Float (Default: EPSILON) + * + * @return Bool + **/ + public static function greaterThan(a:Float, b:Float, margin:Float = 0.0000001):Bool { + return a > b + margin; + } + + /** + * Checks if a is greater than or equally b with considering a margin of error. + * + * @param a Float + * @param b Float + * @param margin Float (Default: EPSILON) + * + * @return Bool + **/ + public static function greaterThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool { + return a >= b + margin; + } + + /** + * Checks if a is approximately equal to b. + * + * @param a Float + * @param b Float + * @param margin Float (Default: EPSILON) + * + * @return Bool + **/ + public static function equal(a:Float, b:Float, margin:Float = 0.0000001):Bool { + return Math.abs(a - b) <= margin; + } + + /** + * Checks if a are not approximately equal to b. + * + * @param a Float + * @param b Float + * @param margin Float (Default: EPSILON) + * + * @return Bool + **/ + public static function notEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool { + return Math.abs(a - b) > margin; + } + + /** + * Shortcut to `Math.max` but with infinite amount of arguments + * + * Might not preserve the order of arguments, please test this. + * + * Dont use this in hscript, it doesnt work, it only works on compile time + **/ + @:dox(hide) public static macro function maxSmart(..._args:Expr):Expr { + return genericMinMaxSmart(_args.toArray(), "Math.max"); + } + + /** + * Shortcut to `Math.min` but with infinite amount of arguments + * + * Might not preserve the order of arguments, please test this. + * + * Dont use this in hscript, it doesnt work, it only works on compile time + **/ + @:dox(hide) public static macro function minSmart(..._args:Expr):Expr { + return genericMinMaxSmart(_args.toArray(), "Math.min"); + } + + #if macro + @:dox(hide) private static function genericMinMaxSmart(_args:Array<Expr>, funcPath:String):Expr { + var args = _args.copy(); + if (args.length == 0) return macro 0; + + var func = funcPath.split("."); + + function nested(lst:Array<Expr>):Expr { + if (lst.length == 1) { + return macro ${lst[0]}; + } else if (lst.length == 2) { + return macro $p{func}(${lst[0]}, ${lst[1]}); + } else { + var mid = Std.int(lst.length / 2); + return macro $p{func}(${nested(lst.slice(0, mid))}, ${nested(lst.slice(mid, lst.length))}); + } + } + + var expr = nested(args); + + //var printer = new haxe.macro.Printer(); + //trace(printer.printExpr(expr)); + + return macro $expr; + } + #end +} \ No newline at end of file diff --git a/source/funkin/backend/utils/MatrixUtil.hx b/source/funkin/backend/utils/MatrixUtil.hx new file mode 100644 index 00000000..851d5dbb --- /dev/null +++ b/source/funkin/backend/utils/MatrixUtil.hx @@ -0,0 +1,223 @@ +package funkin.backend.utils; + +import flixel.graphics.frames.FlxFrame; +import flixel.FlxCamera; +import flixel.math.FlxPoint; +import flixel.util.typeLimit.OneOfTwo; +import funkin.backend.system.FakeCamera; +import funkin.backend.system.FakeCamera.FakeCallCamera; +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxDrawTrianglesItem; +import flixel.math.FlxMatrix; +import flixel.math.FlxPoint; +import flixel.system.FlxAssets.FlxShader; +import openfl.display.BitmapData; +import openfl.display.BlendMode; +import openfl.geom.ColorTransform; +import openfl.geom.Point; +import openfl.geom.Rectangle; + +interface IPrePostDraw { + public function preDraw():Void; + public function postDraw():Void; +} + +@:access(flixel.FlxCamera) +@:access(flixel.FlxSprite) +@:access(flixel.math.FlxMatrix) +@:access(openfl.geom.Matrix) +final class MatrixUtil { + public static function getMatrixPosition(sprite:FlxSprite, points:OneOfTwo<FlxPoint, Array<FlxPoint>>, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array<FlxPoint> + { + //if(_width == -1) _width = sprite.width; + //if(_height == -1) _height = sprite.height; + if(camera == null) camera = sprite.camera; + if(points is FlxBasePoint) points = [points]; + var isFunkinSprite = sprite is FunkinSprite; + var funkinSprite:FunkinSprite = null; + if(isFunkinSprite) funkinSprite = cast sprite; + var isAnimateAtlas = isFunkinSprite && funkinSprite.animateAtlas != null; + + var nc:FakeCamera = isAnimateAtlas ? FakeCallCamera.instance : FakeCamera.instance; + nc.zoom = camera.zoom; + nc.scroll.set(camera.scroll.x, camera.scroll.y); + nc.pixelPerfectRender = camera.pixelPerfectRender; + + var points:Array<FlxPoint> = cast points; + + if(isAnimateAtlas) { + var cnc = FakeCallCamera.instance; + var sprite = funkinSprite; + var oldOnDraw = cnc.onDraw; + + // TODO: fix rotation + // TODO: fix skew + + var boundsTopLeft = new FlxPoint(Math.POSITIVE_INFINITY, Math.POSITIVE_INFINITY); + var boundsTopRight = new FlxPoint(Math.NEGATIVE_INFINITY, Math.POSITIVE_INFINITY); + var boundsBottomLeft = new FlxPoint(Math.POSITIVE_INFINITY, Math.NEGATIVE_INFINITY); + var boundsBottomRight = new FlxPoint(Math.NEGATIVE_INFINITY, Math.NEGATIVE_INFINITY); + + cnc.onDraw = function(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) { + // cnc is already perfect rn + // so we need to manually process the limb + var points = [ + // corners + FlxPoint.get(0, 0), + FlxPoint.get(1, 0), + FlxPoint.get(0, 1), + FlxPoint.get(1, 1) + ]; + + rawTransformPoints(points, matrix, frame.sourceSize.x, frame.sourceSize.y); + + // sort the points, incase like they were transformed with a scale -1 matrix + + var topLeft:FlxPoint = points[0]; + var topRight:FlxPoint = points[1]; + var bottomLeft:FlxPoint = points[2]; + var bottomRight:FlxPoint = points[3]; + + if (points[0].y > points[1].y) { + topLeft = points[1]; + topRight = points[0]; + } + if (points[2].y > points[3].y) { + bottomLeft = points[3]; + bottomRight = points[2]; + } + + if (topLeft.x > topRight.x) { + var temp = topLeft; + topLeft = topRight; + topRight = temp; + } + + if (bottomLeft.x > bottomRight.x) { + var temp = bottomLeft; + bottomLeft = bottomRight; + bottomRight = temp; + } + + // points should now contain the corners of the limb + + // trace("Corners of limb: " + points); + + // set the max bounds of each corner of the limb + if(topLeft.x < boundsTopLeft.x) boundsTopLeft.x = topLeft.x; + if(topLeft.y < boundsTopLeft.y) boundsTopLeft.y = topLeft.y; + if(topRight.x > boundsTopRight.x) boundsTopRight.x = topRight.x; + if(topRight.y < boundsTopRight.y) boundsTopRight.y = topRight.y; + if(bottomLeft.x < boundsBottomLeft.x) boundsBottomLeft.x = bottomLeft.x; + if(bottomLeft.y > boundsBottomLeft.y) boundsBottomLeft.y = bottomLeft.y; + if(bottomRight.x > boundsBottomRight.x) boundsBottomRight.x = bottomRight.x; + if(bottomRight.y > boundsBottomRight.y) boundsBottomRight.y = bottomRight.y; + + // recycle the points + for(point in points) point.put(); + } + var cameras = sprite._cameras; + var oldVisible = cnc.visible; + cnc.visible = true; + sprite._cameras = [cnc]; + sprite.draw(); + sprite._cameras = cameras; + cnc.visible = oldVisible; + cnc.onDraw = oldOnDraw; + + // fake transform + + //trace(""); + //trace("topLeft: " + boundsTopLeft); + //trace("topRight: " + boundsTopRight); + //trace("bottomLeft: " + boundsBottomLeft); + //trace("bottomRight: " + boundsBottomRight); + //trace("Before transform"); + //trace(points); + + for(point in points) { + //var x = matrix.__transformX(point.x * _width, point.y * _height); + //var y = matrix.__transformY(point.x * _width, point.y * _height); + // apply using boundsTopLeft, boundsTopRight, boundsBottomLeft, boundsBottomRight + // matrix doesnt work for this + var x = FlxMath.lerp( + FlxMath.lerp(boundsTopLeft.x, boundsTopRight.x, point.x), + FlxMath.lerp(boundsBottomLeft.x, boundsBottomRight.x, point.x), + point.y + ); + var y = FlxMath.lerp( + FlxMath.lerp(boundsTopLeft.y, boundsTopRight.y, point.x), + FlxMath.lerp(boundsBottomLeft.y, boundsBottomRight.y, point.x), + point.y + ); + + //trace("(" + point.x + ", " + point.y + ") -> (" + x + ", " + y + ")"); + + // reset to ingame coords + x += camera.scroll.x; + y += camera.scroll.y; + + if(isFunkinSprite) { + var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor); + x += camera.width / 2 * ratio; + y += camera.height / 2 * ratio; + } + point.set(x, y); + } + + //trace("After transform"); + //trace(points); + } else { + if(sprite is IPrePostDraw) { + var postDraw = cast(sprite, IPrePostDraw); + postDraw.preDraw(); + sprite.drawComplex(nc); + postDraw.postDraw(); + } else { + sprite.drawComplex(nc); + } + transformPoints(sprite, points, sprite._matrix, camera, _width, _height); + } + + return points; + } + + /** + * Warning: modifies the points in the array + **/ + public static function transformPoints(sprite:FlxSprite, points:Array<FlxPoint>, matrix:FlxMatrix, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1, doCameraTransform:Bool = true):Array<FlxPoint> { + var isFunkinSprite = sprite is FunkinSprite; + var funkinSprite:FunkinSprite = null; + if(isFunkinSprite) funkinSprite = cast sprite; + + for(point in points) { + var x = matrix.__transformX(point.x * _width, point.y * _height); + var y = matrix.__transformY(point.x * _width, point.y * _height); + + if(doCameraTransform) { + // reset to ingame coords + x += camera.scroll.x; + y += camera.scroll.y; + + if(isFunkinSprite) { + var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, funkinSprite.zoomFactor); + x += camera.width / 2 * ratio; + y += camera.height / 2 * ratio; + } + } + point.set(x, y); + } + return points; + } + + private static function rawTransformPoints(points:Array<FlxPoint>, matrix:FlxMatrix, _width:Float = 1, _height:Float = 1):Array<FlxPoint> { + for(point in points) { + var x = matrix.__transformX(point.x * _width, point.y * _height); + var y = matrix.__transformY(point.x * _width, point.y * _height); + + point.set(x, y); + } + return points; + } +} \ No newline at end of file diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx index 0d42e475..2de56184 100644 --- a/source/funkin/backend/utils/MemoryUtil.hx +++ b/source/funkin/backend/utils/MemoryUtil.hx @@ -20,7 +20,7 @@ using StringTools; * * DISCLAIMER: Hardware info is only available on Native platforms. **/ -class MemoryUtil { +final class MemoryUtil { public static var disableCount:Int = 0; public static function askDisable() { @@ -88,14 +88,18 @@ class MemoryUtil { */ public static function getTotalMem():Float { - #if windows - return funkin.backend.utils.native.Windows.getTotalRam(); - #elseif mac - return funkin.backend.utils.native.Mac.getTotalRam(); - #elseif linux - return funkin.backend.utils.native.Linux.getTotalRam(); + #if cpp + #if windows + return funkin.backend.utils.native.Windows.getTotalRam(); + #elseif mac + return funkin.backend.utils.native.Mac.getTotalRam(); + #elseif linux + return funkin.backend.utils.native.Linux.getTotalRam(); + #else + return 0; + #end #else - return 0; + return 0; #end } @@ -123,7 +127,7 @@ class MemoryUtil { public static function getMemType():String { #if windows var memoryMap:Map<Int, String> = [ - 0 => "Unknown", + 0 => null, 1 => "Other", 2 => "DRAM", 3 => "Synchronous DRAM", @@ -162,9 +166,9 @@ class MemoryUtil { ]; var memoryOutput:Int = -1; - var process = new HiddenProcess("wmic", ["memorychip", "get", "SMBIOSMemoryType"]); + var process = new HiddenProcess("powershell", ["-Command", "Get-CimInstance Win32_PhysicalMemory | Select-Object -ExpandProperty SMBIOSMemoryType" ]); if (process.exitCode() == 0) memoryOutput = Std.int(Std.parseFloat(process.stdout.readAll().toString().trim().split("\n")[1])); - if (memoryOutput != -1) return memoryMap[memoryOutput]; + if (memoryOutput != -1) return memoryMap[memoryOutput] == null ? 'Unknown ($memoryOutput)' : memoryMap[memoryOutput]; #elseif mac var process = new HiddenProcess("system_profiler", ["SPMemoryDataType"]); var reg = ~/Type: (.+)/; diff --git a/source/funkin/backend/utils/NativeAPI.hx b/source/funkin/backend/utils/NativeAPI.hx index 143497df..d3b5e758 100644 --- a/source/funkin/backend/utils/NativeAPI.hx +++ b/source/funkin/backend/utils/NativeAPI.hx @@ -1,5 +1,8 @@ package funkin.backend.utils; +import haxe.macro.Expr.Case; +import openfl.ui.Mouse; +import openfl.ui.MouseCursor; import funkin.backend.utils.native.*; import flixel.util.typeLimit.OneOfTwo; import flixel.util.typeLimit.OneOfThree; @@ -200,6 +203,17 @@ class NativeAPI { #end } + /** + * Set cursor icon. + **/ + public static function setCursorIcon(icon:CodeCursor) { + #if (mac && cpp) + Mac.setMouseCursorIcon(cast icon); + #else + Mouse.cursor = icon.toOpenFL(); + #end + } + public static function consoleColorToANSI(color:ConsoleColor) { return switch(color) { case BLACK: 30; @@ -289,4 +303,102 @@ enum abstract MessageBoxIcon(Int) { var MSG_QUESTION = 0x00000020; var MSG_WARNING = 0x00000030; var MSG_INFORMATION = 0x00000040; +} + +enum abstract CodeCursor(String) { + var CUSTOM;// = "arrow"; + var ARROW;// = "arrow"; + var CLICK;// = "click"; + var CROSSHAIR;// = "crosshair"; + var HAND;// = "hand"; + var IBEAM;// = "ibeam"; + var MOVE;// = "move"; + + var RESIZE_H;// = "resize_we"; + var RESIZE_V;// = "resize_ns"; + var RESIZE_TL;// = "resize_nw"; + var RESIZE_TR;// = "resize_ne"; + var RESIZE_BL;// = "resize_sw"; + var RESIZE_BR;// = "resize_se"; + var RESIZE_T;// = "resize_n"; + var RESIZE_B;// = "resize_b"; + var RESIZE_L;// = "resize_w"; + var RESIZE_R;// = "resize_e"; + + var RESIZE_TLBR;// = "resize_nw_se"; + var RESIZE_TRBL;// = "resize_ne_sw"; + + var WAIT;// = "wait"; + var WAIT_ARROW;// = "waitarrow"; + var DISABLED;// = "disabled"; + var DRAG;// = "drag"; + var DRAG_OPEN;// = "dragopen"; + + @:to public function toOpenFL():MouseCursor { + return @:privateAccess switch(cast this) { + case ARROW: MouseCursor.ARROW; + case CROSSHAIR: MouseCursor.__CROSSHAIR; + case CLICK: MouseCursor.BUTTON; + case IBEAM: MouseCursor.IBEAM; + case MOVE: MouseCursor.__MOVE; + case HAND: MouseCursor.HAND; + case DRAG: MouseCursor.HAND; + case DRAG_OPEN: MouseCursor.ARROW; // Could be HAND, but it might be better to use ARROW since on windows it would be weird to have the dragging cursor + case WAIT: MouseCursor.__WAIT; + case WAIT_ARROW: MouseCursor.__WAIT_ARROW; + + case DISABLED: MouseCursor.ARROW; + + case RESIZE_TR: MouseCursor.__RESIZE_NESW; + case RESIZE_BL: MouseCursor.__RESIZE_NESW; + case RESIZE_TL: MouseCursor.__RESIZE_NWSE; + case RESIZE_BR: MouseCursor.__RESIZE_NWSE; + case RESIZE_H: MouseCursor.__RESIZE_WE; + case RESIZE_V: MouseCursor.__RESIZE_NS; + + case RESIZE_T: MouseCursor.__RESIZE_NS; + case RESIZE_B: MouseCursor.__RESIZE_NS; + case RESIZE_L: MouseCursor.__RESIZE_WE; + case RESIZE_R: MouseCursor.__RESIZE_WE; + + case RESIZE_TLBR: MouseCursor.__RESIZE_NWSE; + case RESIZE_TRBL: MouseCursor.__RESIZE_NESW; + case CUSTOM: MouseCursor.__CUSTOM; + //default: ARROW; + } + } + + @:to public function toInt():Int { + return switch(cast this) { + case ARROW: 0; + case CROSSHAIR: 1; + case CLICK: 2; + case IBEAM: 3; + case MOVE: 4; + case HAND: 5; + case DRAG: 6; + case DRAG_OPEN: 7; + case WAIT: 8; + case WAIT_ARROW: 9; + + case DISABLED: 10; + + case RESIZE_TR: 11; + case RESIZE_BL: 12; + case RESIZE_TL: 13; + case RESIZE_BR: 14; + case RESIZE_H: 15; + case RESIZE_V: 16; + + case RESIZE_T: 17; + case RESIZE_B: 18; + case RESIZE_L: 19; + case RESIZE_R: 20; + + case RESIZE_TLBR: 21; + case RESIZE_TRBL: 22; + + case CUSTOM: -1; + } + } } \ No newline at end of file diff --git a/source/funkin/backend/utils/NdllUtil.hx b/source/funkin/backend/utils/NdllUtil.hx index 2dd1c9f8..58db3e64 100644 --- a/source/funkin/backend/utils/NdllUtil.hx +++ b/source/funkin/backend/utils/NdllUtil.hx @@ -14,7 +14,7 @@ package funkin.backend.utils; * - The Function cannot be found in the NDLL * then an empty function will be returned instead, and a message will be shown in logs. */ -class NdllUtil { +final class NdllUtil { #if NDLLS_SUPPORTED #if windows public static final os:String = "windows"; #end #if linux public static final os:String = "linux"; #end diff --git a/source/funkin/backend/utils/RegistryUtil.hx b/source/funkin/backend/utils/RegistryUtil.hx new file mode 100644 index 00000000..722eb964 --- /dev/null +++ b/source/funkin/backend/utils/RegistryUtil.hx @@ -0,0 +1,120 @@ +package funkin.backend.utils; + +enum abstract RegistryHive(Int) { + var HKEY_CLASSES_ROOT = 0x80000000; + var HKEY_CURRENT_USER = 0x80000001; + var HKEY_LOCAL_MACHINE = 0x80000002; + var HKEY_USERS = 0x80000003; + var HKEY_CURRENT_CONFIG = 0x80000005; +} +#if windows +@:cppFileCode(' +#include <windows.h> +#include <tchar.h> +#include <string> +#include <vector> +') +#end +class RegistryUtil { + #if windows + @:functionCode(' + HKEY hKey; + LONG result; + DWORD dataSize = 0; + DWORD dataType = 0; + + std::wstring subkey = std::wstring(key.wchar_str()); + std::wstring valname = std::wstring(string.wchar_str()); + + result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey); + if (result != ERROR_SUCCESS) return null(); + + result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, &dataSize); + if (result != ERROR_SUCCESS || dataSize == 0) { + RegCloseKey(hKey); + return null(); + } + + std::vector<wchar_t> buffer(dataSize / sizeof(wchar_t)); + result = RegQueryValueExW(hKey, valname.c_str(), NULL, NULL, (LPBYTE)buffer.data(), &dataSize); + RegCloseKey(hKey); + + if (result == ERROR_SUCCESS) { + return ::String(buffer.data()); + } + return null(); + ') + #end + public static function get(hive:RegistryHive, key:String, string:String):Null<String> + { + return null; + } + + #if windows + @:functionCode(' + HKEY hKey; + LONG result; + + std::wstring subkey = std::wstring(key.wchar_str()); + std::wstring valname = std::wstring(string.wchar_str()); + std::wstring data = std::wstring(value.wchar_str()); + + result = RegCreateKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL); + if (result != ERROR_SUCCESS) return false; + + result = RegSetValueExW(hKey, valname.c_str(), 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(wchar_t))); + RegCloseKey(hKey); + + return result == ERROR_SUCCESS; + ') + #end + public static function set(hive:RegistryHive, key:String, string:String, value:String):Bool + { + return false; + } + + #if windows + @:functionCode(' + HKEY hKey; + LONG result; + + std::wstring subkey = std::wstring(key.wchar_str()); + std::wstring valname = std::wstring(string.wchar_str()); + + result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey); + if (result != ERROR_SUCCESS) return false; + + DWORD dataType = 0; + result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, NULL); + + RegCloseKey(hKey); + + return result == ERROR_SUCCESS; + ') + #end + public static function exists(hive:RegistryHive, key:String, string:String):Bool + { + return false; + } + + #if windows + @:functionCode(' + HKEY hKey; + LONG result; + + std::wstring subkey = std::wstring(key.wchar_str()); + std::wstring valname = std::wstring(string.wchar_str()); + + result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_SET_VALUE, &hKey); + if (result != ERROR_SUCCESS) return false; + + result = RegDeleteValueW(hKey, valname.c_str()); + RegCloseKey(hKey); + + return result == ERROR_SUCCESS; + ') + #end + public static function delete(hive:RegistryHive, key:String, string:String):Bool { + return false; + } +} \ No newline at end of file diff --git a/source/funkin/backend/utils/ShaderResizeFix.hx b/source/funkin/backend/utils/ShaderResizeFix.hx index 96242859..97fc73fe 100644 --- a/source/funkin/backend/utils/ShaderResizeFix.hx +++ b/source/funkin/backend/utils/ShaderResizeFix.hx @@ -2,7 +2,7 @@ package funkin.backend.utils; import openfl.display.Sprite; -class ShaderResizeFix { +final class ShaderResizeFix { public static var doResizeFix:Bool = true; public static function init() { diff --git a/source/funkin/backend/utils/SortedArrayUtil.hx b/source/funkin/backend/utils/SortedArrayUtil.hx index 7873711a..1eda2ddb 100644 --- a/source/funkin/backend/utils/SortedArrayUtil.hx +++ b/source/funkin/backend/utils/SortedArrayUtil.hx @@ -5,7 +5,7 @@ package funkin.backend.utils; * * DISCLAIMER: All types of utils only usable in a sorted array. */ -class SortedArrayUtil { +final class SortedArrayUtil { /** * Gets the index of a possible new element of an Array of T using an efficient algorithm. * @param array Array of T to check in diff --git a/source/funkin/backend/utils/ThreadUtil.hx b/source/funkin/backend/utils/ThreadUtil.hx index fe2cb5bf..8c2f654c 100644 --- a/source/funkin/backend/utils/ThreadUtil.hx +++ b/source/funkin/backend/utils/ThreadUtil.hx @@ -1,7 +1,7 @@ package funkin.backend.utils; #if ALLOW_MULTITHREADING -class ThreadUtil { +final class ThreadUtil { /** * Creates a new Thread with an error handler. * @param func Function to execute diff --git a/source/funkin/backend/utils/TranslationUtil.hx b/source/funkin/backend/utils/TranslationUtil.hx index 2a3bfcfe..6526096c 100644 --- a/source/funkin/backend/utils/TranslationUtil.hx +++ b/source/funkin/backend/utils/TranslationUtil.hx @@ -38,7 +38,7 @@ final class TranslationUtil /** * Returns the current language config. */ - public static var config:IniMap = []; + public static var config:Map<String, String> = []; /** * Returns the current language. */ @@ -65,15 +65,15 @@ final class TranslationUtil // Private private static inline var LANG_FOLDER:String = "languages"; - private static var langConfigs:Map<String, IniMap> = []; + private static var langConfigs:Map<String, Map<String, String>> = []; private static var nameMap:Map<String, String> = []; private static inline function getDefaultNameMap():Map<String, String> { return [Flags.DEFAULT_LANGUAGE => Flags.DEFAULT_LANGUAGE_NAME]; } - private static inline function getDefaultLangConfigs():Map<String, IniMap> { + private static inline function getDefaultLangConfigs():Map<String, Map<String, String>> { return [Flags.DEFAULT_LANGUAGE => getDefaultConfig(Flags.DEFAULT_LANGUAGE)]; } - @:noUsing private static inline function getDefaultConfig(name:String):IniMap { + @:noUsing private static inline function getDefaultConfig(name:String):Map<String, String> { return ["name" => getLanguageName(name), "credits" => "", "version" => "1.0.0"]; } @@ -200,7 +200,9 @@ final class TranslationUtil var config = getDefaultConfig(lang); if(Assets.exists(path)) { - config = IniUtil.parseAsset(path, config); + var c = IniUtil.parseAsset(path); + for (key => value in c) + config[key] = value; } else { // if there was no config.ini, use the file name as the language name for(file in Paths.getFolderContent(mainPath + lang).sortAlphabetically()) { if(Path.extension(file) == "xml") { @@ -313,7 +315,7 @@ final class TranslationUtil return reverseMap.exists(name) ? reverseMap.get(name) : name; } - public static function getConfig(lang:String):IniMap { + public static function getConfig(lang:String):Map<String, String> { return langConfigs.exists(lang) ? langConfigs.get(lang) : getDefaultConfig(lang); } diff --git a/source/funkin/backend/utils/WindowUtils.hx b/source/funkin/backend/utils/WindowUtils.hx index 3a9f39ce..065a1573 100644 --- a/source/funkin/backend/utils/WindowUtils.hx +++ b/source/funkin/backend/utils/WindowUtils.hx @@ -2,7 +2,7 @@ package funkin.backend.utils; import openfl.Lib; -class WindowUtils { +final class WindowUtils { public static var title(default, set):String; private static function set_title(value:String):String { title = value; diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx index 89e77c8c..2199b966 100644 --- a/source/funkin/backend/utils/XMLUtil.hx +++ b/source/funkin/backend/utils/XMLUtil.hx @@ -34,7 +34,7 @@ typedef TextFormat = { text:String, format:Dynamic } * Class made to make XML parsing easier. * Used in Stage.hx, Character.hx, and more. */ -class XMLUtil { +final class XMLUtil { /** * Applies a property XML node to an object. * The format for the XML is as follows: @@ -114,12 +114,13 @@ class XMLUtil { * @param parentFolder The parent folder * @param defaultAnimType The default animation type */ - public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT):FunkinSprite { + public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, loadGraphic:Bool = true):FunkinSprite { if (parentFolder == null) parentFolder = ""; spr.name = node.getAtt("name"); spr.antialiasing = true; - spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true)); + if (loadGraphic) + spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true)); spr.spriteAnimType = defaultAnimType; if (node.has.type) { @@ -200,10 +201,15 @@ class XMLUtil { if(node.has.color) spr.color = FlxColor.fromString(node.getAtt("color")).getDefault(0xFFFFFFFF); + if(node.has.angle) + spr.angle = Std.parseFloat(node.getAtt("angle")).getDefault(spr.angle); + if (node.has.playOnCountdown) spr.skipNegativeBeats = node.att.playOnCountdown == "true"; if (node.has.beatInterval) spr.beatInterval = Std.parseInt(node.att.beatInterval); + if (node.has.interval) + spr.beatInterval = Std.parseInt(node.att.interval); if (node.has.beatOffset) spr.beatOffset = Std.parseInt(node.att.beatOffset); @@ -234,10 +240,10 @@ class XMLUtil { * @param cl The class to create (advanced) * @param args The arguments to pass to the class (advanced) */ - public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>):FunkinSprite { + public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>, loadGraphic:Bool = true):FunkinSprite { if(cl == null) cl = FunkinSprite; if(args == null) args = []; - return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType); + return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType, loadGraphic); } /** diff --git a/source/funkin/backend/utils/ZipUtil.hx b/source/funkin/backend/utils/ZipUtil.hx index d082e009..0e87fd55 100644 --- a/source/funkin/backend/utils/ZipUtil.hx +++ b/source/funkin/backend/utils/ZipUtil.hx @@ -1,5 +1,8 @@ package funkin.backend.utils; +import haxe.zip.Entry; +import haxe.zip.Reader; + #if sys #if (!macro && sys) import openfl.display.BitmapData; @@ -9,8 +12,6 @@ import haxe.Exception; import haxe.Json; import haxe.crypto.Crc32; import haxe.zip.Compress; -import haxe.zip.Entry; -import haxe.zip.Reader; import haxe.zip.Tools; import haxe.zip.Uncompress; import haxe.zip.Writer; @@ -23,7 +24,7 @@ using StringTools; // import ZipUtils; ZipUtils.uncompressZip(ZipUtils.openZip("E:\\Desktop\\test\\termination lua.ycemod"), "E:\\Desktop\\test\\uncompressed\\"); // import ZipUtils; var e = ZipUtils.createZipFile("file.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close(); -class ZipUtil { +final class ZipUtil { public static var bannedNames:Array<String> = [".git", ".gitignore", ".github", ".vscode", ".gitattributes", "readme.txt"]; /** @@ -135,12 +136,12 @@ class ZipUtil { if (prog == null) prog = new ZipProgress(); try { - var curPath:Array<String> = ['$path']; + var curPath:Array<String> = [path]; var destPath:Array<String> = []; if (prefix != "") { prefix = prefix.replace("\\", "/"); - while(prefix.charAt(0) == "/") prefix = prefix.substr(1); - while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1); + while(prefix.charCodeAt(0) == "/".code) prefix = prefix.substr(1); + while(prefix.charCodeAt(prefix.length-1) == "/".code) prefix = prefix.substr(0, prefix.length-1); destPath.push(prefix); } @@ -160,7 +161,7 @@ class ZipUtil { } else { // is file, put it in the list var zipPath = '$zipPath/$e'; - while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1); + while(zipPath.charCodeAt(0) == "/".code) zipPath = zipPath.substr(1); files.push(new StrNameLabel('$path/$e', zipPath)); } } @@ -227,20 +228,6 @@ class ZipProgress { public function new() {} } -class ZipReader extends Reader { - public var files:List<Entry>; - - public override function read() { - if (files != null) return files; - try { - var files = super.read(); - return this.files = files; - } catch(e) { - } - return new List<Entry>(); - } -} - class ZipWriter extends Writer { public function flush() { o.flush(); @@ -265,4 +252,18 @@ class StrNameLabel { this.label = label; } } -#end \ No newline at end of file +#end + +class ZipReader extends Reader { + public var files:List<Entry>; + + public override function read() { + if (files != null) return files; + try { + var files = super.read(); + return this.files = files; + } catch(e) { + } + return new List<Entry>(); + } +} \ No newline at end of file diff --git a/source/funkin/backend/utils/native/Linux.hx b/source/funkin/backend/utils/native/Linux.hx index 01e6a84b..661c5842 100644 --- a/source/funkin/backend/utils/native/Linux.hx +++ b/source/funkin/backend/utils/native/Linux.hx @@ -2,7 +2,8 @@ package funkin.backend.utils.native; #if linux @:cppFileCode("#include <stdio.h>") -class Linux { +@:dox(hide) +final class Linux { @:functionCode(' FILE *meminfo = fopen("/proc/meminfo", "r"); diff --git a/source/funkin/backend/utils/native/Mac.hx b/source/funkin/backend/utils/native/Mac.hx index fa68700a..afd9d589 100644 --- a/source/funkin/backend/utils/native/Mac.hx +++ b/source/funkin/backend/utils/native/Mac.hx @@ -1,8 +1,13 @@ package funkin.backend.utils.native; -#if mac -@:cppFileCode("#include <sys/sysctl.h>") -class Mac { +#if (mac && cpp) +import funkin.backend.utils.NativeAPI.CodeCursor; +import openfl.ui.Mouse; + +@:headerInclude('sys/sysctl.h') +@:dox(hide) +final class Mac +{ @:functionCode(' int mib [] = { CTL_HW, HW_MEMSIZE }; int64_t value = 0; @@ -17,5 +22,39 @@ class Mac { { return 0; } + + public static function setMouseCursorIcon(icon:CodeCursor):Void + { + final valid:Bool = external.ExternalMac.setCursorIcon(icon.toInt(), null, 0, 0); + + if (!valid) + Mouse.cursor = icon.toOpenFL(); + } } -#end \ No newline at end of file +#end + + +/* + +Cursor + +Description +The arrow cursor (arrow) +The I-beam cursor for indicating insertion points (iBeam) +The cross-hair cursor (crosshair) +The closed-hand cursor (closedHand) +The open-hand cursor (openHand) +The pointing-hand cursor (pointingHand) +The resize-left cursor (resizeLeft) +The resize-right cursor (resizeRight) +The resize-left-and-right cursor (resizeLeftRight) +The resize-up cursor (resizeUp) +The resize-down cursor (resizeDown) +The resize-up-and-down cursor (resizeUpDown) +The disappearing item cursor (disappearingItem) +The I-Beam text cursor for vertical layout (iBeamCursorForVerticalLayout). +The not allowed cursor (operationNotAllowed). +The drag link cursor (dragLink). +The drag copy cursor (dragCopy). +The contextual menu cursor (contextualMenu). +*/ diff --git a/source/funkin/backend/utils/native/Windows.hx b/source/funkin/backend/utils/native/Windows.hx index 9ec12843..78aa5e10 100644 --- a/source/funkin/backend/utils/native/Windows.hx +++ b/source/funkin/backend/utils/native/Windows.hx @@ -114,7 +114,7 @@ class AudioFixClient : public IMMNotificationClient { AudioFixClient *curAudioFix; ') @:dox(hide) -class Windows { +final class Windows { public static var __audioChangeCallback:Void->Void = function() { trace("test"); diff --git a/source/funkin/backend/week/Week.hx b/source/funkin/backend/week/Week.hx new file mode 100644 index 00000000..d9f36922 --- /dev/null +++ b/source/funkin/backend/week/Week.hx @@ -0,0 +1,92 @@ +package funkin.backend.week; + +import flixel.math.FlxPoint; +import flixel.util.FlxColor; +import funkin.backend.week.WeekData.WeekCharacter; +import funkin.backend.week.WeekData; +import haxe.xml.Access; + +class Week { + public static function loadWeek(weekName:String, loadCharactersData:Bool = true):WeekData { + var week:Access = null; + try week = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/weeks/$weekName'))).firstElement()) + catch(e) Logs.trace('Cannot parse week "$weekName.xml": ${Std.string(e)}', ERROR); + + if (week == null) return null; + if (!week.has.name) { + Logs.trace('Week "${weekName}" has no name attribute', WARNING); + return null; + } + + var weekObj:WeekData = { + xml: week, + name: week.att.name, + id: weekName, + sprite: week.getAtt('sprite').getDefault(weekName), + chars: [null, null, null], + songs: [], + difficulties: ['easy', 'normal', 'hard'], + bgColor: week.has.bgColor ? FlxColor.fromString(week.getAtt("bgColor")) : Flags.DEFAULT_WEEK_COLOR + }; + + var weekName = weekObj.name; + for (k => song in week.nodes.song) { + if (song == null) continue; + try { + var name = song.innerData.trim(); + if (name == "") { + Logs.trace('Song at index ${k} in week $weekName has no name. Skipping...', WARNING); + continue; + } + weekObj.songs.push({ + name: name, + hide: song.getAtt('hide').getDefault('false') == "true", + displayName: song.getAtt('displayName') + }); + } catch(e) { + Logs.trace('Song at index ${k} in week $weekName cannot contain any other XML nodes in its name.', WARNING); + continue; + } + } + + if (weekObj.songs.length <= 0) { + Logs.trace('Week $weekName has no songs.', WARNING); + return null; + } + + var diffNodes = week.nodes.difficulty; + if (diffNodes.length > 0) { + var diffs:Array<String> = [for (e in diffNodes) if (e.has.name) e.att.name]; + if (diffs.length > 0) weekObj.difficulties = diffs; + } + + if (week.has.chars) for (k => e in week.att.chars.split(",")) { + var trim = e.trim(); + weekObj.chars[k] = (trim == "" || e == "none" || e == "null") ? null : (loadCharactersData ? loadWeekCharacter(trim) : {name: trim}); + } + + return weekObj; + } + + public static function loadWeekCharacter(charName:String):WeekCharacter { + var char:Access = null; + try char = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/characters/$charName'))).firstElement()) + catch(e) Logs.trace('Cannot parse character "$charName.xml": ${Std.string(e)}', ERROR); + if (char == null) return null; + + if (!char.has.name) char.x.set("name", charName); + if (!char.has.sprite) char.x.set("sprite", 'menus/storymenu/characters/${char.att.name}'); + if (!char.has.updateHitbox) char.x.set("updateHitbox", "true"); + + return { + xml: char, + name: char.att.name, + spritePath: char.att.sprite, + scale: Std.parseFloat(char.getAtt('scale')).getDefault(1), + offset: FlxPoint.get( + Std.parseFloat(char.getAtt('x')).getDefault(0), + Std.parseFloat(char.getAtt('y')).getDefault(0) + ) + }; + } +} \ No newline at end of file diff --git a/source/funkin/backend/week/WeekData.hx b/source/funkin/backend/week/WeekData.hx new file mode 100644 index 00000000..7be40175 --- /dev/null +++ b/source/funkin/backend/week/WeekData.hx @@ -0,0 +1,31 @@ +package funkin.backend.week; + +import flixel.math.FlxPoint; +import flixel.util.FlxColor; +import haxe.xml.Access; + +typedef WeekData = { + var ?xml:Access; + var name:String; // name SHOULD NOT be used for loading week highscores, its just the name on the right side of the week, remember that next time!! - Nex + var id:String; // id IS instead for saving and loading!! - Nex + var sprite:String; + var chars:Array<WeekCharacter>; + var songs:Array<WeekSong>; + var difficulties:Array<String>; + var bgColor:FlxColor; +} + +typedef WeekCharacter = { + var ?xml:Access; + var name:String; + var ?spritePath:String; + var ?scale:Float; + var ?offset:FlxPoint; + // var frames:FlxFramesCollection; +} + +typedef WeekSong = { + var name:String; + var hide:Bool; + var ?displayName:String; +} \ No newline at end of file diff --git a/source/funkin/editors/DebugOptions.hx b/source/funkin/editors/DebugOptions.hx index a79c6898..5c45393e 100644 --- a/source/funkin/editors/DebugOptions.hx +++ b/source/funkin/editors/DebugOptions.hx @@ -36,6 +36,10 @@ class DebugOptionsScreen extends OptionsScreen { NativeAPI.allocConsole(); })); #end + add(new Checkbox( + "Resizable Editors", + "If checked, this will allow the editors to render beyond the base 1280x720 resolution of FNF (allowing for more detail and space...)", + "editorsResizable")); add(new Checkbox( getName("editorSFX"), getDesc("editorSFX"), diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx index 2b4f8d1a..ff349203 100644 --- a/source/funkin/editors/EditorPicker.hx +++ b/source/funkin/editors/EditorPicker.hx @@ -26,7 +26,7 @@ class EditorPicker extends MusicBeatSubstate { { name: "Stage Editor", id: "stage", - state: null + state: funkin.editors.stage.StageSelection }, #if (debug || debug_ui) { diff --git a/source/funkin/editors/EditorTreeMenu.hx b/source/funkin/editors/EditorTreeMenu.hx index 543b0555..7bf5a0fb 100644 --- a/source/funkin/editors/EditorTreeMenu.hx +++ b/source/funkin/editors/EditorTreeMenu.hx @@ -7,25 +7,28 @@ import funkin.options.*; class EditorTreeMenu extends TreeMenu { public var bg:FlxBackdrop; public var bgType:String = "default"; + public var bgMovement:FlxPoint = new FlxPoint(); public override function create() { super.create(); + UIState.setResolutionAware(); + FlxG.camera.fade(0xFF000000, 0.5, true); bg = new FlxBackdrop(); bg.loadGraphic(Paths.image('editors/bgs/${bgType}')); bg.antialiasing = true; + setBackgroundRotation(-5); add(bg); } + public inline function setBackgroundRotation(rotation:Float) { + bg.rotation = rotation; + bg.velocity.set(85, 0).degrees = bg.rotation; + } + public override function exit() { FlxG.switchState(new MainMenuState()); } - - public override function update(elapsed:Float) { - super.update(elapsed); - bg.x += elapsed * 125; - bg.y += elapsed * 125; - } } \ No newline at end of file diff --git a/source/funkin/editors/ModConfigWarning.hx b/source/funkin/editors/ModConfigWarning.hx new file mode 100644 index 00000000..a7a9037e --- /dev/null +++ b/source/funkin/editors/ModConfigWarning.hx @@ -0,0 +1,74 @@ +package funkin.editors; + +import funkin.backend.assets.ModsFolderLibrary; +import flixel.FlxState; + +class ModConfigWarning extends UIState { + public static var hadPopup:Bool = false; + + var library:ModsFolderLibrary = null; + var goToState:Class<FlxState>; + + public static inline var defaultModConfigText = +'[Common] +MOD_NAME="YOUR MOD NAME HERE" +MOD_DESCRIPTION="YOUR MOD DESCRIPTION HERE" + +# DO NOT EDIT!! this is used to check for version compatibility! +MOD_API_VERSION=1 + +MOD_DOWNLOAD_LINK="YOUR MOD PAGE LINK HERE" + +# Not supported yet +;MOD_ICON64="path/to/icon64.png" +;MOD_ICON32="path/to/icon32.png" +;MOD_ICON16="path/to/icon16.png" +;MOD_ICON="path/to/icon.ico" + +[Flags] +DISABLE_BETA_WARNING_SCREEN=true + +[Discord] +MOD_DISCORD_CLIENT_ID="" +MOD_DISCORD_LOGO_KEY="" +MOD_DISCORD_LOGO_TEXT=""'; + + public function new(library:ModsFolderLibrary, ?goToState:Class<FlxState>) { + super(); + this.library = library; + this.goToState = goToState != null ? goToState : funkin.menus.TitleState; + } + + override function createPost() { + super.createPost(); + hadPopup = true; + + var substate = new UIWarningSubstate("Missing mod folder configuration!", "Your mod is currently missing a mod config file!\n\nWould you like to automatically generate one?\n\n(PS: If this is not your mod please disable Developer mode to stop this popup from appearing.)", [ + { + label: "Not Now", + color: 0x969533, + onClick: function (_) { + MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false; + FlxG.switchState(cast Type.createInstance(goToState, [])); + } + }, + { + label: "Yes", + onClick: function(_) { + var path = '${library.folderPath}/data/config/modpack.ini'; + CoolUtil.safeSaveFile(path, defaultModConfigText); + openSubState(new UIWarningSubstate("Created mod config!", "Your mod config file has been created at " + path + "!", [ + { + label: "Ok", + onClick: function (_) { + MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false; + FlxG.switchState(cast Type.createInstance(goToState, [])); + } + }, + ], false)); + } + } + ], false); + openSubState(substate); + } +} \ No newline at end of file diff --git a/source/funkin/editors/SaveWarning.hx b/source/funkin/editors/SaveWarning.hx index b94c8c5f..df67a974 100644 --- a/source/funkin/editors/SaveWarning.hx +++ b/source/funkin/editors/SaveWarning.hx @@ -37,7 +37,7 @@ class SaveWarning { if (closingWindow) WindowUtils.resetClosing(); } else { WindowUtils.preventClosing = false; WindowUtils.resetClosing(); - Sys.exit(0); + openfl.system.System.exit(0); } } }, @@ -51,7 +51,7 @@ class SaveWarning { if (closingWindow) WindowUtils.resetClosing(); } else { WindowUtils.preventClosing = false; WindowUtils.resetClosing(); - Sys.exit(0); + openfl.system.System.exit(0); } } } diff --git a/source/funkin/editors/UIDebugState.hx b/source/funkin/editors/UIDebugState.hx index 3a0bf57f..e7270da6 100644 --- a/source/funkin/editors/UIDebugState.hx +++ b/source/funkin/editors/UIDebugState.hx @@ -1,9 +1,17 @@ package funkin.editors; +import funkin.editors.ui.old.OldUISliceSpriteTri; +import funkin.editors.ui.UITopMenu.UITopMenuButton; import flixel.tweens.FlxTween; import funkin.editors.ui.*; +import funkin.game.Character; class UIDebugState extends UIState { + public var topMenuSpr:UITopMenu; + + public var testingUIItems:Array<FlxSprite> = []; + public var testingUIHidden:Bool = false; + public override function create() { super.create(); @@ -14,7 +22,11 @@ class UIDebugState extends UIState { bg.scrollFactor.set(); add(bg); - add(new UITopMenu([ + var gf = new Character(0, 0, "gf"); + gf.dance(); + add(gf); + + add(topMenuSpr = new UITopMenu([ { label: "File", childs: [ @@ -45,15 +57,57 @@ class UIDebugState extends UIState { }, { label: "Help" + }, + { + label: "9-Splice Benchmark >", + childs: [ + { + label: "Hide Other UI Items", + onSelect: (t) -> { + testingUIHidden = !testingUIHidden; + t.icon = testingUIHidden ? 1 : 0; + + for (item in testingUIItems) + item.visible = !testingUIHidden; + }, + icon: testingUIHidden ? 1 : 0 + }, + { + label: "Clear Benchmark Sprites", + onSelect: (t) -> { + for (splice in spliceSprites) { + splice.kill(); + splice.destroy(); + + this.remove(splice); + } + + spliceSprites.resize(0); + MemoryUtil.clearMajor(); + } + }, + null, + { + label: "Draw Triangles?", + onSelect: (t) -> { + spliceUseTris = !spliceUseTris; + t.icon = spliceUseTris ? 1 : 0; + + spliceCl = spliceUseTris ? OldUISliceSpriteTri : UISliceSprite; + trace(spliceCl); + }, + icon: spliceUseTris ? 1 : 0 + } + ] } ])); - add(new UICheckbox(10, 40, "Test unchecked", false)); - add(new UICheckbox(10, 70, "Test checked", true)); - add(new UIButton(10, 100, "Test button", function() { + testingUIItems.push(cast add(new UICheckbox(10, 40, "Test unchecked", false))); + testingUIItems.push(cast add(new UICheckbox(10, 70, "Test checked", true))); + testingUIItems.push(cast add(new UIButton(10, 100, "Test button", function() { trace("Hello, World!"); - }, 130, 32)); - add(new UIButton(10, 140, "Warning test", function() { + }, 130, 32))); + testingUIItems.push(cast add(new UIButton(10, 140, "Warning test", function() { openSubState(new UIWarningSubstate("Test", "This is a test message", [ { label: "Alt. Choice", @@ -68,8 +122,8 @@ class UIDebugState extends UIState { } } ])); - }, 130, 32)); - add(new UIButton(10, 180, "Warning test (Overflowing)", function() { + }, 130, 32))); + testingUIItems.push(cast add(new UIButton(10, 180, "Warning test (Overflowing)", function() { openSubState(new UIWarningSubstate("Test", "This is a test message", [ { label: "Alt. Choice", @@ -98,18 +152,54 @@ class UIDebugState extends UIState { onClick: function(t) {} } ])); - }, 130, 48)); - add(new UITextBox(10, 220, "")); + }, 130, 48))); + testingUIItems.push(cast add(new UITextBox(10, 220, ""))); - var spliceSprite:UISliceSprite = new UISliceSprite(650, 350, 30, 30, "editors/ui/context-bg"); - add(spliceSprite); - FlxTween.num(0, 1, 2, {type: PINGPONG}, function (v:Float) { - spliceSprite.bWidth = Std.int(lerp(12, 200, v)); spliceSprite.bHeight = Std.int(lerp(12, 200, v)); - }); + spliceText = new UIText(0, 0, 0, "(0) Sprites"); + add(spliceText); } + public var spliceSprites:Array<FlxSprite> = []; + public var spliceText:UIText; + + public var spliceTimer:Float = 0; + public var spliceCoolDown:Float = 0.04; + + public var spliceUseTris:Bool = false; + public var spliceCl:Class<FlxSprite> = UISliceSprite; + public override function update(elapsed:Float) { super.update(elapsed); + + if (FlxG.keys.pressed.SPACE) { + spliceTimer += elapsed; + if (spliceTimer >= spliceCoolDown) { + spliceTimer -= spliceCoolDown; + + for (i in 0...3) { + var width:Int = FlxG.random.int(10, 200); + var height:Int = FlxG.random.int(10, 200); + + var spliceSprite:FlxSprite = Type.createInstance(spliceCl, + [ + FlxG.random.float(0, FlxG.width-width), + FlxG.random.float(22, FlxG.height-height), + width, height, "editors/ui/context-bg" + ] + ); + + spliceSprites.push(cast add(spliceSprite)); + } + } + } + + var spliceTopButton:UITopMenuButton = cast topMenuSpr.members[topMenuSpr.members.length-1]; + + spliceText.x = spliceTopButton.x + spliceTopButton.bWidth - 2; + spliceText.y = spliceTopButton.label.y+1; + + spliceText.text = '(${spliceSprites.length}) 9-Splice Sprites'; + if (FlxG.mouse.justReleasedRight) { openContextMenu([ { diff --git a/source/funkin/editors/UndoList.hx b/source/funkin/editors/UndoList.hx index cefdc12f..79d61a87 100644 --- a/source/funkin/editors/UndoList.hx +++ b/source/funkin/editors/UndoList.hx @@ -35,4 +35,10 @@ class UndoList<T> { public inline function save() savedLength = undoList.length; + + public inline function clear() { + undoList = []; + redoList = []; + save(); + } } \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimButton.hx b/source/funkin/editors/character/CharacterAnimButton.hx new file mode 100644 index 00000000..61182562 --- /dev/null +++ b/source/funkin/editors/character/CharacterAnimButton.hx @@ -0,0 +1,516 @@ +package funkin.editors.character; + +import flixel.text.FlxText.FlxTextFormat; +import flixel.text.FlxText.FlxTextFormatMarkerPair; +import flxanimate.animate.FlxAnim.FlxSymbolAnimation; +import flxanimate.animate.FlxSymbol; +import flxanimate.animate.FlxElement; +import flxanimate.animate.SymbolParameters; +import flxanimate.animate.FlxKeyFrame; +import flxanimate.animate.FlxTimeline; +import flixel.graphics.frames.FlxFrame; +import flixel.animation.FlxAnimation; +import openfl.geom.Rectangle; +import funkin.backend.utils.XMLUtil.AnimData; +import flixel.math.FlxPoint; +import flixel.util.FlxColor; + +using StringTools; + +class CharacterAnimButton extends UIButton { + public var anim:String = null; + public var valid:Bool = true; + public var data:AnimData = null; + public var parent:CharacterAnimsWindow; + + public var animationDisplayBG:UISliceSprite; + public var nameTextBox:UITextBox; + public var animTextBox:UIAutoCompleteTextBox; + public var positionXStepper:UINumericStepper; + public var positionYStepper:UINumericStepper; + public var fpsStepper:UINumericStepper; + public var loopedCheckbox:UICheckbox; + public var indicesTextBox:UITextBox; + public var XYComma:UIText; + + public var editButton:UIButton; + public var editIcon:FlxSprite; + + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + public var ghostButton:UIButton; + public var ghostIcon:FlxSprite; + + public var animIcon:FlxSprite; + + public var labels:Map<UISprite, UIText> = []; + + public var foldableButtons:Array<FlxSprite> = []; + public var closed:Bool = true; + + public function new(x:Float, y:Float, animData:AnimData, parent:CharacterAnimsWindow) { + this.anim = animData.name; + this.data = animData; + this.parent = parent; + super(x,y, '${animData.name} (${animData.x}, ${animData.y})', function () { + CharacterEditor.instance.playAnimation(this.anim); + }, Std.int(500-16-32), 208); + + function addLabelOn(ui:UISprite, text:String, ?size:Int):UIText { + var uiText:UIText = new UIText(ui.x, ui.y-18, 0, text, size); + members.push(uiText); labels.set(ui, uiText); + return uiText; + } + + autoAlpha = autoFrames = autoFollow = false; + + frames = Paths.getFrames('editors/ui/inputbox'); + field.fieldWidth = 0; framesOffset = 9; + field.size = 14; + + animationDisplayBG = new UISliceSprite(x+12, y+12+18+12, 128, 128, 'editors/ui/inputbox-small'); + members.push(animationDisplayBG); + foldableButtons.push(animationDisplayBG); + + nameTextBox = new UITextBox(animationDisplayBG.x+126+16, animationDisplayBG.y, animData.name, 104, 22, false, true); + nameTextBox.onChange = (newName:String) -> {this.changeName(newName);}; + members.push(nameTextBox); + foldableButtons.push(nameTextBox); + addLabelOn(nameTextBox, "Name", 12); + + animTextBox = new UIAutoCompleteTextBox(nameTextBox.x + 100, nameTextBox.y, animData.anim, 156, 22, false, true); + animTextBox.onChange = (newAnim:String) -> {this.changeAnim(newAnim);}; + members.push(animTextBox); + foldableButtons.push(animTextBox); + addLabelOn(animTextBox, "Animation", 12); + + positionXStepper = new UINumericStepper(animTextBox.x, animTextBox.y+32+18, animData.x, 0.001, 2, null, null, 64, 22, true); + positionXStepper.onChange = (text:String) -> { + @:privateAccess positionXStepper.__onChange(text); + this.changeOffset(positionXStepper.value, null); + }; + members.push(positionXStepper); + foldableButtons.push(positionXStepper); + addLabelOn(positionXStepper, "Position (X,Y)", 12); + + members.push(XYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 18)); + foldableButtons.push(XYComma); + + positionYStepper = new UINumericStepper(positionXStepper.x+104-32+26, positionXStepper.y, animData.y, 0.001, 2, null, null, 64, 22, true); + positionYStepper.onChange = (text:String) -> { + @:privateAccess positionYStepper.__onChange(text); + this.changeOffset(null, positionYStepper.value); + }; + members.push(positionYStepper); + foldableButtons.push(positionYStepper); + + fpsStepper = new UINumericStepper(animTextBox.x + 200 + 26, animTextBox.y, animData.fps, 0.1, 2, 1, 100, 52, 22, true); + fpsStepper.onChange = (text:String) -> { + @:privateAccess fpsStepper.__onChange(text); + this.changeFPS(fpsStepper.value); + }; + members.push(fpsStepper); + foldableButtons.push(fpsStepper); + addLabelOn(fpsStepper, "FPS", 12); + + loopedCheckbox = new UICheckbox(fpsStepper.x + 82 - 32 + 26, fpsStepper.y, "Looping?", animData.loop, 0, true); + loopedCheckbox.onChecked = (newLooping:Bool) -> {this.changeLooping(newLooping);}; + members.push(loopedCheckbox); + foldableButtons.push(loopedCheckbox); + + loopedCheckbox.x += 8; loopedCheckbox.y += 6; + + indicesTextBox = new UITextBox(nameTextBox.x, nameTextBox.y, CoolUtil.formatNumberRange(animData.indices.getDefault([]), ", "), 278, 22, false, true); + indicesTextBox.onChange = (text:String) -> { + this.changeIndicies(CoolUtil.parseNumberRange(text)); + } + members.push(indicesTextBox); + foldableButtons.push(indicesTextBox); + addLabelOn(indicesTextBox, "Indices (frames)", 12); + + animIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/character/anim-icons"), true, 16, 12); + animIcon.animation.add("play", [0]); + animIcon.animation.add("error", [1]); + // animIcon.color = 0xFFD60E0E; + animIcon.animation.play("play"); + animIcon.antialiasing = false; + animIcon.updateHitbox(); + members.push(animIcon); + + deleteButton = new UIButton(0, 0, null, () -> {parent.deleteAnimation(this);}, 28*2,24); + deleteButton.frames = Paths.getFrames("editors/ui/grayscale-button"); + deleteButton.color = 0xFFAC3D3D; + members.push(deleteButton); + + deleteIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/deleter")); + deleteIcon.color = 0xFFD60E0E; + deleteIcon.antialiasing = false; + members.push(deleteIcon); + + editButton = new UIButton(0, 0, null, () -> { + if (!this.closed) UIUtil.confirmUISelections(this); + this.closed = !this.closed; + }, 28,24); + editButton.frames = Paths.getFrames("editors/ui/grayscale-button"); + editButton.color = 0xFFAFAA12; + members.push(editButton); + + editIcon = new FlxSprite().loadGraphic(Paths.image('editors/character/edit-button')); + //editIcon.color = 0xFFE8E801; + editIcon.antialiasing = false; + members.push(editIcon); + + ghostButton = new UIButton(0, 0, null, toggleGhost, 28, 24); + ghostButton.frames = Paths.getFrames("editors/ui/grayscale-button"); + ghostButton.color = 0xFFADADAD; + ghostButton.alpha = 0.5; + members.push(ghostButton); + + ghostIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image('editors/character/ghost'), true, 16, 12); + ghostIcon.animation.add("alive", [0]); + ghostIcon.animation.add("dead", [1]); + ghostIcon.animation.play("dead"); + ghostIcon.color = 0xFFADADAD; + ghostIcon.antialiasing = false; + ghostIcon.updateHitbox(); + members.push(ghostIcon); + + checkValid(); + } + + public inline function updateButtonsPos() { + animIcon.follow(this, 22, (52/2) - (animIcon.height/2) + (animIcon.animation.name == "error" ? 2 : 0)); + field.follow(this, 22+16+10, (52/2) - (field.height/2) + 1); + + deleteButton.follow(this, 380, 14); + deleteIcon.follow(deleteButton, (deleteButton.bWidth/2)-6.5, (deleteButton.bHeight/2)-6); + + editButton.follow(this, 340, 14); + editIcon.follow(editButton, (editButton.bWidth/2)-6, (editButton.bHeight/2)-6); + + ghostButton.follow(this, 300, 14); + ghostIcon.follow(ghostButton, (ghostButton.bWidth/2)-8, (ghostButton.bHeight/2)-6); + + animationDisplayBG.follow(this, 16, 61); + nameTextBox.follow(this, 158, 72); + animTextBox.follow(this, 282, 72); + + positionXStepper.follow(this, 158, 118); + positionYStepper.follow(this, 218, 118); + XYComma.follow(positionXStepper, 64-24, 6); + + fpsStepper.follow(this, 282, 118); + loopedCheckbox.follow(this, 336, 122); + + indicesTextBox.follow(this, 158, 164); + + for (ui => text in labels) { + text.follow(ui, -(2+(ui is UICheckbox?12:0)), -(18+(ui is UICheckbox?6:0))); + text.visible = text.active = ui.visible; + } + } + + public override function update(elapsed:Float) { + bHeight = closed ? 52 : 208; + + for (button in foldableButtons) + button.visible = button.active = !closed; + + super.update(elapsed); + } + + public function changeName(newName:String, addToUndo:Bool = true) @:privateAccess { + if (newName == anim) return; + + if (parent.character.getNameList().indexOf(newName) != -1) { + nameTextBox.label.text = anim; + return; + } + + var oldName:String = anim; + if (parent.character.animateAtlas != null) { + var animSymbol:FlxSymbolAnimation = __getAnimationSymbol(); + + parent.character.animateAtlas.anim.animsMap.remove(anim); + parent.character.animateAtlas.anim.animsMap.set(newName, animSymbol); + } else { + var flxAnimation:FlxAnimation = __getFlxAnimation(); + flxAnimation.name = newName; + + parent.character.animation._animations.remove(anim); + parent.character.animation._animations.set(newName, flxAnimation); + } + + var animData:AnimData = parent.character.animDatas[anim]; + animData.name = newName; + + parent.character.animDatas.remove(anim); + parent.character.animDatas.set(newName, animData); + + var animOffset:FlxPoint = parent.character.animOffsets[anim]; + parent.character.animOffsets.remove(anim); + parent.character.animOffsets.set(newName, animOffset); + + var displayFrame:{scale:Float, animBounds:Rectangle, frame:Int} = parent.displayAnimsFramesList[anim]; + parent.displayAnimsFramesList.remove(anim); + parent.displayAnimsFramesList.set(newName, displayFrame); + + this.anim = newName; + nameTextBox.label.text = newName; + updateText(); + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimEditName(ID, oldName, newName)); + } + + public function changeAnim(newAnim:String, addToUndo:Bool = true) @:privateAccess { + var animData:AnimData = parent.character.animDatas[anim]; + if (newAnim == animData.anim) return; + + var oldAnim:String = animData.anim; + animData.anim = newAnim; + + if (parent.character.animateAtlas != null) + __refreshAnimation(); + else { + var flxAnimation:FlxAnimation = __getFlxAnimation(); + flxAnimation.prefix = newAnim; + + refreshFlxAnimationFrames(flxAnimation, animData); + + if (valid) { + parent.buildAnimDisplay(anim, data); + animationDisplayBG.alpha = 1; + } else { + parent.removeAnimDisplay(anim); + animationDisplayBG.alpha = 0.4; + } + } + + if (parent.character.getAnimName() == anim) + CharacterEditor.instance.playAnimation(anim); + + animTextBox.label.text = newAnim; + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimEditAnim(ID, oldAnim, newAnim)); + } + + public function changeOffset(newOffsetX:Null<Float>, newOffsetY:Null<Float>, addToUndo:Bool = true) { + var animData:AnimData = parent.character.animDatas[anim]; + + if (newOffsetX != null && newOffsetY != null && newOffsetX == animData.x && newOffsetY == animData.y) + return; + + var oldPosition:FlxPoint = FlxPoint.get(animData.x, animData.y); + + if (newOffsetX != null) animData.x = newOffsetX; + if (newOffsetY != null) animData.y = newOffsetY; + + parent.character.animOffsets[anim].set(animData.x, animData.y); + + if (parent.character.getAnimName() == anim && Options.playAnimOnOffset) + CharacterEditor.instance.playAnimation(anim); + + positionXStepper.label.text = Std.string(animData.x); + positionYStepper.label.text = Std.string(animData.y); + + updateText(); + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimEditOffset(ID, oldPosition, FlxPoint.get(animData.x, animData.y))); + } + + public function changeFPS(newFPS:Float, addToUndo:Bool = true) @:privateAccess { + var animData:AnimData = parent.character.animDatas[anim]; + var oldFPS:Float = animData.fps; + animData.fps = newFPS; + + if (parent.character.animateAtlas != null) { + var animSymbol:FlxSymbolAnimation = __getAnimationSymbol(); + animSymbol.frameRate = newFPS; + } else { + var flxAnimation:FlxAnimation = __getFlxAnimation(); + flxAnimation.frameRate = newFPS; + } + + if (parent.character.getAnimName() == anim) + CharacterEditor.instance.playAnimation(anim); + + fpsStepper.label.text = Std.string(newFPS); + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimEditFPS(ID, oldFPS, newFPS)); + } + + public function changeLooping(newLooping:Bool, addToUndo:Bool = true) @:privateAccess { + var animData:AnimData = parent.character.animDatas[anim]; + + animData.loop = newLooping; + + if (parent.character.animateAtlas != null) { + var animSymbol:FlxSymbolAnimation = __getAnimationSymbol(); + animSymbol.instance.symbol.loop = animData.loop ? Loop : PlayOnce; + } else { + var flxAnimation:FlxAnimation = __getFlxAnimation(); + flxAnimation.looped = animData.loop; + } + + if (parent.character.getAnimName() == anim) + CharacterEditor.instance.playAnimation(anim); + + loopedCheckbox.checked = newLooping; + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimEditLooping(ID, newLooping)); + } + + public function changeIndicies(indicies:Array<Int>, addToUndo:Bool = true) @:privateAccess { + var animData:AnimData = parent.character.animDatas[anim]; + var oldIndices:Array<Int> = animData.indices.copy(); + + animData.indices = indicies; + + __refreshAnimation(); + + if (parent.character.getAnimName() == anim) + CharacterEditor.instance.playAnimation(anim); + + indicesTextBox.label.text = animData.indices.join(", "); + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimEditIndices(ID, oldIndices, indicies)); + } + + public inline function refreshFlxAnimationFrames(flxAnimation:FlxAnimation, animData:AnimData) @:privateAccess { + try { + if (animData.indices.length > 0) { + var frameIndices:Array<Int> = new Array<Int>(); + parent.character.animation.byIndicesHelper(frameIndices, flxAnimation.prefix, animData.indices, ""); + + flxAnimation.frames = frameIndices; + } else { + final animFrames:Array<FlxFrame> = new Array<FlxFrame>(); + parent.character.animation.findByPrefix(animFrames, flxAnimation.prefix); + + final frameIndices:Array<Int> = []; + parent.character.animation.byPrefixHelper(frameIndices, animFrames, flxAnimation.prefix); + + flxAnimation.frames = frameIndices; + } + + if (flxAnimation.frames.length <= 0) invalidate(); + else validate(); + } catch (e) { + trace('ERROR REFRESHING FLXANIMATION FRAMES: $e'); + invalidate(); + } + } + + public inline function refreshSymbolKeyFrames(symbol:FlxSymbolAnimation, animData:AnimData) @:privateAccess { + var wasRefreshed:Bool = false; + + try { + parent.character.animateAtlas.anim.animsMap.remove(animData.name); + if (animData.indices.length > 0) + parent.character.animateAtlas.anim.addBySymbolIndices(animData.name, animData.anim, animData.indices, animData.fps, animData.loop); + else + parent.character.animateAtlas.anim.addBySymbol(animData.name, animData.anim, animData.fps, animData.loop); + wasRefreshed = parent.character.animateAtlas.anim.animsMap.exists(animData.name); + if (!wasRefreshed) __getAnimationSymbol(); + } catch (e) { + trace('ERROR REFRESHING SYMBOL FRAMES: $e'); + } + + validate(wasRefreshed); + } + + public function toggleGhost() { + if (valid && parent.ghosts.indexOf(anim) == -1) { + parent.ghosts.push(anim); + ghostIcon.animation.play("alive", true); + ghostIcon.color = 0xFFFFFFFF; + } else { + parent.ghosts.remove(anim); + ghostIcon.animation.play("dead", true); + ghostIcon.color = 0xFFADADAD; + } + } + + public override function draw() { + updateButtonsPos(); + super.draw(); + + if (!closed && parent.displayAnimsFramesList.exists(anim)) { + var displayData:{frame:Int, scale:Float, animBounds:Rectangle} = parent.displayAnimsFramesList.get(anim); + parent.displayWindowSprite.frame = parent.displayWindowSprite.frames.frames[displayData.frame]; + + parent.displayWindowSprite.scale.x = parent.displayWindowSprite.scale.y = displayData.scale; + parent.displayWindowSprite.updateHitbox(); + + parent.displayWindowSprite.follow( + this, 16+(128/2)-((parent.displayWindowSprite.frame.sourceSize.x*displayData.scale)/2), + 8+32+8+2+11+(128/2)-((parent.displayWindowSprite.frame.sourceSize.y*displayData.scale)/2) + ); + parent.displayWindowSprite.draw(); + } + } + + public function updateText() { + field.applyMarkup( + '${anim} ' + (valid ? '(${parent.character.animOffsets[anim].x}, ${parent.character.animOffsets[anim].y})' : "|(Invalid Animation!!!)|"), + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFF29797), "|")]); + } + + public function validate(isValid:Bool = true) { + var wasValid:Bool = valid; + this.valid = isValid; + + animIcon.animation.play(valid ? "play" :"error"); + color = valid ? 0xFFFFFFFF : 0xFFD60E0E; + + ghostIcon.visible = ghostButton.visible = isValid; + XYComma.alpha = isValid ? 1 : 0.4; + for (uiElement in [nameTextBox, positionXStepper, positionYStepper, fpsStepper, loopedCheckbox, indicesTextBox]) { + uiElement.selectable = isValid; + if (labels[uiElement] != null) + labels[uiElement].alpha = isValid ? 1 : 0.4; + } + if (!valid) toggleGhost(); + updateText(); + + if (!wasValid && isValid && CharacterEditor.instance.characterFakeAnim == anim) + CharacterEditor.instance.playAnimation(this.anim); + } + + public inline function invalidate() + validate(false); + + public inline function __getFlxAnimation():Null<FlxAnimation> @:privateAccess { + if (!parent.character.animation._animations.exists(anim)) { + final flxanim = new FlxAnimation(parent.character.animation, anim, [], data.fps, data.loop); + flxanim.prefix = data.anim; // sobbing + + parent.character.animation._animations.set(anim, flxanim); + parent.character.animDatas.set(anim, data); + } + return parent.character.animation._animations[anim]; + } + + public inline function __getAnimationSymbol():Null<FlxSymbolAnimation> @:privateAccess { + if (!parent.character.animateAtlas.anim.animsMap.exists(anim)) + XMLUtil.addAnimToSprite(parent.character, data); + return parent.character.animateAtlas.anim.animsMap[anim]; + } + + + @:noCompletion function __refreshAnimation() @:privateAccess { + if (parent.character.animateAtlas != null) refreshSymbolKeyFrames(__getAnimationSymbol(), data); + else refreshFlxAnimationFrames(__getFlxAnimation(), data); + + if (valid) { + parent.buildAnimDisplay(anim, data); + animationDisplayBG.alpha = 1; + } else { + parent.removeAnimDisplay(anim); + animationDisplayBG.alpha = 0.4; + } + } + + public inline function checkValid() + __refreshAnimation(); +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimButtons.hx b/source/funkin/editors/character/CharacterAnimButtons.hx deleted file mode 100644 index c80913cd..00000000 --- a/source/funkin/editors/character/CharacterAnimButtons.hx +++ /dev/null @@ -1,89 +0,0 @@ -package funkin.editors.character; - -import flixel.math.FlxPoint; -import flixel.util.FlxColor; - -class CharacterAnimButtons extends UIButton { - public var anim:String = ""; - - public var editButton:UIButton; - public var editIcon:FlxSprite; - - public var ghostButton:UIButton; - public var ghostIcon:FlxSprite; - - public var deleteButton:UIButton; - public var deleteIcon:FlxSprite; - - public function new(x:Float,y:Float,anim:String, offset:FlxPoint) { - this.anim = anim; - super(x,y, '$anim (${offset.x}, ${offset.y})', function () { - CharacterEditor.instance.playAnimation(this.anim); - }, 282); - autoAlpha = false; - - ghostButton = new UIButton(x+282+17, y, "", function () { - CharacterEditor.instance.ghostAnim(this.anim); - }, 32); - ghostButton.autoAlpha = false; - members.push(ghostButton); - - ghostIcon = new FlxSprite(ghostButton.x + 8, ghostButton.y + 8).loadGraphic(Paths.image('editors/character/ghost-button'), true, 16, 16); - ghostIcon.animation.add("alive", [0]); - ghostIcon.animation.add("dead", [1]); - ghostIcon.animation.play("dead"); ghostIcon.alpha = 0.5; - ghostIcon.antialiasing = false; - ghostIcon.updateHitbox(); - members.push(ghostIcon); - - editButton = new UIButton(ghostButton.x+32+17, y, "", function () { - CharacterEditor.instance.editAnimWithUI(this.anim); - }, 32); - editButton.frames = Paths.getFrames("editors/ui/grayscale-button"); - editButton.color = FlxColor.YELLOW; - editButton.autoAlpha = false; - members.push(editButton); - - editIcon = new FlxSprite(editButton.x + 8, editButton.y + 8).loadGraphic(Paths.image('editors/character/edit-button')); - editIcon.antialiasing = false; - members.push(editIcon); - - deleteButton = new UIButton(editButton.x+32+17, y, "", function () { - CharacterEditor.instance.deleteAnim(this.anim); - }, 32); - deleteButton.color = FlxColor.RED; - deleteButton.autoAlpha = false; - members.push(deleteButton); - - deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button')); - deleteIcon.antialiasing = false; - members.push(deleteIcon); - } - - public override function update(elapsed:Float) { - editButton.selectable = ghostButton.selectable = deleteButton.selectable = selectable; - editButton.shouldPress = ghostButton.shouldPress = deleteButton.shouldPress = shouldPress; - - hovered = !deleteButton.hovered; - updatePos(); - super.update(elapsed); - } - - public function updateInfo(anim:String, offset:FlxPoint, ghost:Bool) { - this.anim = anim; - - field.text = '$anim (${offset.x}, ${offset.y})'; - ghostIcon.animation.play(ghost ? "alive" : "dead"); - ghostIcon.alpha = ghost ? 1 : 0.5; - } - - public inline function updatePos() { - // buttons - deleteButton.x = (editButton.x = (ghostButton.x = (x+282+17))+32+17)+32+17; - deleteButton.y = editButton.y = ghostButton.y = y; - // icons - ghostIcon.x = ghostButton.x + 8; ghostIcon.y = ghostButton.y + 8; - editIcon.x = editButton.x + 8; editIcon.y = editButton.y + 8; - deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8; - } -} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimScreen.hx b/source/funkin/editors/character/CharacterAnimScreen.hx deleted file mode 100644 index 0d9f30da..00000000 --- a/source/funkin/editors/character/CharacterAnimScreen.hx +++ /dev/null @@ -1,120 +0,0 @@ -package funkin.editors.character; - -import flixel.animation.FlxAnimation; -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; - -class CharacterAnimScreen extends UISubstateWindow { - public var animData:AnimData; - - public var nameTextBox:UITextBox; - public var animTextBox:UITextBox; - public var fpsStepper:UINumericStepper; - public var loopedCheckbox:UICheckbox; - public var indicesTextBox:UITextBox; - public var offsetXStepper:UINumericStepper; - public var offsetYStepper:UINumericStepper; - - public var saveButton:UIButton; - public var closeButton:UIButton; - - public var onSave:(animData:AnimData) -> Void = null; - - public function new(animData:AnimData, ?onSave:(animData:AnimData) -> Void) { - this.animData = animData; - this.onSave = onSave; - super(); - } - - inline function translate(id:String, ?args:Array<Dynamic>) - return TU.translate("characterAnimScreen." + id, args); - - public override function create() { - var creatingAnim:Bool = animData == null; - - if (creatingAnim) - animData = { - name: "name", - anim: "anim", - fps: 24, - loop: false, - animType: NONE, - x: 0, - y: 0, - indices: [] - }; - - winTitle = translate("win-title-" + (creatingAnim ? "creating" : "editing"), [animData.name]); - winWidth = 690; winHeight = 314; - - super.create(); - - function addLabelOn(ui:UISprite, text:String) - add(new UIText(ui.x, ui.y - 24, 0, text)); - - var title:UIText; - add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title-" + (creatingAnim ? "creating" : "editing")), 28)); - - nameTextBox = new UITextBox(title.x, title.y + title.height + 38, animData.name, 160); - add(nameTextBox); - addLabelOn(nameTextBox, translate("name")); - - animTextBox = new UITextBox(nameTextBox.x + 160 + 26, nameTextBox.y, animData.anim, 200); - add(animTextBox); - addLabelOn(animTextBox, translate("anim")); - - fpsStepper = new UINumericStepper(animTextBox.x + 200 + 26, animTextBox.y, animData.fps, 0.1, 2, 1, 100, 82); - add(fpsStepper); - addLabelOn(fpsStepper, translate("fps")); - - loopedCheckbox = new UICheckbox(fpsStepper.x + 82 - 32 + 26, fpsStepper.y, translate("looped"), animData.loop); - add(loopedCheckbox); - addLabelOn(loopedCheckbox, translate("looping")); - loopedCheckbox.x += 8; loopedCheckbox.y += 6; - - indicesTextBox = new UITextBox(nameTextBox.x, nameTextBox.y + 32 + 40, CoolUtil.formatNumberRange(animData.indices, ", "), 270); - add(indicesTextBox); - addLabelOn(indicesTextBox, translate("indices")); - - offsetXStepper = new UINumericStepper(indicesTextBox.x + 270 + 26, indicesTextBox.y, animData.x, 0.001, 2, null, null, 84); - add(offsetXStepper); - addLabelOn(offsetXStepper, translate("offset")); - - add(new UIText(offsetXStepper.x + 84 - 32 + 0, offsetXStepper.y + 9, 0, ",", 22)); - - offsetYStepper = new UINumericStepper(offsetXStepper.x + 84 - 32 + 26, offsetXStepper.y, animData.y, 0.001, 2, null, null, 84); - add(offsetYStepper); - - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { - saveAnimData(); - close(); - }, 125); - add(saveButton); - - closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor." + (creatingAnim ? "cancel" : "close")), function() { - if (creatingAnim && onSave != null) onSave(null); - close(); - }, 125); - add(closeButton); - closeButton.color = 0xFFFF0000; - closeButton.x -= closeButton.bWidth; - } - - public function saveAnimData() { - for (stepper in [offsetXStepper, offsetYStepper, fpsStepper]) - @:privateAccess stepper.__onChange(stepper.label.text); - - animData = { - name: nameTextBox.label.text, - anim: animTextBox.label.text, - fps: fpsStepper.value, - loop: loopedCheckbox.checked, - animType: NONE, - x: offsetXStepper.value, - y: offsetYStepper.value, - indices: CoolUtil.parseNumberRange(indicesTextBox.label.text) - }; - - if (onSave != null) onSave(animData); - } -} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimsWindow.hx b/source/funkin/editors/character/CharacterAnimsWindow.hx new file mode 100644 index 00000000..a58f790f --- /dev/null +++ b/source/funkin/editors/character/CharacterAnimsWindow.hx @@ -0,0 +1,171 @@ +package funkin.editors.character; + +import flixel.util.typeLimit.OneOfTwo; +import funkin.backend.utils.XMLUtil.AnimData; +import flixel.animation.FlxAnimation; +import flixel.graphics.FlxGraphic; +import openfl.geom.Rectangle; +import flixel.graphics.frames.FlxFrame; +import funkin.game.Character; +import flixel.animation.FlxPrerotatedAnimation; + +using funkin.backend.utils.BitmapUtil; + +class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> { + public var character:CharacterGhost; + + public var displayWindowSprite:FlxSprite; + public var displayWindowGraphic:FlxGraphic; + public var displayAnimsFramesList:Map<String, {scale:Float, animBounds:Rectangle, frame:OneOfTwo<Int, String>}> = []; + + public var animButtons:Map<String, CharacterAnimButton> = []; + public var animsList:Array<String> = []; + + public function new(x:Float, y:Float, character:CharacterGhost) { + super(x, y, Std.int(500-16), 419, null, FlxPoint.get(Std.int(500-16-32), 208)); + this.character = character; + + cameraSpacing = 0; + frames = Paths.getFrames('editors/ui/inputbox'); + + buttonCameras.pixelPerfectRender = true; + + if (Assets.exists(Paths.image('characters/${character.sprite}')) && character.animateAtlas == null) + displayWindowGraphic = FlxG.bitmap.add(Assets.getBitmapData(Paths.image('characters/${character.sprite}'), true, false)); + + displayWindowSprite = new FlxSprite(); + displayWindowSprite.loadGraphicFromSprite(character); + displayWindowSprite.antialiasing = character.antialiasing; + displayWindowSprite.flipX = character.flipX; + + alpha = 0.7; + + for (anim in character.getAnimOrder()) + addAnimation(character.animDatas.get(anim), -1, false); + addButton.callback = generateAnimation; + + setAnimAutoComplete(CoolUtil.getAnimsListFromSprite(character)); + + nextscrollY = 0; + // dragCallback = (button:CharacterAnimButton, oldID:Int, newID:Int) -> {} + } + + public var ghosts:Array<String> = []; + var __movingAnimOldOrder:Int = -1; + public override function update(elapsed:Float) { + var __oldMoving:CharacterAnimButton = curMoving; + super.update(elapsed); + + if (curMoving != null && __oldMoving == null) + __movingAnimOldOrder = curMoving.ID; + + if (curMoving == null && __oldMoving != null) + if (__movingAnimOldOrder != __oldMoving.ID) + CharacterEditor.undos.addToUndo(CAnimEditOrder(__movingAnimOldOrder, __oldMoving.ID)); + + + animsList = [for (button in buttons) button.anim]; + character.ghosts = ghosts; + } + + public function buildAnimDisplay(name:String, anim:AnimData) @:privateAccess { + if (character.animateAtlas == null) { + var anim:FlxAnimation = character.animation._animations[anim.name]; + if (anim == null || anim.frames.length <= 0) return; + + var frameIndex:Int = anim.frames.getDefault([0])[0]; + var frame:FlxFrame = displayWindowSprite.frames.frames[frameIndex]; + + var frameRect:Rectangle = new Rectangle(frame.offset.x, frame.offset.y, frame.sourceSize.x, frame.sourceSize.y); + var animBounds:Rectangle = displayWindowGraphic != null ? displayWindowGraphic.bitmap.bounds(frameRect) : frameRect; + + displayAnimsFramesList.set(name, {frame: anim.frames.getDefault([0])[0], scale: 104/Math.max(animBounds.width, animBounds.height), animBounds: animBounds}); + } else { + character.storeAtlasState(); + + /* + character.animateAtlas.anim.play(anim.name, true, false, 0); + character.animateAtlas.anim.stop(); + + var animBounds:Rectangle = MatrixUtil.getBounds(character).copyToFlash(); + displayAnimsFramesList.set(name, {frame: anim.anim, scale: 104/animBounds.height, animBounds: animBounds}); + */ + + character.restoreAtlasState(); + } + } + + public function removeAnimDisplay(name:String) + displayAnimsFramesList.remove(name); + + public function deleteAnimation(button:CharacterAnimButton, addToUndo:Bool = true) { + if (buttons.members.length <= 1) return; + if (character.getAnimName() == button.anim) + @:privateAccess CharacterEditor.instance._animation_down(null); + + character.removeAnimation(button.anim); + if (character.animOffsets.exists(button.anim)) character.animOffsets.remove(button.anim); + if (character.animDatas.exists(button.anim)) character.animDatas.remove(button.anim); + + if (addToUndo) CharacterEditor.undos.addToUndo(CAnimDelete(button.ID, button.data)); + remove(button); button.destroy(); + } + + public function generateAnimation() { + var animName:String = "New Anim"; + var animNames:Array<String> = character.getNameList(); + + var newAnimCount:Int = 0; + while (animNames.indexOf(animName) != -1) { + newAnimCount++; + animName = 'New Anim - $newAnimCount'; + } + + if (__autoCompleteAnims.length <= 0) + setAnimAutoComplete(CoolUtil.getAnimsListFromSprite(character)); + + var animData:AnimData = { + name: animName, + anim: __autoCompleteAnims[0], + fps: 24, loop: false, + x: 0, y: 0, + indices: [], + animType: NONE, + }; + addAnimation(animData); + } + + public function addAnimation(animData:AnimData, animID:Int = -1, addToUndo:Bool = true) @:privateAccess { + XMLUtil.addAnimToSprite(character, animData); + + var newButton:CharacterAnimButton = new CharacterAnimButton(0, 0, animData, this); + newButton.alpha = 0.25; animButtons.set(animData.name, newButton); + newButton.animTextBox.suggestItems = __autoCompleteAnims; + + if (animID == -1) add(newButton); + else insert(newButton, animID); + + if (newButton.valid) + buildAnimDisplay(animData.name, animData); + + if (addToUndo) + CharacterEditor.undos.addToUndo(CAnimCreate(newButton.ID, newButton.data)); + + var nextButtonY:Float = 0; + for (buttonID in 0...newButton.ID) + nextButtonY += buttons.members[buttonID].bHeight + buttonOffset.y; + nextscrollY = nextButtonY; + } + + @:noCompletion var __autoCompleteAnims:Array<String> = []; + public inline function setAnimAutoComplete(anims:Array<String>) { + __autoCompleteAnims = anims.copy(); + for (button in buttons) + button.animTextBox.suggestItems = __autoCompleteAnims; + } + + public function findValid():Null<String> { + for (button in buttons) if (button.valid) return button.anim; + return null; + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterCreationScreen.hx b/source/funkin/editors/character/CharacterCreationScreen.hx new file mode 100644 index 00000000..5bb5062e --- /dev/null +++ b/source/funkin/editors/character/CharacterCreationScreen.hx @@ -0,0 +1,149 @@ +package funkin.editors.character; + +import haxe.xml.Printer; +import funkin.game.Character; +import funkin.editors.ui.UIImageExplorer.ImageSaveData; +import flixel.text.FlxText.FlxTextFormat; +import flixel.text.FlxText.FlxTextFormatMarkerPair; + +class CharacterCreationScreen extends UISubstateWindow { + private var onSave:(String, ImageSaveData, Xml)-> Void = null; + + public var characterNameTextBox:UITextBox; + public var imageExplorer:UIImageExplorer; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public function new(?onSave:(String, ImageSaveData, Xml)->Void) { + super(); + if (onSave != null) this.onSave = onSave; + } + + public override function create() { + winTitle = "Creating New Character"; + + winWidth = 360; + winHeight = 520; + + super.create(); + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + characterNameTextBox = new UITextBox(windowSpr.x + 20, windowSpr.y + 30 + 16 + 20, "character", 320); + characterNameTextBox.onChange = (_) -> {checkRequired();}; + add(characterNameTextBox); + addLabelOn(characterNameTextBox, "Character File Name").applyMarkup( + "Character File Name $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + imageExplorer = new UIImageExplorer(characterNameTextBox.x, characterNameTextBox.y + 30 + 16 + 20, null, 320, 58, (_, _) -> {onLoadImage();}, "images/characters"); + add(imageExplorer); + addLabelOn(imageExplorer, "Character Image File").applyMarkup( + "Character Image File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + imageExplorer.maxSize.y -= 100; + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + close(); + createCharacter(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() { + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + + onLoadImage(); + } + + public function onLoadImage() { + refreshWindowSize(); + checkRequired(); + } + + public function refreshWindowSize() { + if (imageExplorer == null) return; + windowSpr.bWidth = 20 + imageExplorer.bWidth + 20; + windowSpr.bHeight = 30 + 16 + 20 + 32 + 30 + 10 + imageExplorer.bHeight + 14 + saveButton.bHeight + 14; + + saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - saveButton.bWidth; + closeButton.x = saveButton.x - 20 - closeButton.bWidth; + closeButton.y = saveButton.y = imageExplorer.y + imageExplorer.bHeight + 14; + } + + public function checkRequired() { + saveButton.selectable = characterNameTextBox.label.text.length > 0 && !CoolUtil.isMapEmpty(imageExplorer.imageFiles) && (imageExplorer.animationList.length > 0); + } + + public function createCharacter() { + var imageSaveData:ImageSaveData = imageExplorer.getSaveData(); + + var xml:Xml = Xml.createElement("character"); + xml.attributeOrder = Character.characterProperties.copy(); + + xml.set("sprite", '${imageSaveData.directory.length > 0 ? '${imageSaveData.directory}/' : ""}' + imageSaveData.imageName); + + // Look for animations >:D + var animationList:Array<String> = imageExplorer.animationList.copy(); + animationList.sort((a, b) -> { + var lengthCompare = a.length - b.length; + if (lengthCompare != 0) return lengthCompare; + + // Miss animations don't show up as regular if they are the same length as regular >:D + var aIsMiss = a.toLowerCase().contains("miss"); + var bIsMiss = b.toLowerCase().contains("miss"); + return aIsMiss == bIsMiss ? 0 : (aIsMiss ? 1 : -1); + }); + + var animationsFound:Map<String, String> = [ + "singLEFT" => null, + "singRIGHT" => null, + "singUP" => null, + "singDOWN" => null, + "idle" => null + ]; + + for (anim => found in animationsFound) { + var animToLookFor:String = StringTools.replace(anim, "sing", "").toLowerCase(); + for (imageAnim in animationList) + if (imageAnim.toLowerCase().contains(animToLookFor)) { + animationsFound.set(anim, imageAnim); + animationList.remove(imageAnim); + break; + } + } + + // Add said animations >:D + for (anim => found in animationsFound) { + var animXml:Xml = Xml.createElement('anim'); + animXml.attributeOrder = Character.characterAnimProperties; + + animXml.set("name", anim); + animXml.set("anim", found.getDefault(animationList[0])); + + xml.addChild(animXml); + } + + // Do the rest only if not atlas (until we make it animations and not symbols >:D) + if (!imageSaveData.isAtlas) { + for (imageAnim in animationList) { + var animXml:Xml = Xml.createElement('anim'); + animXml.attributeOrder = Character.characterAnimProperties; + + animXml.set("name", imageAnim); + animXml.set("anim", imageAnim); + + xml.addChild(animXml); + } + } + + if (onSave != null) onSave(characterNameTextBox.label.text, imageSaveData, xml); + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index ffae1645..7f44162e 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -1,45 +1,64 @@ package funkin.editors.character; +import funkin.editors.character.CharacterInfoScreen.CharacterExtraInfo; +import funkin.editors.extra.AxisGizmo; +import flixel.math.FlxRect; +import funkin.editors.stage.StageEditor; +import funkin.game.Stage; +import funkin.editors.ui.UITopMenu.UITopMenuButton; +import funkin.editors.extra.CameraHoverDummy; +import openfl.display.BitmapData; import flixel.math.FlxPoint; import funkin.backend.system.framerate.Framerate; import funkin.backend.utils.XMLUtil.AnimData; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; +import funkin.editors.ui.UIContextMenu.UIContextMenuOptionSpr; import funkin.game.Character; import haxe.xml.Access; import haxe.xml.Printer; +import funkin.editors.ui.UIImageExplorer.ImageSaveData; +import sys.FileSystem; +import sys.io.File; class CharacterEditor extends UIState { static var __character:String; - public var character:Character; - - public var ghosts:CharacterGhostsHandler; + public var character:CharacterGhost; public static var instance(get, never):CharacterEditor; private static inline function get_instance() return FlxG.state is CharacterEditor ? cast FlxG.state : null; - /** - * CHARACTER UI STUFF - */ - public var characterBG:FunkinSprite; public var topMenu:Array<UIContextMenuOption>; - public var topMenuSpr:UITopMenu; + @:noCompletion private var animationIndex:Int = 5; + @:noCompletion private var stageIndex:Int = 3; + public var topMenuSpr:UITopMenu; + // public var dragOffsetsCheckbox:UICheckbox; + // public var lockCameraCheckbox:UICheckbox; + + public var axisGizmo:AxisGizmo; + public var characterGizmo:CharacterGizmos; public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>(); - // WINDOWS + public var cameraHoverDummy:CameraHoverDummy; + public var characterPropertiesWindow:CharacterPropertiesWindow; - public var characterAnimsWindow:UIButtonList<CharacterAnimButtons>; + public var characterAnimsWindow:CharacterAnimsWindow; - // camera for the character itself so that it can be unzoomed/zoomed in again public var charCamera:FlxCamera; - // camera for the animations list - public var animsCamera:FlxCamera; - // camera for the ui + public var gizmosCamera:FlxCamera; public var uiCamera:FlxCamera; - public var undos:UndoList<CharacterChange> = new UndoList<CharacterChange>(); + public var animationText:UIText; + + public var currentStage:String = null; + public var stage:Stage = null; + + public var stageSprites:Array<FlxBasic> = []; + public var stagePosition:String = null; + + public static var undos:UndoList<CharacterEditorChange>; public function new(character:String) { super(); @@ -52,6 +71,8 @@ class CharacterEditor extends UIState { public override function create() { super.create(); + undos = new UndoList<CharacterEditorChange>(); + WindowUtils.suffix = " (" + translate("name") + ")"; SaveWarning.selectionClass = CharacterSelection; SaveWarning.saveFunc = () -> {_file_save(null);}; @@ -85,6 +106,17 @@ class CharacterEditor extends UIState { { label: translate("topBar.edit"), childs: [ + { + label: "Copy Offset", + keybind: [CONTROL, C], + onSelect: _edit_copy_offset + }, + { + label: "Paste Offset", + keybind: [CONTROL, V], + onSelect: _edit_paste_offset + }, + null, { label: translate("edit.undo"), keybind: [CONTROL, Z], @@ -94,44 +126,19 @@ class CharacterEditor extends UIState { label: translate("edit.redo"), keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]], onSelect: _edit_redo - } - ] - }, - { - label: translate("topBar.character"), - childs: [ - { - label: translate("character.newAnim"), - keybind: [CONTROL, N], - onSelect: _char_add_anim, - }, - { - label: translate("character.editAnim"), - onSelect: _char_edit_anim, - }, - { - label: translate("character.deleteAnim"), - keybind: [DELETE], - onSelect: _char_remove_anim, }, null, { - label: translate("character.editInfo"), - onSelect: _char_edit_info, - } - ] - }, - { - label: translate("topBar.playback"), - childs: [ - { - label: translate("playback.play"), - keybind: [SPACE], - onSelect: _playback_play_anim, + label: "Edit information", + color: 0xFF959829, icon: 4, + onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, + onSelect: _edit_info }, { - label: translate("playback.stop"), - onSelect: _playback_stop_anim + label: "Edit sprite", + color: 0xFF959829, icon: 4, + onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, + onSelect: _edit_sprite } ] }, @@ -180,6 +187,11 @@ class CharacterEditor extends UIState { onSelect: _offsets_extra_right, }, null, + { + label: "Drag Offsets With Mouse?", + onSelect: _offsets_drag_offsets_mouse, + icon: Options.characterDragging ? 1 : 0 + }, { label: translate("offset.clearOffsets"), keybind: [CONTROL, R], @@ -187,6 +199,10 @@ class CharacterEditor extends UIState { } ] }, + { + label: "Stage", + childs: buildStagesUI() + }, { label: translate("topBar.view"), childs: [ @@ -205,56 +221,113 @@ class CharacterEditor extends UIState { keybind: [CONTROL, NUMPADZERO], onSelect: _view_zoomreset }, - + null, + { + label: "Character Hitbox?", + onSelect: _view_character_show_hitbox, + icon: Options.characterHitbox ? 1 : 0 + }, + { + label: "Character Camera?", + onSelect: _view_character_show_camera, + icon: Options.characterCamera ? 1 : 0 + }, + { + label: "XY Axis?", + onSelect: _view_character_show_axis, + icon: Options.characterAxis ? 1 : 0 + } + ] + }, + { + label: "Animation >", + childs: [ + { + label: "Play Animation", + keybind: [SPACE], + onSelect: _animation_play, + }, + { + label: "Stop Animation", + onSelect: _animation_stop + }, + null, + { + label: "Change Animation ↑", + keybind: [W], + onSelect: _animation_up + }, + { + label: "Change Animation ↓", + keybind: [S], + onSelect: _animation_down + }, + null, + { + label: "Play Animation On Offset?", + onSelect: _animation_toggle_anim_playing_offsets, + icon: Options.playAnimOnOffset ? 1 : 0 + }, ] }, ]; charCamera = FlxG.camera; + gizmosCamera = new FlxCamera(); + gizmosCamera.bgColor = 0; + FlxG.cameras.add(gizmosCamera, false); + + axisGizmo = new AxisGizmo(); + axisGizmo.cameras = [gizmosCamera]; + add(axisGizmo); + + characterGizmo = new CharacterGizmos(); + characterGizmo.boxGizmo = Options.characterHitbox; + characterGizmo.cameraGizmo = Options.characterCamera; + characterGizmo.cameras = [gizmosCamera]; + add(characterGizmo); + uiCamera = new FlxCamera(); uiCamera.bgColor = 0; - animsCamera = new FlxCamera(800-23,140+23+30+46,450+23,511-24-30); - animsCamera.bgColor = 0; - - characterBG = new FunkinSprite(0, 0, Paths.image('editors/character/WOAH')); - characterBG.cameras = [charCamera]; - characterBG.screenCenter(); - characterBG.scale.set(FlxG.width/characterBG.width, FlxG.height/characterBG.height); - characterBG.scrollFactor.set(); - add(characterBG); FlxG.cameras.add(uiCamera); - FlxG.cameras.add(animsCamera); - character = new Character(0,0, __character, false, false); + character = new CharacterGhost(0,0, __character, false, false); character.debugMode = true; character.cameras = [charCamera]; - ghosts = new CharacterGhostsHandler(character); - ghosts.cameras = [charCamera]; + characterGizmo.character = character; + + changeCharacterIsPlayer(character.playerOffsets); - add(ghosts); add(character); - topMenuSpr = new UITopMenu(topMenu); - topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; + uiGroup.cameras = [uiCamera]; + add(cameraHoverDummy = new CameraHoverDummy(uiGroup, FlxPoint.weak(0, 0))); - characterPropertiesWindow = new CharacterPropertiesWindow(); + characterPropertiesWindow = new CharacterPropertiesWindow((FlxG.width-(440)-16) - (((500-16)-(440))/2), 23+12+10, character); uiGroup.add(characterPropertiesWindow); - characterAnimsWindow = new UIButtonList<CharacterAnimButtons>(777, 209, 473, 488, translate("characterAnimsWindow.name"), FlxPoint.get(429, 32)); - characterAnimsWindow.addButton.callback = function() CharacterEditor.instance.createAnimWithUI(); - var animOrder = character.getAnimOrder(); - for (i=>anim in animOrder) - characterAnimsWindow.add(new CharacterAnimButtons(0,0, anim, character.getAnimOffset(anim))); - uiGroup.add(characterAnimsWindow); + topMenuSpr = new UITopMenu(topMenu); + uiGroup.add(topMenuSpr); - playAnimation(animOrder[0]); + animationText = new UIText(0, 0, 0, ""); + uiGroup.add(animationText); + + characterAnimsWindow = new CharacterAnimsWindow((FlxG.width-(500-16)-16), characterPropertiesWindow.y+224+16, character); + uiGroup.add(characterPropertiesWindow.animsWindow = characterAnimsWindow); - add(topMenuSpr); add(uiGroup); + playAnimation(character.getAnimOrder()[0]); + changeStage("stage"); + + if (Options.editorsResizable) + UIState.setResolutionAware(); + + _view_focus_character(null); + if(Framerate.isLoaded) { Framerate.fpsCounter.alpha = 0.4; Framerate.memoryCounter.alpha = 0.4; @@ -265,6 +338,10 @@ class CharacterEditor extends UIState { } override function destroy() { + _point.put(); + draggingOffset.put(); + clipboard.put(); + super.destroy(); if(Framerate.isLoaded) { Framerate.fpsCounter.alpha = 1; @@ -273,68 +350,104 @@ class CharacterEditor extends UIState { } } - //private var movingCam:Bool = false; - //private var camDragSpeed:Float = 1.2; - - private var nextScroll:FlxPoint = FlxPoint.get(0,0); + var _point:FlxPoint = new FlxPoint(); + var _nextScroll:FlxPoint = FlxPoint.get(0,0); + var _cameraZoomMulti:Float = 1; + public var draggingCharacter:Bool = false; + public var draggingOffset:FlxPoint = new FlxPoint(); public override function update(elapsed:Float) { - super.update(elapsed); + if(FlxG.keys.justPressed.ANY && currentFocus == null) + UIUtil.processShortcuts(topMenu); - if (true) { - if(FlxG.keys.justPressed.ANY) - UIUtil.processShortcuts(topMenu); - } - - if (character != null) - characterPropertiesWindow.characterInfo.text = [ - translate("info.animations", [character.getNameList().length]), - translate("info.flipped", [character.flipX]), - translate("info.sprite", [character.sprite]), - translate("info.anim", [character.getAnimName()]), - translate("info.offset", [character.frameOffset.x, character.frameOffset.y]) - ].join('\n'); - else characterPropertiesWindow.characterInfo.text = translate("info.invalidCharacter"); - - if (!(characterPropertiesWindow.hovered || characterAnimsWindow.hovered) && !characterAnimsWindow.dragging) { + if (cameraHoverDummy.hovered && !draggingCharacter) { if (FlxG.mouse.wheel != 0) { zoom += 0.25 * FlxG.mouse.wheel; __camZoom = Math.pow(2, zoom); } if (FlxG.mouse.justReleasedRight) { + var mousePos = FlxG.mouse.getScreenPosition(uiCamera); closeCurrentContextMenu(); - openContextMenu(topMenu[2].childs); + openContextMenu(topMenu[2].childs, null, mousePos.x, mousePos.y); } - if (FlxG.mouse.pressed) { - nextScroll.set(nextScroll.x - FlxG.mouse.deltaScreenX, nextScroll.y - FlxG.mouse.deltaScreenY); - currentCursor = HAND; + + if (FlxG.mouse.pressed && !FlxG.mouse.justPressed) { + _nextScroll.set(_nextScroll.x - FlxG.mouse.deltaScreenX, _nextScroll.y - FlxG.mouse.deltaScreenY); + cameraHoverDummy.cursor = HAND; } else - currentCursor = ARROW; + cameraHoverDummy.cursor = ARROW; } else if (!FlxG.mouse.pressed) currentCursor = ARROW; charCamera.scroll.set( - lerp(charCamera.scroll.x, nextScroll.x, 0.35), - lerp(charCamera.scroll.y, nextScroll.y, 0.35) + lerp(charCamera.scroll.x, _nextScroll.x, 0.35), + lerp(charCamera.scroll.y, _nextScroll.y, 0.35) ); - charCamera.zoom = lerp(charCamera.zoom, __camZoom, 0.125); + charCamera.zoom = lerp(charCamera.zoom, __camZoom*_cameraZoomMulti, 0.125); - characterBG.scale.set(FlxG.width/characterBG.width, FlxG.height/characterBG.height); - characterBG.scale.set(characterBG.scale.x / charCamera.zoom, characterBG.scale.y / charCamera.zoom); + if (topMenuSpr.members[animationIndex] != null) { + var animationTopButton:UITopMenuButton = cast topMenuSpr.members[animationIndex]; + animationText.x = animationTopButton.x + animationTopButton.bWidth + 6; + animationText.y = Std.int((animationTopButton.bHeight - animationText.height) / 2); + } + animationText.text = '"${characterFakeAnim}"'; + + StageEditor.calcSpriteBounds(character); + + if (Options.characterDragging) + handleMouseOffsets(); WindowUtils.prefix = undos.unsaved ? Flags.UNDO_PREFIX : ""; SaveWarning.showWarning = undos.unsaved; + + super.update(elapsed); + } + + inline function handleMouseOffsets() { + if (draggingCharacter) { + cameraHoverDummy.cursor = DRAG; + + if (FlxG.mouse.justReleased) { + draggingOffset.x /= character.scale.x; + draggingOffset.y /= character.scale.y; + + _change_offset((draggingOffset.x * (character.isFlippedOffsets() ? -1 : 1)), draggingOffset.y); + + draggingOffset.set(0, 0); draggingCharacter = false; + character.extraOffset = draggingOffset; + } else { + draggingOffset.x += FlxG.mouse.deltaScreenX; draggingOffset.y += FlxG.mouse.deltaScreenY; + character.extraOffset = draggingOffset; + } + } else { + var point = FlxG.mouse.getWorldPosition(charCamera, _point); + var bounds:FlxRect = cast character.extra.get(StageEditor.exID("bounds")); + if (bounds.containsPoint(point)) { + cameraHoverDummy.cursor = #if (mac) DRAG_OPEN; #else CLICK; #end + if (FlxG.mouse.justPressed) + draggingCharacter = true; + } + } + } + + public override function onResize(width:Int, height:Int) { + super.onResize(width, height); + if (!UIState.resolutionAware) return; + + if (width < FlxG.initialWidth || height < FlxG.initialHeight) { + width = FlxG.initialWidth; height = FlxG.initialHeight; + } + + characterPropertiesWindow.x = (FlxG.width-(440)-16) - (((500-16)-(440))/2); + characterAnimsWindow.x = (FlxG.width-(500-16)-16); + + characterAnimsWindow.bHeight = Std.int(FlxG.height-(characterPropertiesWindow.y+characterPropertiesWindow.bHeight)-(32)); } // TOP MENU OPTIONS #if REGION - function _file_exit(_) { - if (undos.unsaved) SaveWarning.triggerWarning(); - else FlxG.switchState(new CharacterSelection()); - } - function _file_new(_) { } @@ -344,10 +457,10 @@ class CharacterEditor extends UIState { '${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml', buildCharacter() ); - undos.save(); #else _file_saveas(_); #end + undos.save(); } function _file_saveas(_) { @@ -357,311 +470,466 @@ class CharacterEditor extends UIState { undos.save(); } + function _file_exit(_) { + if (undos.unsaved) SaveWarning.triggerWarning(); + else {undos = null; FlxG.switchState(new CharacterSelection());} + } + function buildCharacter():String { + if (character.extra.exists(StageEditor.exID("bounds"))) + character.extra.remove(StageEditor.exID("bounds")); + var charXML:Xml = character.buildXML([ for (button in characterAnimsWindow.buttons.members) button.anim ]); - // clean - if (charXML.exists("gameOverChar") && character.gameOverCharacter == Flags.DEFAULT_GAMEOVER_CHARACTER) charXML.remove("gameOverChar"); - if (charXML.exists("camx") && character.cameraOffset.x == 0) charXML.remove("camx"); - if (charXML.exists("camy") && character.cameraOffset.y == 0) charXML.remove("camy"); - if (charXML.exists("holdTime") && character.holdTime == 4) charXML.remove("holdTime"); - if (charXML.exists("flipX") && !character.flipX) charXML.remove("flipX"); - if (charXML.exists("scale") && character.scale.x == 1) charXML.remove("scale"); - if (charXML.exists("antialiasing") && character.antialiasing) charXML.remove("antialiasing"); - return "<!DOCTYPE codename-engine-character>\n" + Printer.print(charXML, true); } - function _edit_undo(_) { - var undo = undos.undo(); - switch(undo) { - case null: - // do nothing - case CEditInfo(oldInfo, newInfo): - editInfo(oldInfo, false); - case CCreateAnim(animID, animData): - deleteAnim(animData.name, false); - case CEditAnim(name, oldData, animData): - editAnim(name, oldData, false); - case CDeleteAnim(animID, animData): - createAnim(animData, animID, false); - case CChangeOffset(name, change): - changeOffset(name, change * -1, false); - case CResetOffsets(oldOffsets): - for (anim => offsets in oldOffsets) { - character.animOffsets.set(anim, offsets.clone()); - ghosts.setOffsets(anim, offsets.clone()); + var clipboard:FlxPoint = FlxPoint.get(); + function _edit_copy_offset(_) { + clipboard.copyFrom(character.animOffsets[character.getAnimName()]); + } + function _edit_paste_offset(_) { + _set_offset(clipboard.x, clipboard.y); + } + + function _undo(undo:CharacterEditorChange) { + switch (undo) { + case null: // do nothing + case CCharEditPosition(oldPos, newPos): + characterPropertiesWindow.changePosition(oldPos.x, oldPos.y, false); + case CCharEditCamPosition(oldPos, newPos): + characterPropertiesWindow.changeCamPosition(oldPos.x, oldPos.y, false); + case CCharEditScale(oldScale, newScale): + characterPropertiesWindow.changeScale(oldScale, false); + case CCharEditFlipped(newFlipped): + characterPropertiesWindow.changeFlipX(!newFlipped, false); + case CCharEditAntialiasing(newAntialiasing): + characterPropertiesWindow.changeAntialiasing(!newAntialiasing, false); + case CCharEditDesignedAs(newIsPlayer): + changeCharacterDesginedAs(!newIsPlayer, false); + case CCharEditInfo(oldInfo, newInfo): + characterPropertiesWindow.editCharacterInfo(oldInfo, false); + case CCharEditSprite(fileID): + var cneisdPath:String = './.temp/__undo__${Type.getClassName(Type.getClass(FlxG.state))}__${fileID}.cneisd'; + if (FileSystem.exists(cneisdPath)) { + try { + var cneisdData:String = File.getContent(cneisdPath); + var imageSaveData:ImageSaveData = UIImageExplorer.deserializeSaveDataGlobal(cneisdData); + + UIImageExplorer.saveFilesGlobal(imageSaveData, '${Paths.getAssetsRoot()}/images/characters', () -> { + characterPropertiesWindow.changeSprite('${imageSaveData.directory.length > 0 ? '${imageSaveData.directory}/' : ""}' + imageSaveData.imageName); + }, false); + } catch (e) { + trace('ERROR COMPLETING UNDO: $e'); + }; } - - for (charButton in characterAnimsWindow.buttons.members) - charButton.updateInfo(charButton.anim, character.getAnimOffset(charButton.anim), ghosts.animGhosts[charButton.anim].visible); - - changeOffset(character.getAnimName(), FlxPoint.get(0, 0), false); // apply da new offsets + case CAnimCreate(animID, animData): + characterAnimsWindow.deleteAnimation(characterAnimsWindow.buttons.members[animID], false); + case CAnimDelete(animID, animData): + characterAnimsWindow.addAnimation(animData, animID, false); + playAnimation(animData.name); + case CAnimEditOrder(animID, newAnimID): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[newAnimID]; + characterAnimsWindow.buttons.members.remove(button); + characterAnimsWindow.buttons.members.insert(animID, button); + case CAnimEditName(animID, oldName, newName): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeName(oldName, false); + case CAnimEditAnim(animID, oldAnim, newAnim): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeAnim(oldAnim, false); + case CAnimEditOffset(animID, oldOffset, newOffset): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeOffset(oldOffset.x, oldOffset.y, false); + case CAnimEditFPS(animID, oldFPS, newFPS): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeFPS(oldFPS, false); + case CAnimEditLooping(animID, newLooping): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeLooping(!newLooping, false); + case CAnimEditIndices(animID, oldIndicies, newIndicies): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeIndicies(oldIndicies, false); + case CCharClearOffsets(oldOffsets): + for (anim => offset in oldOffsets) + if (characterAnimsWindow.animButtons[anim] != null) + characterAnimsWindow.animButtons[anim].changeOffset(offset.x, offset.y, false); } } - function _edit_redo(_) { - var redo = undos.redo(); - switch(redo) { - case null: - // do nothing - case CEditInfo(oldInfo, newInfo): - editInfo(newInfo, false); - case CCreateAnim(animID, animData): - createAnim(animData, animID, false); - case CEditAnim(name, oldData, animData): - editAnim(oldData.name, animData, false); - case CDeleteAnim(animID, animData): - deleteAnim(animData.name, false); - case CChangeOffset(name, change): - changeOffset(name, change, false); - case CResetOffsets(oldOffsets): + function _edit_undo(_) { + if (draggingCharacter) return; + _undo(undos.undo()); + } + + function _redo(redo:CharacterEditorChange) { + switch (redo) { + case null: // do nothing + case CCharEditPosition(oldPos, newPos): + characterPropertiesWindow.changePosition(newPos.x, newPos.y, false); + case CCharEditCamPosition(oldPos, newPos): + characterPropertiesWindow.changeCamPosition(newPos.x, newPos.y, false); + case CCharEditScale(oldScale, newScale): + characterPropertiesWindow.changeScale(newScale, false); + case CCharEditFlipped(newFlipped): + characterPropertiesWindow.changeFlipX(newFlipped, false); + case CCharEditAntialiasing(newAntialiasing): + characterPropertiesWindow.changeAntialiasing(newAntialiasing, false); + case CCharEditDesignedAs(newIsPlayer): + changeCharacterDesginedAs(newIsPlayer, false); + case CCharEditInfo(oldInfo, newInfo): + characterPropertiesWindow.editCharacterInfo(newInfo, false); + case CCharEditSprite(fileID): + var cneisdPath:String = './.temp/__redo__${Type.getClassName(Type.getClass(FlxG.state))}__${fileID}.cneisd'; + if (FileSystem.exists(cneisdPath)) { + try { + var cneisdData:String = File.getContent(cneisdPath); + var imageSaveData:ImageSaveData = UIImageExplorer.deserializeSaveDataGlobal(cneisdData); + + UIImageExplorer.saveFilesGlobal(imageSaveData, '${Paths.getAssetsRoot()}/images/characters', () -> { + characterPropertiesWindow.changeSprite('${imageSaveData.directory.length > 0 ? '${imageSaveData.directory}/' : ""}' + imageSaveData.imageName); + }, false); + } catch (e) { + trace('ERROR COMPLETING UNDO: $e'); + }; + } + case CAnimCreate(animID, animData): + characterAnimsWindow.addAnimation(animData, animID, false); + playAnimation(animData.name); + case CAnimDelete(animID, animData): + characterAnimsWindow.deleteAnimation(characterAnimsWindow.buttons.members[animID], false); + case CAnimEditOrder(animID, newAnimID): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + characterAnimsWindow.buttons.members.remove(button); + characterAnimsWindow.buttons.members.insert(newAnimID, button); + case CAnimEditName(animID, oldName, newName): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeName(newName, false); + case CAnimEditAnim(animID, oldAnim, newAnim): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeAnim(newAnim, false); + case CAnimEditOffset(animID, oldOffset, newOffset): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeOffset(newOffset.x, newOffset.y, false); + case CAnimEditFPS(animID, oldFPS, newFPS): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeFPS(newFPS, false); + case CAnimEditLooping(animID, newLooping): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeLooping(newLooping, false); + case CAnimEditIndices(animID, oldIndicies, newIndicies): + var button:CharacterAnimButton = characterAnimsWindow.buttons.members[animID]; + button.changeIndicies(newIndicies, false); + case CCharClearOffsets(oldOffsets): clearOffsets(false); } } - function _char_add_anim(_) { - createAnimWithUI(); + function _edit_redo(_) { + _redo(undos.redo()); } - function _char_edit_anim(_) { - editAnimWithUI(character.getAnimName()); + function _edit_info(_) + characterPropertiesWindow.editCharacterInfoUI(); + function _edit_sprite(_) {} + + function _offsets_left(_) _change_offset(-1, 0); + function _offsets_up(_) _change_offset(0, -1); + function _offsets_down(_) _change_offset(0, 1); + function _offsets_right(_) _change_offset(1, 0); + function _offsets_extra_left(_) _change_offset(-5, 0); + function _offsets_extra_up(_) _change_offset(0, -5); + function _offsets_extra_down(_) _change_offset(0, 5); + function _offsets_extra_right(_) _change_offset(5, 0); + + function _offsets_drag_offsets_mouse(t) { + t.icon = (Options.characterDragging = !Options.characterDragging) ? 1 : 0; } - function _char_remove_anim(_) { - deleteAnim(character.getAnimName()); - } - - function _char_edit_info(_) { - editInfoWithUI(); - } - - public function createAnimWithUI() { - FlxG.state.openSubState(new CharacterAnimScreen(null, (_) -> { - if (_ != null) createAnim(_); - })); - } - - public function editAnimWithUI(name:String) { - FlxG.state.openSubState(new CharacterAnimScreen(character.animDatas.get(name), (_) -> { - if (_ != null) editAnim(name, _); - })); - } - - public function createAnim(animData:AnimData, animID:Int = -1, addToUndo:Bool = true) { - XMLUtil.addAnimToSprite(character, animData); - ghosts.createGhost(animData.name); - var newButton = new CharacterAnimButtons(0, 0, animData.name, FlxPoint.get(animData.x,animData.y)); - if (animID == -1) characterAnimsWindow.add(newButton); - else characterAnimsWindow.insert(newButton, animID); - - playAnimation(animData.name); - - if (addToUndo) - undos.addToUndo(CCreateAnim(character.getNameList().length, animData)); - } - - public function editAnim(name:String, animData:AnimData, addToUndo:Bool = true) { - var oldAnimData:AnimData = character.animDatas.get(name); - var buttoner:CharacterAnimButtons = null; - for (button in characterAnimsWindow.buttons.members) - if (button.anim == name) buttoner = button; - - ghosts.removeGhost(name); - XMLUtil.addAnimToSprite(character, animData); - ghosts.createGhost(animData.name); - buttoner.updateInfo(animData.name, character.getAnimOffset(animData.name), ghosts.animGhosts[animData.name].visible); - - if (character.getAnimName() == animData.name) // update anim ifs its currently selected - playAnimation(animData.name); - - if (addToUndo) - undos.addToUndo(CEditAnim(animData.name, oldAnimData, animData)); - } - - public function deleteAnim(name:String, addToUndo:Bool = true) { - var animOrder:Array<String> = [ - for (button in characterAnimsWindow.buttons.members) - button.anim - ]; - - if (character.getNameList().length-1 == 0) - @:privateAccess character.animation._curAnim = null; - else - playAnimation(animOrder[Std.int(Math.abs(animOrder.indexOf(name)-1))]); - - for(button in characterAnimsWindow.buttons.members) - if(button.anim == name) characterAnimsWindow.remove(button); - - // undo shit blah blah - var oldID:Int = animOrder.indexOf(name); - var oldAnimData:AnimData = character.animDatas.get(name); - - ghosts.removeGhost(name); - character.removeAnimation(name); - if (character.animOffsets.exists(name)) character.animOffsets.remove(name); - if (character.animDatas.exists(name)) character.animDatas.remove(name); - - if (addToUndo) - undos.addToUndo(CDeleteAnim(oldID, oldAnimData)); - } - - public function editInfoWithUI() { - FlxG.state.openSubState(new CharacterInfoScreen(character, (_) -> { - if (_ != null) editInfo(_); - })); - } - - public function editInfo(newInfo:Xml, addToUndo:Bool = true) { - var lastAnim:String = character.getAnimName(); - - var oldInfo = character.buildXML(); - character.applyXML(new Access(newInfo)); - ghosts.updateInfos(newInfo); - - playAnimation(lastAnim); - - if (addToUndo) - undos.addToUndo(CEditInfo(oldInfo, newInfo)); - } - - public function ghostAnim(anim:String) { - var ghost:Character = ghosts.animGhosts.get(anim); - ghost.visible = !ghost.visible; - - var animButton:CharacterAnimButtons = null; - for (button in characterAnimsWindow.buttons.members) - if (button.anim == anim) animButton = button; - animButton.updateInfo(anim, character.getAnimOffset(anim), ghost.visible); - } - - function _playback_play_anim(_) { - if (character.getNameList().length != 0) - playAnimation(character.getAnimName()); - } - - function _playback_stop_anim(_) { - if (character.getNameList().length != 0) - character.stopAnimation(); - } - - public function playAnimation(anim:String) { - character.playAnim(anim, true); - - for(i in characterAnimsWindow.buttons.members) - i.alpha = i.anim == anim ? 1 : 0.25; - } - - function _offsets_left(_) { - changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? 1 : -1, 0)); - } - - function _offsets_up(_) { - changeOffset(character.getAnimName(), FlxPoint.get(0, 1)); - } - - function _offsets_down(_) { - changeOffset(character.getAnimName(), FlxPoint.get(0, -1)); - } - - function _offsets_right(_) { - changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? -1 : 1, 0)); - } - - function _offsets_extra_left(_) { - changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? 5 : -5, 0)); - } - - function _offsets_extra_up(_) { - changeOffset(character.getAnimName(), FlxPoint.get(0, 5)); - } - - function _offsets_extra_down(_) { - changeOffset(character.getAnimName(), FlxPoint.get(0, -5)); - } - - function _offsets_extra_right(_) { - changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? -5 : 5, 0)); - } - - function _offsets_clear(_) { - clearOffsets(); - } - - function changeOffset(anim:String, change:FlxPoint, addToUndo:Bool = true) { - if (character.getNameList().length == 0) return; - - var animData = character.animDatas.get(anim); - if (animData != null) { - animData.x += change.x; - animData.y += change.y; - } - - character.animOffsets.set(anim, character.getAnimOffset(anim) + change); - for (i in characterAnimsWindow.buttons.members) - if (i.anim == anim) - i.updateInfo(anim, character.getAnimOffset(anim), ghosts.animGhosts[anim].visible); - character.frameOffset.set(character.getAnimOffset(anim).x, character.getAnimOffset(anim).y); - - ghosts.updateOffsets(anim, change); - - if (addToUndo) - undos.addToUndo(CChangeOffset(anim, change)); - } + function _offsets_clear(_) clearOffsets(); function clearOffsets(addToUndo:Bool = true) { - if (character.getNameList().length == 0) return; - var oldOffsets:Map<String, FlxPoint> = [ for (anim => offsets in character.animOffsets) anim => offsets.clone() ]; - for (anim in character.getNameList()) { - character.animOffsets[anim].zero(); - for (i in characterAnimsWindow.buttons.members) - if (i.anim == anim) - i.updateInfo(anim, character.getAnimOffset(anim), ghosts.animGhosts[anim].visible); + + for (anim => button in characterAnimsWindow.animButtons) + button.changeOffset(0, 0, false); + + if (addToUndo) undos.addToUndo(CCharClearOffsets(oldOffsets)); + } + + function _change_offset(x:Float, y:Float) { + if (characterFakeAnim == null) return; + _set_offset( + character.animOffsets[characterFakeAnim].x - x, + character.animOffsets[characterFakeAnim].y - y + ); + } + + function _set_offset(x:Float, y:Float) { + if (characterAnimsWindow.animButtons[characterFakeAnim] == null || !characterAnimsWindow.animButtons[characterFakeAnim].valid) return; + characterAnimsWindow.animButtons.get(characterFakeAnim).changeOffset( + FlxMath.roundDecimal(x, 2), FlxMath.roundDecimal(y, 2) + ); + } + + function buildStagesUI() { + var stageTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[stageIndex]; + var newChilds:Array<UIContextMenuOption> = []; + + var stageFileList = Stage.getList(true); + if (stageFileList.length == 0) stageFileList = Stage.getList(false); + + for (stage in stageFileList) + newChilds.push({ + label: 'Use "$stage"?', + icon: currentStage == stage ? 1 : 0, + onSelect: (_) -> {changeStage(stage);} + }); + + newChilds.push(null); + newChilds.push({ + label: "Use No Stage?", + icon: currentStage == null ? 1 : 0, + onSelect: (_) -> {changeStage(null);} + }); + + if (stageTopButton != null) stageTopButton.contextMenu = newChilds; + return newChilds; + } + + function changeStage(__stage:String) { + if (stage != null) { + if (stage.characterPoses.exists(stagePosition)) + stage.characterPoses[stagePosition].revertCharacter(character); + + for (sprite in stageSprites) { + remove(sprite); + sprite.destroy(); + } + stageSprites.clear(); + + stage.destroy(); + stage = null; + } + remove(character); + + if (__stage == null) { + updateStagePositions([]); + changeStagePosition("NONE"); + + add(character); + } else { + stage = new Stage(__stage, this, false); + stage.onAddSprite = (sprite:FlxBasic) -> { + sprite.cameras = [charCamera]; + stageSprites.push(sprite); + }; + + stage.loadXml(stage.stageXML, true); + add(stage); + + stagePosition = character.playerOffsets || character.isPlayer ? "BOYFRIEND" : "DAD"; + updateStagePositions([ + for (pose in stage.characterPoses.keys()) + pose.toUpperCase() + ]); + + changeStagePosition(stagePosition); + changeCharacterDesginedAs(stagePosition.toUpperCase() == "BOYFRIEND", false); } - changeOffset(character.getAnimName(), FlxPoint.get(), false); - ghosts.clearOffsets(); - - if (addToUndo) - undos.addToUndo(CResetOffsets(oldOffsets)); + currentStage = __stage; + buildStagesUI(); } - var zoom(default, set):Float = 0; - var __camZoom(default, set):Float = 1; - function set_zoom(val:Float) { - return zoom = FlxMath.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling + function updateStagePositions(stagePositions:Array<String>) @:privateAccess { + if (stage != null && stagePositions.length > 0) { + characterPropertiesWindow.testAsDropDown.items = + UIDropDown.getItems(characterPropertiesWindow.testAsDropDown.options = stagePositions); + } else + characterPropertiesWindow.testAsDropDown.items = + UIDropDown.getItems(characterPropertiesWindow.testAsDropDown.options = ["NONE"]); } - function set___camZoom(val:Float) { - return __camZoom = FlxMath.bound(val, 0.1, 3); + + public function changeStagePosition(position:String) { + if (stage != null && position != "NONE") { + if (stage.characterPoses.exists(stagePosition)) + stage.characterPoses[stagePosition].revertCharacter(character); + + stagePosition = position.toLowerCase(); + remove(character); + + if (stage.characterPoses.exists(stagePosition)) + stage.applyCharStuff(character, stagePosition, 0); + _animation_play(null); + } else + stagePosition = position.toLowerCase(); + + characterPropertiesWindow.testAsDropDown.index = characterPropertiesWindow.testAsDropDown.options.indexOf(stagePosition.toUpperCase()); + characterPropertiesWindow.testAsDropDown.label.text = stagePosition.toUpperCase(); + } + + public function changeCharacterDesginedAs(player:Bool, addToUndo:Bool = true) @:privateAccess { + if (stage == null) { + changeCharacterIsPlayer(player); + return; + } + + if (stage.characterPoses.exists(stagePosition)) + stage.characterPoses[stagePosition].revertCharacter(character); + + changeCharacterIsPlayer(player); + updateCharacterStagePosition(); + _animation_play(null); + + characterPropertiesWindow.designedAsDropDown.index = characterPropertiesWindow.designedAsDropDown.options.indexOf(player ? "BOYFRIEND" : "DAD"); + characterPropertiesWindow.designedAsDropDown.label.text = player ? "BOYFRIEND" : "DAD"; + + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditDesignedAs(player)); + } + + public inline function updateCharacterStagePosition() { + remove(character); + if (stage.characterPoses.exists(stagePosition)) + stage.applyCharStuff(character, stagePosition, 0); + } + + public function changeCharacterIsPlayer(player:Bool) @:privateAccess { + if (character.__swappedLeftRightAnims) + character.swapLeftRightAnimations(); + if (character.isPlayer) + character.flipX = !character.__baseFlipped; + + character.isPlayer = player; + character.fixChar(false, false); } function _view_zoomin(_) { zoom += 0.25; __camZoom = Math.pow(2, zoom); } + function _view_zoomout(_) { zoom -= 0.25; __camZoom = Math.pow(2, zoom); } + function _view_zoomreset(_) { zoom = 0; __camZoom = Math.pow(2, zoom); } + + function _view_character_show_hitbox(t) { + t.icon = (Options.characterHitbox = !Options.characterHitbox) ? 1 : 0; + characterGizmo.boxGizmo = Options.characterHitbox; + } + + function _view_character_show_camera(t) { + t.icon = (Options.characterCamera = !Options.characterCamera) ? 1 : 0; + characterGizmo.cameraGizmo = Options.characterCamera; + } + + function _view_character_show_axis(t) { + t.icon = (Options.characterAxis = !Options.characterAxis) ? 1 : 0; + axisGizmo.visible = Options.characterAxis; + } + + function _view_focus_character(_) { + if (character == null) return; + + var characterMidpoint:FlxPoint = character.getMidpoint(); + characterMidpoint.x -= (FlxG.width/2)-character.globalOffset.x; + characterMidpoint.y -= (FlxG.height/2)-character.globalOffset.y; + _nextScroll.set(characterMidpoint.x, characterMidpoint.y); + characterMidpoint.put(); + } + + var zoom(default, set):Float = 0; + var __camZoom(default, set):Float = 1; + function set_zoom(val:Float) { + return zoom = CoolUtil.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling + } + function set___camZoom(val:Float) { + return __camZoom = CoolUtil.bound(val, 0.1, 3); + } + + function _animation_play(_) { + if (character.getNameList().length != 0) + playAnimation(characterFakeAnim); + } + + function _animation_stop(_) { + if (character.getNameList().length != 0) + character.stopAnimation(); + } + + function _animation_up(_) + playAnimation( + characterAnimsWindow.animsList[ + FlxMath.wrap( + characterAnimsWindow.animsList.indexOf(characterFakeAnim) - 1, + 0, characterAnimsWindow.animsList.length-1 + )] + ); + function _animation_down(_) + playAnimation( + characterAnimsWindow.animsList[FlxMath.wrap( + characterAnimsWindow.animsList.indexOf(characterFakeAnim) + 1, + 0, characterAnimsWindow.animsList.length-1 + )] + ); + + function _animation_toggle_anim_playing_offsets(t) { + t.icon = (Options.playAnimOnOffset = !Options.playAnimOnOffset) ? 1 : 0; + } + + // The animation thats playing regardless if its valid or not + public var characterFakeAnim:String = ""; + public function playAnimation(anim:String) @:privateAccess { + characterFakeAnim = anim; + if (characterAnimsWindow.animButtons[anim] != null && characterAnimsWindow.animButtons[anim].valid) { + character.playAnim(anim, true); + character.colorTransform.__identity(); + } else { + var validAnimation:String = characterAnimsWindow.findValid(); + if (validAnimation != null) character.playAnim(validAnimation, true); + _animation_stop(null); + character.colorTransform.color = 0xFFEF0202; + } + + for(i in characterAnimsWindow.buttons.members) + i.alpha = i.anim == anim ? 1 : 0.25; + } #end } -enum CharacterChange { - CEditInfo(oldInfo:Xml, newInfo:Xml); - CCreateAnim(animID:Int, animData:AnimData); - CEditAnim(name:String, oldData:AnimData, animData:AnimData); - CDeleteAnim(animID:Int, animData:AnimData); - CChangeOffset(name:String, change:FlxPoint); - CResetOffsets(oldOffsets:Map<String, FlxPoint>); +enum CharacterEditorChange { + CCharEditPosition(oldPos:FlxPoint, newPos:FlxPoint); + CCharEditCamPosition(oldPos:FlxPoint, newPos:FlxPoint); + CCharEditScale(oldScale:Float, newScale:Float); + CCharEditFlipped(newFlipped:Bool); + CCharEditAntialiasing(newAntialiasing:Bool); + CCharEditDesignedAs(newIsPlayer:Bool); + + CCharEditInfo(oldInfo:CharacterExtraInfo, newInfo:CharacterExtraInfo); + CCharEditSprite(fileID:Int); + + CAnimCreate(animID:Int, animData:AnimData); + CAnimDelete(animID:Int, animData:AnimData); + CAnimEditOrder(animID:Int, newAnimID:Int); + + CAnimEditName(animID:Int, oldName:String, newName:String); + CAnimEditAnim(animID:Int, oldAnim:String, newAnim:String); + CAnimEditOffset(animID:Int, oldOffset:FlxPoint, newOffset:FlxPoint); + CAnimEditFPS(animID:Int, oldFPS:Float, newFPS:Float); + CAnimEditLooping(animID:Int, newLooping:Bool); + CAnimEditIndices(animID:Int, oldIndicies:Array<Int>, newIndicies:Array<Int>); + + CCharClearOffsets(oldOffsets:Map<String, FlxPoint>); } \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterGhost.hx b/source/funkin/editors/character/CharacterGhost.hx new file mode 100644 index 00000000..81c7c5dd --- /dev/null +++ b/source/funkin/editors/character/CharacterGhost.hx @@ -0,0 +1,91 @@ +package funkin.editors.character; + +import funkin.backend.system.FakeCamera.FakeCallCamera; +import openfl.geom.ColorTransform; +import flixel.animation.FlxAnimation; +import funkin.game.Character; + +typedef AtlasState = { + var oldAnim:String; + var oldFrame:Int; + var oldTick:Float; + var oldPlaying:Bool; +}; + +class CharacterGhost extends Character { + public var ghosts:Array<String> = []; + public override function draw() @:privateAccess { + ghostDraw = FakeCallCamera.instance.ignoreDraws = true; + + var wasInvalidFrame:Bool = !colorTransform.__isDefault(false); + colorTransform.__identity(); + + if (animateAtlas != null) { + storeAtlasState(); + for (anim in ghosts) { + animateAtlas.anim.play(anim, true, false, 0); + setAnimOffset(anim); + + alpha = 0.4; color = 0xFFAEAEAE; + super.draw(); + } + + if (ghosts.length > 0) restoreAtlasState(); + } + else { + for (anim in ghosts) @:privateAccess { + alpha = 0.4; color = 0xFFAEAEAE; + + var flxanim:FlxAnimation = animation._animations.get(anim); + var frameIndex:Int = flxanim.frames.getDefault([0])[0]; + frame = frames.frames[frameIndex]; + + setAnimOffset(anim); + super.draw(); + } + + if (ghosts.length > 0) + frame = frames.frames[animation.frameIndex]; + } + ghostDraw = FakeCallCamera.instance.ignoreDraws = false; + + alpha = 1; color = 0xFFFFFFFF; + if (wasInvalidFrame) { + frameOffset.set(0, 0); + offset.set(globalOffset.x * (isPlayer != playerOffsets ? 1 : -1), -globalOffset.y); + colorTransform.color = 0xFFEF0202; + } else + setAnimOffset(animateAtlas != null ? atlasPlayingAnim : animation.name); + + super.draw(); + } + + public function setAnimOffset(anim:String) { + var daOffset:FlxPoint = animOffsets.get(anim); + if (daOffset != null) { + frameOffset.set(daOffset.x, daOffset.y); + daOffset.putWeak(); + } + + offset.set(globalOffset.x * (isPlayer != playerOffsets ? 1 : -1), -globalOffset.y); + } + + // This gets annoying lmao -lunar + private var atlasState:AtlasState = null; + public function storeAtlasState():AtlasState @:privateAccess { + return atlasState = { + oldAnim: atlasPlayingAnim, + oldFrame: animateAtlas.anim.curFrame, + oldTick: animateAtlas.anim._tick, + oldPlaying: animateAtlas.anim.isPlaying, + }; + } + + public function restoreAtlasState(state:AtlasState = null) @:privateAccess { + if (state == null) state = atlasState; + + animateAtlas.anim.play(state.oldAnim, true, false, state.oldFrame); + animateAtlas.anim._tick = state.oldTick; + animateAtlas.anim.isPlaying = state.oldPlaying; + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterGhostsHandler.hx b/source/funkin/editors/character/CharacterGhostsHandler.hx deleted file mode 100644 index fe823898..00000000 --- a/source/funkin/editors/character/CharacterGhostsHandler.hx +++ /dev/null @@ -1,75 +0,0 @@ -package funkin.editors.character; - -import flixel.math.FlxPoint; -import funkin.game.Character; -import haxe.xml.Access; - -class CharacterGhostsHandler extends FlxTypedGroup<Character> { - public var character:Character; - public var animGhosts:Map<String, Character> = []; - - public function new(character:Character) { - this.character = character; - super(); - - for (anim in character.getNameList()) - createGhost(anim); - } - - public function createGhost(anim:String) { - var ghost:Character = new Character(0,0, character.curCharacter); - ghost.applyXML(character.xml); // apply cur character stuff - ghost.playAnim(anim); - ghost.stopAnimation(); - ghost.alpha = 0.5; - ghost.visible = false; - add(ghost); - - animGhosts.set(anim, ghost); - } - - public function removeGhost(anim:String) { - var ghost:Character = animGhosts.get(anim); - @:privateAccess if (ghost != null) { - ghost.animation._curAnim = null; - animGhosts.remove(anim); - ghost.destroy(); - } - } - - public function updateInfos(xml:Xml) { - for (anim => ghost in animGhosts) { - ghost.applyXML(new Access(xml)); - ghost.playAnim(anim); - ghost.stopAnimation(); - } - - rePositionGhosts(); - } - - public function updateOffsets(name:String, change:FlxPoint) { - for (anim => ghost in animGhosts) - ghost.animOffsets.set(name, ghost.getAnimOffset(name) + change); - - rePositionGhosts(); - } - - public function setOffsets(name:String, offset:FlxPoint) { - for (anim => ghost in animGhosts) - ghost.animOffsets.set(name, offset); - - rePositionGhosts(); - } - - public function clearOffsets() { - for (anim => ghost in animGhosts) - ghost.animOffsets[anim].zero(); - - rePositionGhosts(); - } - - inline function rePositionGhosts() { - for (ghost in animGhosts) - ghost.frameOffset.set(ghost.getAnimOffset(ghost.getAnimName()).x, ghost.getAnimOffset(ghost.getAnimName()).y); - } -} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterGizmos.hx b/source/funkin/editors/character/CharacterGizmos.hx new file mode 100644 index 00000000..acfd6382 --- /dev/null +++ b/source/funkin/editors/character/CharacterGizmos.hx @@ -0,0 +1,76 @@ +package funkin.editors.character; + +import funkin.editors.stage.StageEditor; +import flixel.math.FlxRect; +import funkin.game.Character; + +class CharacterGizmos extends FlxSprite { + public var character:Character; + + public var boxGizmo:Bool = true; + public var cameraGizmo:Bool = true; + + public override function draw() { + super.draw(); + + if (character == null) return; + + if (boxGizmo) drawHitbox(); + if (cameraGizmo) drawCamera(); + } + + public function drawHitbox() { + for (camera in cameras) { + if (character.animateAtlas != null) { + var bounds:FlxRect = cast character.extra.get(StageEditor.exID("bounds")); + character._rect.copyFrom(bounds.getDefault(FlxRect.weak())); + + character._rect.x -= character.cameras[0].viewX; + character._rect.y -= character.cameras[0].viewY; + } else if (character._matrix != null && character.frame != null) { + character._rect.set( + character._matrix.tx, character._matrix.ty, + Math.abs(character.frame.frame.width * character._matrix.a), + Math.abs(character.frame.frame.height * character._matrix.d) + ); + if (character._matrix.a < 0) character._rect.x -= character._rect.width; + if (character._matrix.d < 0) character._rect.y -= character._rect.height; + + character._rect.offset(-character.cameras[0].viewMarginLeft, -character.cameras[0].viewMarginTop); + } + + character._rect.x *= character.cameras[0].zoom; + character._rect.y *= character.cameras[0].zoom; + character._rect.width *= character.cameras[0].zoom; + character._rect.height *= character.cameras[0].zoom; + + if (DrawUtil.line == null) DrawUtil.createDrawers(); + DrawUtil.line.camera = camera; DrawUtil.line.alpha = 0.85; + + DrawUtil.drawRect(character._rect, 1, 0xFF007B8F); + } + } + + public function drawCamera() { + for (camera in cameras) { + var camPos:FlxPoint = character.getCameraPosition(); + camPos -= camera.scroll; + + if (DrawUtil.line == null) DrawUtil.createDrawers(); + DrawUtil.line.camera = camera; DrawUtil.line.alpha = 1; + + DrawUtil.drawLine( + CoolUtil.pointToScreenPosition(FlxPoint.weak(camPos.x - 8, camPos.y), FlxG.camera, FlxPoint.weak()), + CoolUtil.pointToScreenPosition(FlxPoint.weak(camPos.x + 8, camPos.y), FlxG.camera, FlxPoint.weak()), + 1, 0xFF00A0B9); + + DrawUtil.drawLine( + CoolUtil.pointToScreenPosition(FlxPoint.weak(camPos.x, camPos.y - 8), FlxG.camera, FlxPoint.weak()), + CoolUtil.pointToScreenPosition(FlxPoint.weak(camPos.x, camPos.y + 8), FlxG.camera, FlxPoint.weak()), + 1, 0xFF00A0B9); + + camPos.put(); + + } + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index 3fd31268..406c74d9 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -1,36 +1,40 @@ package funkin.editors.character; -import flixel.math.FlxPoint; +import flixel.util.FlxColor; +import funkin.editors.stage.StageEditor; import funkin.editors.extra.PropertyButton; +import flixel.graphics.FlxGraphic; +import openfl.geom.Point; +import openfl.geom.Rectangle; +import openfl.display.BitmapData; import funkin.game.Character; -import haxe.xml.Access; + +using funkin.backend.utils.BitmapUtil; + +typedef CharacterExtraInfo = { + var icon:String; + var iconColor:Null<FlxColor>; + var holdTime:Float; + var customProperties:Map<String, Dynamic>; +} class CharacterInfoScreen extends UISubstateWindow { public var character:Character; - public var spriteTextBox:UITextBox; - public var iconTextBox:UITextBox; - public var iconSprite:FlxSprite; - public var gameOverCharTextBox:UITextBox; - public var antialiasingCheckbox:UICheckbox; - public var flipXCheckbox:UICheckbox; + public var iconColorPicker:UIIconColorPicker; public var iconColorWheel:UIColorwheel; - public var positionXStepper:UINumericStepper; - public var positionYStepper:UINumericStepper; - public var cameraXStepper:UINumericStepper; - public var cameraYStepper:UINumericStepper; - public var scaleStepper:UINumericStepper; - public var singTimeStepper:UINumericStepper; + + public var useDurationCheckbox:UICheckbox; + public var durationStepper:UINumericStepper; + public var customPropertiesButtonList:UIButtonList<PropertyButton>; - public var isPlayerCheckbox:UICheckbox; - public var isGFCheckbox:UICheckbox; public var saveButton:UIButton; public var closeButton:UIButton; - public var onSave:(xml:Xml) -> Void = null; + public var onSave:(info:CharacterExtraInfo) -> Void = null; - public function new(character:Character, ?onSave:(xml:Xml) -> Void) { + public function new(character:Character, onSave:(info:CharacterExtraInfo) -> Void) { this.character = character; this.onSave = onSave; super(); @@ -41,8 +45,7 @@ class CharacterInfoScreen extends UISubstateWindow { public override function create() { winTitle = translate("win-title"); - winWidth = 1014; - winHeight = 600; + winWidth = 824; winHeight = 400; super.create(); @@ -50,161 +53,68 @@ class CharacterInfoScreen extends UISubstateWindow { add(new UIText(ui.x, ui.y - 24, 0, text)); var title:UIText; - add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("spriteData"), 28)); + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Character Info", 28)); - spriteTextBox = new UITextBox(title.x, title.y + title.height + 38, character.sprite, 200); - add(spriteTextBox); - addLabelOn(spriteTextBox, translate("sprite")); + iconColorPicker = new UIIconColorPicker(title.x, title.y + title.height + 38, character.getIcon(), character.antialiasing, null); + add(iconColorPicker); + addLabelOn(iconColorPicker, "Icon"); - iconTextBox = new UITextBox(spriteTextBox.x + 200 + 26, spriteTextBox.y, character.getIcon(), 150); - iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);} - add(iconTextBox); - addLabelOn(iconTextBox, translate("icon")); - - updateIcon(character.getIcon()); - - gameOverCharTextBox = new UITextBox(iconTextBox.x + 150 + (75 + 12), iconTextBox.y, character.gameOverCharacter, 200); - add(gameOverCharTextBox); - addLabelOn(gameOverCharTextBox, translate("gameOverChar")); - - antialiasingCheckbox = new UICheckbox(spriteTextBox.x, spriteTextBox.y + 10 + 32 + 28, translate("antialiasing"), character.antialiasing); - add(antialiasingCheckbox); - addLabelOn(antialiasingCheckbox, translate("antialiased")); - - flipXCheckbox = new UICheckbox(antialiasingCheckbox.x + 172, spriteTextBox.y + 10 + 32 + 28, translate("flipX"), character.flipX); - add(flipXCheckbox); - addLabelOn(flipXCheckbox, translate("flipped")); - - iconColorWheel = new UIColorwheel(gameOverCharTextBox.x + 200 + 20, gameOverCharTextBox.y, character.iconColor); + iconColorWheel = new UIColorwheel(iconColorPicker.x+12+125+12+22, iconColorPicker.y, character.iconColor); add(iconColorWheel); addLabelOn(iconColorWheel, translate("iconColor")); if (character.iconColor != null) iconColorWheel.colorChanged = true; + iconColorPicker.colorWheel = iconColorWheel; - add(title = new UIText(spriteTextBox.x, spriteTextBox.y + 10 + 46 + 84, 0, translate("characterData"), 28)); + durationStepper = new UINumericStepper(iconColorWheel.x, iconColorWheel.y + 125 + 36, character.holdTime == -1 ? 4 : character.holdTime, 0.001, 2, 0, 9999999, 74); + add(durationStepper); + addLabelOn(durationStepper, "Sing Duration"); - positionXStepper = new UINumericStepper(title.x, title.y + title.height + 36, character.globalOffset.x, 0.001, 2, null, null, 84); - add(positionXStepper); - addLabelOn(positionXStepper, translate("position")); + useDurationCheckbox = new UICheckbox(durationStepper.x + durationStepper.bWidth + 20, durationStepper.y+6, "Use Sing Duration?", character.holdTime != -1); + useDurationCheckbox.onChecked = (checked:Bool) -> {durationStepper.selectable = checked;}; + add(useDurationCheckbox); - add(new UIText(positionXStepper.x + 84 - 32 + 0, positionXStepper.y + 9, 0, ",", 22)); + durationStepper.selectable = useDurationCheckbox.checked; - positionYStepper = new UINumericStepper(positionXStepper.x + 84 - 32 + 26, positionXStepper.y, character.globalOffset.y, 0.001, 2, null, null, 84); - add(positionYStepper); - - cameraXStepper = new UINumericStepper(positionYStepper.x + 36 + 84 - 32, positionYStepper.y, character.cameraOffset.x, 0.001, 2, null, null, 84); - add(cameraXStepper); - addLabelOn(cameraXStepper, translate("cameraPosition")); - - add(new UIText(cameraXStepper.x + 84 - 32 + 0, cameraXStepper.y + 9, 0, ",", 22)); - - cameraYStepper = new UINumericStepper(cameraXStepper.x + 84 - 32 + 26, cameraXStepper.y, character.cameraOffset.y, 0.001, 2, null, null, 84); - add(cameraYStepper); - - scaleStepper = new UINumericStepper(cameraYStepper.x + 84 - 32 + 90, cameraYStepper.y, character.scale.x, 0.001, 2, null, null, 74); - add(scaleStepper); - addLabelOn(scaleStepper, translate("scale")); - - singTimeStepper = new UINumericStepper(scaleStepper.x + 74 - 32 + 36, scaleStepper.y, character.holdTime, 0.001, 2, null, null, 74); - add(singTimeStepper); - addLabelOn(singTimeStepper, translate("singDuration")); - - customPropertiesButtonList = new UIButtonList<PropertyButton>(singTimeStepper.x + singTimeStepper.width + 200, singTimeStepper.y, 290, 200, '', FlxPoint.get(280, 35), null, 5); + customPropertiesButtonList = new UIButtonList<PropertyButton>(iconColorWheel.x+iconColorWheel.bWidth+22, iconColorWheel.y, 290, 200, '', FlxPoint.get(280, 35), null, 5); customPropertiesButtonList.frames = Paths.getFrames('editors/ui/inputbox'); customPropertiesButtonList.cameraSpacing = 0; customPropertiesButtonList.addButton.callback = function() { customPropertiesButtonList.add(new PropertyButton("newProperty", "valueHere", customPropertiesButtonList)); } + for (prop=>val in character.extra) - customPropertiesButtonList.add(new PropertyButton(prop, val, customPropertiesButtonList)); + if (prop != StageEditor.exID("bounds")) + customPropertiesButtonList.add(new PropertyButton(prop, val, customPropertiesButtonList)); + add(customPropertiesButtonList); addLabelOn(customPropertiesButtonList, translate("customValues")); - isPlayerCheckbox = new UICheckbox(positionXStepper.x, positionXStepper.y + 10 + 32 + 28, translate("isPlayer"), character.playerOffsets); - add(isPlayerCheckbox); - - isGFCheckbox = new UICheckbox(isPlayerCheckbox.x + 128, positionXStepper.y + 10 + 32 + 28, translate("isGF"), false); - add(isGFCheckbox); - - for (checkbox in [isPlayerCheckbox, isGFCheckbox, antialiasingCheckbox, flipXCheckbox]) - {checkbox.y += 4; checkbox.x += 6;} - - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight- 20, TU.translate("editor.saveClose"), function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveCharacterInfo(); close(); }, 125); - saveButton.x -= saveButton.bWidth; - saveButton.y -= saveButton.bHeight; + add(saveButton); - closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() { - if (onSave != null) onSave(null); + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); - closeButton.x -= closeButton.bWidth; - closeButton.color = 0xFFFF0000; add(closeButton); - add(saveButton); + closeButton.color = 0xFFFF0000; } - function updateIcon(icon:String) { - if (iconSprite == null) add(iconSprite = new FlxSprite()); + public function saveCharacterInfo() { + UIUtil.confirmUISelections(this); - if (iconSprite.animation.exists(icon)) return; - @:privateAccess iconSprite.animation.clearAnimations(); - - var path:String = Paths.image('icons/$icon'); - if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON); - - iconSprite.loadGraphic(path, true, 150, 150); - iconSprite.animation.add(icon, [0], 0, false); - iconSprite.antialiasing = true; - iconSprite.animation.play(icon); - - iconSprite.scale.set(0.5, 0.5); - iconSprite.updateHitbox(); - iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); - } - - function saveCharacterInfo() { - for (stepper in [positionXStepper, positionYStepper, cameraXStepper, cameraYStepper, singTimeStepper, scaleStepper]) - @:privateAccess stepper.__onChange(stepper.label.text); - - var xml = Xml.createElement("character"); - xml.set("isPlayer", isPlayerCheckbox.checked ? "true" : "false"); - xml.set("x", Std.string(positionXStepper.value)); - xml.set("y", Std.string(positionYStepper.value)); - xml.set("gameOverChar", gameOverCharTextBox.label.text); - xml.set("camx", Std.string(cameraXStepper.value)); - xml.set("camy", Std.string(cameraYStepper.value)); - xml.set("holdTime", Std.string(singTimeStepper.value)); - xml.set("flipX", Std.string(flipXCheckbox.checked)); - xml.set("icon", iconTextBox.label.text); - xml.set("scale", Std.string(scaleStepper.value)); - xml.set("antialiasing", antialiasingCheckbox.checked ? "true" : "false"); - xml.set("sprite", spriteTextBox.label.text); - if (iconColorWheel.colorChanged) - xml.set("color", iconColorWheel.curColor.toWebString()); - for (val in customPropertiesButtonList.buttons.members) - xml.set(val.propertyText.label.text, val.valueText.label.text); - - for (anim in character.animDatas) - { - var animXml:Xml = Xml.createElement('anim'); - animXml.set("name", anim.name); - animXml.set("anim", anim.anim); - animXml.set("loop", Std.string(anim.loop)); - animXml.set("fps", Std.string(anim.fps)); - var offset:FlxPoint = character.getAnimOffset(anim.name); - animXml.set("x", Std.string(offset.x)); - animXml.set("y", Std.string(offset.y)); - offset.put(); - - if (anim.indices.length > 0) - animXml.set("indices", CoolUtil.formatNumberRange(anim.indices)); - xml.addChild(animXml); - } - - if (onSave != null) onSave(xml); + if (onSave != null) onSave({ + icon: iconColorPicker.iconTextBox.label.text, + iconColor: iconColorWheel.curColor, + holdTime: useDurationCheckbox.checked ? durationStepper.value : -1, + customProperties: [ + for (val in customPropertiesButtonList.buttons.members) + val.propertyText.label.text => val.valueText.label.text + ] + }); } } \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterPropertiesWindow.hx b/source/funkin/editors/character/CharacterPropertiesWindow.hx index bb1fe6ae..e5bd08ea 100644 --- a/source/funkin/editors/character/CharacterPropertiesWindow.hx +++ b/source/funkin/editors/character/CharacterPropertiesWindow.hx @@ -1,31 +1,282 @@ package funkin.editors.character; -import flixel.util.FlxColor; +import funkin.backend.FlxAnimate; +import haxe.io.Path; +import funkin.editors.character.CharacterInfoScreen.CharacterExtraInfo; +import funkin.game.Character; -class CharacterPropertiesWindow extends UIWindow { - public var newButton:UIButton; - public var editButton:UIButton; +class CharacterPropertiesWindow extends UISliceSprite { + public var character:Character; + public var animsWindow:CharacterAnimsWindow; - public var characterInfo:UIText; + public var positionXStepper:UINumericStepper; + public var positionYStepper:UINumericStepper; + public var positionXYComma:UIText; + public var scaleStepper:UINumericStepper; + public var editCharacterButton:UIButton; + public var editSpriteButton:UIButton; + public var flipXCheckbox:UICheckbox; + + public var cameraXStepper:UINumericStepper; + public var cameraYStepper:UINumericStepper; + public var cameraXYComma:UIText; + public var antialiasingCheckbox:UICheckbox; + public var testAsDropDown:UIDropDown; + public var designedAsDropDown:UIDropDown; + + public var labels:Map<UISprite, UIText> = []; inline function translate(id:String, ?args:Array<Dynamic>) return TU.translate("characterEditor.characterPropertiesWindow." + id, args); - public function new() { - super(800-23,23 + 23, 450 + 23, 140, translate("name")); + public function new(x:Float, y:Float, character:Character) @:privateAccess { + super(x, y, 424+16, 204+20, "editors/ui/inputbox"); - newButton = new UIButton( x +(450 + 23)- 25 - 3 - 200, y + 12 + 31, translate("newCharacter"), null, 200); - members.push(newButton); - newButton.color = FlxColor.GREEN; + function addLabelOn(ui:UISprite, text:String) { + var uiText:UIText = new UIText(ui.x, ui.y-24, 0, text); + members.push(uiText); labels.set(ui, uiText); + } - editButton = new UIButton(newButton.x, newButton.y + 12 + 30 + 4, translate("editCharacter"), function () { - CharacterEditor.instance.editInfoWithUI(); - }, 200); - members.push(editButton); + positionXStepper = new UINumericStepper(x+16, y+36, character.globalOffset.x, 0.001, 2, null, null, 104); + positionXStepper.onChange = (text:String) -> { + @:privateAccess positionXStepper.__onChange(text); + this.changePosition(positionXStepper.value, null); + }; + members.push(positionXStepper); + addLabelOn(positionXStepper, "Position (X,Y)"); - characterInfo = new UIText(x + 450 + 20 - 42 - 400, y + 36 + 10, 400, ""); - characterInfo.alignment = LEFT; - members.push(characterInfo); + members.push(positionXYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 22)); + + positionYStepper = new UINumericStepper(positionXStepper.x+104-32+26, positionXStepper.y, character.globalOffset.y, 0.001, 2, null, null, 104); + positionYStepper.onChange = (text:String) -> { + @:privateAccess positionYStepper.__onChange(text); + this.changePosition(null, positionYStepper.value); + }; + members.push(positionYStepper); + + scaleStepper = new UINumericStepper(positionYStepper.x+104-32+26, positionYStepper.y, character.scale.x, 0.001, 2, 0, null, 90); + scaleStepper.onChange = (text:String) -> { + @:privateAccess scaleStepper.__onChange(text); + this.changeScale(scaleStepper.value); + }; + members.push(scaleStepper); + addLabelOn(scaleStepper, "Scale"); + + editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y-20, "Edit Info", editCharacterInfoUI, 120, 24); + editCharacterButton.field.size -= 2; + members.push(editCharacterButton); + + editSpriteButton = new UIButton(editCharacterButton.x, editCharacterButton.y+24+6, "Edit Sprite", editCharacterSpriteUI, 120, 24); + editSpriteButton.field.size -= 2; + members.push(editSpriteButton); + + flipXCheckbox = new UICheckbox(scaleStepper.x+22, scaleStepper.y+32+14, "Flipped?", character.isPlayer ? !character.__baseFlipped : character.__baseFlipped); + flipXCheckbox.onChecked = (checked:Bool) -> {this.changeFlipX(checked);}; + members.push(flipXCheckbox); + + cameraXStepper = new UINumericStepper(positionXStepper.x, positionXStepper.y+32+32+4, character.cameraOffset.x, 0.001, 2, null, null, 104); + cameraXStepper.onChange = (text:String) -> { + @:privateAccess cameraXStepper.__onChange(text); + this.changeCamPosition(cameraXStepper.value, null); + }; + members.push(cameraXStepper); + addLabelOn(cameraXStepper, "Camera Position (X,Y)"); + + members.push(cameraXYComma = new UIText(cameraXStepper.x + 104-32+0, cameraXStepper.y+9, 0, ",", 22)); + + cameraYStepper = new UINumericStepper(cameraXStepper.x+104-32+26, cameraXStepper.y, character.cameraOffset.y, 0.001, 2, null, null, 104); + cameraYStepper.onChange = (text:String) -> { + @:privateAccess cameraYStepper.__onChange(text); + this.changeCamPosition(null, cameraYStepper.value); + }; + members.push(cameraYStepper); + + antialiasingCheckbox = new UICheckbox(scaleStepper.x+22, flipXCheckbox.y+32, "Antialiased?", character.antialiasing); + antialiasingCheckbox.onChecked = (checked:Bool) -> {this.changeAntialiasing(checked);}; + members.push(antialiasingCheckbox); + + testAsDropDown = new UIDropDown(cameraXStepper.x, cameraXStepper.y+32+32+4, 193, 32, ["BOYFRIEND", "DAD"], character.playerOffsets ? 0 : 1); + testAsDropDown.onChange = (index:Int) -> { + CharacterEditor.instance.changeStagePosition(testAsDropDown.options[index]); + }; + members.push(testAsDropDown); + addLabelOn(testAsDropDown, "Test Character As..."); + testAsDropDown.bWidth = 193; //REFUSES TO FUCKING SET TO 170 PIECE OF SHIT!! + + designedAsDropDown = new UIDropDown(testAsDropDown.x+193+22, testAsDropDown.y, 193, 32, ["BOYFRIEND", "DAD"], character.playerOffsets ? 0 : 1); + designedAsDropDown.onChange = (index:Int) -> { + CharacterEditor.instance.changeCharacterDesginedAs(designedAsDropDown.options[index] == "BOYFRIEND"); + }; + members.push(designedAsDropDown); + addLabelOn(designedAsDropDown, "Char Desgined As..."); + designedAsDropDown.bWidth = 193; + + alpha = 0.7; + + this.character = character; + } + + public function changePosition(newPosX:Null<Float>, newPosY:Null<Float>, addToUndo:Bool = true) { + if (newPosX != null && newPosY != null && newPosX == character.globalOffset.x && newPosY == character.globalOffset.y) + return; + else if (addToUndo) { + if (newPosX != null && newPosX == character.globalOffset.x) return; + if (newPosY != null && newPosY == character.globalOffset.y) return; + } + var oldPosition:FlxPoint = character.globalOffset.clone(); + + if (newPosX != null) character.globalOffset.x = newPosX; + if (newPosY != null) character.globalOffset.y = newPosY; + + CharacterEditor.instance.playAnimation(character.getAnimName()); + + positionXStepper.label.text = Std.string(character.globalOffset.x); + positionYStepper.label.text = Std.string(character.globalOffset.y); + + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditPosition(oldPosition, character.globalOffset.clone())); + else oldPosition.put(); + } + + public function changeScale(newScale:Float, addToUndo:Bool = true) { + if (character.scale.x == newScale) return; + var oldScale:Float = character.scale.x; + + character.scale.set(newScale, newScale); + character.updateHitbox(); + + CharacterEditor.instance.playAnimation(character.getAnimName()); + + scaleStepper.label.text = Std.string(newScale); + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditScale(oldScale, newScale)); + } + + public function editCharacterInfoUI() { + CharacterEditor.instance.openSubState(new CharacterInfoScreen(character, (info:CharacterExtraInfo) -> {editCharacterInfo(info);})); + } + + public function editCharacterInfo(info:CharacterExtraInfo, addToUndo:Bool = true) { + var oldInfo:CharacterExtraInfo = { + icon: character.icon, + iconColor: character.iconColor, + holdTime: character.holdTime, + customProperties: character.extra.copy() + }; + + character.icon = info.icon; + character.iconColor = info.iconColor; + character.holdTime = info.holdTime; + character.extra = info.customProperties.copy(); + + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditInfo(oldInfo, info)); + } + + public function editCharacterSpriteUI() { + CharacterEditor.instance.openSubState(new CharacterSpriteScreen('characters/${character.sprite}', (sprite:String, isAtlas:Bool) -> { + changeSprite(sprite); + })); + } + + public function changeSprite(sprite:String) @:privateAccess { + var path:String = Paths.image('characters/$sprite'); + var noExt:String = Path.withoutExtension(path); + + character.sprite = sprite; + + animsWindow.displayWindowSprite.animation.reset(); + animsWindow.displayAnimsFramesList.clear(); + if (animsWindow.displayWindowGraphic != null) + animsWindow.displayWindowGraphic.destroy(); + + if (Assets.exists('$noExt/Animation.json')) { + if (character.animateAtlas == null) { + character.animation.reset(); + character.animateAtlas = new FlxAnimate(character.x, character.y); + } else + character.animateAtlas.anim.stop(); + + character.atlasPath = noExt; + character.animateAtlas.loadAtlas(noExt); + } else { + if (character.animateAtlas != null) { + character.animateAtlas.destroy(); + character.animateAtlas = null; + character.atlasPlayingAnim = character.atlasPath = null; + } + character.frames = Paths.getFrames(path, true); + + animsWindow.displayWindowSprite.loadGraphicFromSprite(character); + if (Assets.exists(Paths.image('characters/${character.sprite}'))) + animsWindow.displayWindowGraphic = FlxG.bitmap.add(Assets.getBitmapData(Paths.image('characters/${character.sprite}'), true, false)); + } + + character.animDatas.clear(); + for (anim in animsWindow.buttons) anim.checkValid(); // Re-add all animations + + CharacterEditor.instance.playAnimation(animsWindow.findValid().getDefault(animsWindow.buttons.members[0].anim)); + animsWindow.setAnimAutoComplete(CoolUtil.getAnimsListFromSprite(character)); + } + + public function changeFlipX(newFlipX:Bool, addToUndo:Bool = true) @:privateAccess { + character.flipX = character.isPlayer ? !newFlipX : newFlipX; + character.__baseFlipped = character.flipX; + + CharacterEditor.instance.playAnimation(character.getAnimName()); + + flipXCheckbox.checked = newFlipX; + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditFlipped(newFlipX)); + } + + public function changeCamPosition(newPosX:Null<Float>, newPosY:Null<Float>, addToUndo:Bool = true) { + if (newPosX != null && newPosY != null && newPosX == character.cameraOffset.x && newPosY == character.cameraOffset.y) + return; + else if (addToUndo) { + if (newPosX != null && newPosX == character.cameraOffset.x) return; + if (newPosY != null && newPosY == character.cameraOffset.y) return; + } + var oldCamPosition:FlxPoint = character.cameraOffset.clone(); + + if (newPosX != null) character.cameraOffset.x = newPosX; + if (newPosY != null) character.cameraOffset.y = newPosY; + + cameraXStepper.label.text = Std.string(character.cameraOffset.x); + cameraYStepper.label.text = Std.string(character.cameraOffset.y); + + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditCamPosition(oldCamPosition, character.cameraOffset.clone())); + else oldCamPosition.put(); + } + + public function changeAntialiasing(newAntialiasing:Bool, addToUndo:Bool = true) { + if (character.antialiasing == newAntialiasing) return; + character.antialiasing = newAntialiasing; + animsWindow.displayWindowSprite.antialiasing = newAntialiasing; + + antialiasingCheckbox.checked = newAntialiasing; + if (addToUndo) CharacterEditor.undos.addToUndo(CCharEditAntialiasing(newAntialiasing)); + } + + public function updateButtonsPos() { + positionXStepper.follow(this, 16, 36); + positionYStepper.follow(this, 16+104-32+26, 36); + positionXYComma.follow(this, 16+104-32+0, 36 + 9); + scaleStepper.follow(this, (16+104-32+26)+104-32+26, 36); + editCharacterButton.follow(this, ((16+104-32+26)+104-32+26)+90-32+26, 36-20); + editSpriteButton.follow(this, ((16+104-32+26)+104-32+26)+90-32+26, (36-20)+24+6); + flipXCheckbox.follow(this, (16+104-32+26)+104-32+26+22, 36+32+14); + + cameraXStepper.follow(this, 16, 36+32+32+4); + cameraYStepper.follow(this, (16)+104-32+26, 36+32+32+4); + cameraXYComma.follow(this, 16 + 104-32+0, (36+32+32+4)+9); + antialiasingCheckbox.follow(this, ((16+104-32+26)+104-32+26)+22, 36+32+14+32); + testAsDropDown.follow(this, 16, (36+32+32+4)+32+32+4); + designedAsDropDown.follow(this, (16)+193+22, (36+32+32+4)+32+32+4); + + for (ui => text in labels) + text.follow(ui, 0, -24); + } + + public override function draw() { + updateButtonsPos(); + super.draw(); } } \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index c3330496..9f009ccb 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -1,24 +1,29 @@ package funkin.editors.character; +import haxe.xml.Printer; +import funkin.editors.ui.UIImageExplorer.ImageSaveData; import funkin.game.Character; import funkin.options.OptionsScreen; import funkin.options.type.IconOption; import funkin.options.type.NewOption; import funkin.options.type.TextOption; import funkin.options.type.OptionType; +import funkin.backend.assets.ModsFolder; class CharacterSelection extends EditorTreeMenu { inline function translate(id:String, ?args:Array<Dynamic>) return TU.translate("characterSelection." + id, args); + public var modsList:Array<String> = []; + public override function create() { bgType = "charter"; super.create(); var isMods:Bool = true; - var modsList:Array<String> = Character.getList(true, true); + modsList = Character.getList(true, true); if(modsList.length == 0) { modsList = Character.getList(false, true); @@ -46,11 +51,8 @@ class CharacterSelection extends EditorTreeMenu } } - var newChar = translate("newCharacter"); - list.insert(0, new NewOption(newChar, newChar, function() { - openSubState(new UIWarningSubstate(translate("warnings.notImplemented-title"), translate("warnings.notImplemented-body"), [ - {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}} - ])); + list.insert(0, new NewOption(translate("newCharacter"), translate("newCharacterDesc"), function() { + openSubState(new CharacterCreationScreen(createCharacter)); })); return list; @@ -68,4 +70,29 @@ class CharacterSelection extends EditorTreeMenu main.changeSelection(1); } + + public function createCharacter(name:String, imageSaveData:ImageSaveData, xml:Xml) { + var characterAlreadyExists:Bool = modsList.contains(name); + if (characterAlreadyExists) { + openSubState(new UIWarningSubstate("Creating Character: Error!", "The character you are trying to create already exists, if you would like to override it delete the character first!", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + return; + } + + // Save Data file + var characterPath:String = '${Paths.getAssetsRoot()}/data/characters/${name}.xml'; + CoolUtil.safeSaveFile(characterPath, "<!DOCTYPE codename-engine-character>\n" + Printer.print(xml, true)); + + // Save Image files + UIImageExplorer.saveFilesGlobal(imageSaveData, '${Paths.getAssetsRoot()}/images/characters'); + + // Add to Menu >:D + var option:IconOption = new IconOption(name, "Press ACCEPT to edit this character.", Character.getIconFromCharName(name), + function() { + FlxG.switchState(new CharacterEditor(name)); + } + ); + optionsTree.members[optionsTree.members.length-1].insert(1, option); + } } \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterSpriteScreen.hx b/source/funkin/editors/character/CharacterSpriteScreen.hx new file mode 100644 index 00000000..a3497127 --- /dev/null +++ b/source/funkin/editors/character/CharacterSpriteScreen.hx @@ -0,0 +1,94 @@ +package funkin.editors.character; + +import haxe.io.Bytes; +import flixel.util.typeLimit.OneOfTwo; +import flixel.text.FlxText.FlxTextFormat; +import funkin.editors.ui.UIImageExplorer.ImageSaveData; +import flixel.text.FlxText.FlxTextFormatMarkerPair; + +class CharacterSpriteScreen extends UISubstateWindow { + private var imagePath:String = null; + private var onSave:(String, Bool) -> Void = null; + + public var ogImageSaveData:ImageSaveData; + public var imageExplorer:UIImageExplorer; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public function new(imagePath:String, ?onSave:(String, Bool)->Void) { + super(); + this.imagePath = imagePath; + if (onSave != null) this.onSave = onSave; + } + + public override function create() { + winTitle = 'Edit character sprite'; + winWidth = 360; winHeight = 183; + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + super.create(); + + imageExplorer = new UIImageExplorer(20, windowSpr.y + 30 + 16 + 20, imagePath, 320, 58, (_, _) -> {onLoadImage();}, "images/characters"); + add(imageExplorer); + addLabelOn(imageExplorer, "Character Image File").applyMarkup( + "Character Image File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + ogImageSaveData = imageExplorer.getSaveData(); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { + addToUndo(); // should be async?? -lunar + imageExplorer.saveFiles('${Paths.getAssetsRoot()}/images/characters', () -> { + onSave('${imageExplorer.saveData.directory.length > 0 ? '${imageExplorer.saveData.directory}/' : ""}' + imageExplorer.imageName, imageExplorer.isAtlas); + close(); + }); + }, 125); + saveButton.x -= saveButton.bWidth; + saveButton.y -= saveButton.bHeight; + saveButton.selectable = false; + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Cancel", function() { + close(); + }, 125); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + //closeButton.y -= closeButton.bHeight; + add(closeButton); + add(saveButton); + + refreshWindowSize(); + } + + public function onLoadImage() { + refreshWindowSize(); + + if (imageExplorer == null || imageExplorer.imageFiles == null) return; + var filesSame = CoolUtil.deepEqual(ogImageSaveData.imageFiles, imageExplorer.imageFiles); + saveButton.selectable = !filesSame && !CoolUtil.isMapEmpty(imageExplorer.imageFiles) && (imageExplorer.animationList.length > 0); + } + + public function refreshWindowSize() { + if (imageExplorer == null) return; + windowSpr.bWidth = 20 + imageExplorer.bWidth + 20; + windowSpr.bHeight = 30 + 16 + 20 + imageExplorer.bHeight + 14 + saveButton.bHeight + 14; + + saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - saveButton.bWidth; + closeButton.x = saveButton.x - 20 - closeButton.bWidth; + closeButton.y = saveButton.y = imageExplorer.y + imageExplorer.bHeight + 14; + } + + public static var idCounter:Int = -1; + public inline function addToUndo() { + idCounter = FlxMath.wrap(idCounter + FlxG.random.int(1, 57349), 0, 9999); + CharacterEditor.undos.addToUndo(CCharEditSprite(idCounter)); + + CoolUtil.safeSaveFile('./.temp/__undo__${Type.getClassName(Type.getClass(FlxG.state))}__${idCounter}.cneisd', UIImageExplorer.serializeSaveDataGlobal(ogImageSaveData)); + CoolUtil.safeSaveFile('./.temp/__redo__${Type.getClassName(Type.getClass(FlxG.state))}__${idCounter}.cneisd', UIImageExplorer.serializeSaveDataGlobal(imageExplorer.getSaveData())); + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/ChartCreationScreen.hx b/source/funkin/editors/charter/ChartCreationScreen.hx index 61b937da..c212481e 100644 --- a/source/funkin/editors/charter/ChartCreationScreen.hx +++ b/source/funkin/editors/charter/ChartCreationScreen.hx @@ -67,7 +67,7 @@ class ChartCreationScreen extends UISubstateWindow { add(scrollSpeedTextBox); addLabelOn(scrollSpeedTextBox, TU.translate("chartCreation.scrollSpeed")); - strumLineList = new UIButtonList<StrumLineButton>(difficultyNameTextBox.x, difficultyNameTextBox.y+difficultyNameTextBox.bHeight+36, 620, (552-179)-16, "", FlxPoint.get(620, 246), null, 6); + strumLineList = new UIButtonList<StrumLineButton>(difficultyNameTextBox.x, difficultyNameTextBox.y+difficultyNameTextBox.bHeight+36, 620, (552-179)-16, null, FlxPoint.get(620, 246), null, 6); strumLineList.frames = Paths.getFrames('editors/ui/inputbox'); strumLineList.cameraSpacing = 0; @@ -145,8 +145,7 @@ class ChartCreationScreen extends UISubstateWindow { var strumLines:Array<ChartStrumLine> = []; for (strline in strumLineList.buttons.members) { - for (stepper in [strline.hudXStepper, strline.hudYStepper, strline.hudScaleStepper]) - @:privateAccess stepper.__onChange(stepper.label.text); + UIUtil.confirmUISelections(strline); strumLines.push({ characters: [for (char in strline.charactersList.buttons.members) char.textBox.label.text], @@ -208,7 +207,7 @@ class StrumLineButton extends UIButton { return uiText; } - charactersList = new UIButtonList<CompactCharacterButton>(16, 8+26, 210, 160, "", FlxPoint.get(200, 40), null, 5); + charactersList = new UIButtonList<CompactCharacterButton>(16, 8+26, 210, 160, null, FlxPoint.get(200, 40), null, 5); charactersList.frames = Paths.getFrames('editors/ui/inputbox'); charactersList.cameraSpacing = 0; @@ -271,7 +270,7 @@ class StrumLineButton extends UIButton { } members.push(usesChartScrollSpeed); - deleteButton = new UIButton(16, 246-32-11, "", function () { + deleteButton = new UIButton(16, 246-32-11, null, function () { parent.remove(this); }, 620-32); deleteButton.color = 0xFFFF0000; @@ -316,11 +315,12 @@ class CompactCharacterButton extends UIButton { public var deleteIcon:FlxSprite; public function new(char:String, charsList:Array<String>, parent:UIButtonList<CompactCharacterButton>) { - super(0, 0, "", null, 200, 40); + super(0, 0, null, null, 200, 40); autoAlpha = false; charIcon = new HealthIcon(funkin.game.Character.getIconFromCharName(char)); - charIcon.scale.set(0.2, 0.2); + var size = Std.int(150 * 0.2); + charIcon.setUnstretchedGraphicSize(size, size, true); charIcon.updateHitbox(); charIcon.setPosition(10, bHeight/2 - charIcon.height / 2); charIcon.scrollFactor.set(1,1); @@ -332,14 +332,13 @@ class CompactCharacterButton extends UIButton { textBox.antialiasing = true; textBox.onChange = function(char:String) { char = funkin.game.Character.getIconFromCharName(char); - var image = Paths.image("icons/" + char); - if(!Assets.exists(image)) - image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON); - charIcon.loadGraphic(image, true, 150, 150); + charIcon.setIcon(char); + charIcon.setUnstretchedGraphicSize(size, size, true); charIcon.updateHitbox(); + charIcon.setPosition(10, bHeight/2 - charIcon.height / 2); } - deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), "", function () { + deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), null, function () { parent.remove(this); }, 32); deleteButton.color = 0xFFFF0000; diff --git a/source/funkin/editors/charter/ChartDataScreen.hx b/source/funkin/editors/charter/ChartDataScreen.hx index 2d9521c8..57334cde 100644 --- a/source/funkin/editors/charter/ChartDataScreen.hx +++ b/source/funkin/editors/charter/ChartDataScreen.hx @@ -4,6 +4,7 @@ import flixel.math.FlxPoint; import funkin.backend.chart.ChartData.ChartMetaData; import funkin.backend.chart.ChartData; import funkin.game.Stage; +import funkin.game.HudCamera; using StringTools; @@ -15,6 +16,9 @@ class ChartDataScreen extends UISubstateWindow { public var scrollSpeedStepper:UINumericStepper; public var stageTextBox:UIAutoCompleteTextBox; + public var strumLineCam:HudCamera; + public var previewStrumLine:CharterPreviewStrumLine; + public function new(data:ChartData) { super(); this.data = data; @@ -60,11 +64,19 @@ class ChartDataScreen extends UISubstateWindow { add(closeButton); closeButton.color = 0xFFFF0000; closeButton.x -= closeButton.bWidth; + + strumLineCam = new HudCamera(); + strumLineCam.downscroll = Options.downscroll; + strumLineCam.bgColor = 0; + strumLineCam.alpha = 0; + FlxG.cameras.add(strumLineCam, false); + previewStrumLine = new CharterPreviewStrumLine(96, 50, 1, 1, 4, 0); + previewStrumLine.camera = strumLineCam; + add(previewStrumLine); } - public function saveInfo() - { - @:privateAccess scrollSpeedStepper.__onChange(scrollSpeedStepper.label.text); + public function saveInfo() { + UIUtil.confirmUISelections(this); var oldData:{stage:String, speed:Float} = {stage: PlayState.SONG.stage, speed: PlayState.SONG.scrollSpeed}; @@ -73,4 +85,19 @@ class ChartDataScreen extends UISubstateWindow { Charter.undos.addToUndo(CEditChartData(oldData, {stage: stageTextBox.label.text, speed: scrollSpeedStepper.value})); } + + override public function update(elapsed:Float) { + var scrollSpeed:Float = 0.0; + if (scrollSpeedStepper.hovered || scrollSpeedStepper.focused) + scrollSpeed = scrollSpeedStepper.value; + + strumLineCam.alpha = CoolUtil.fpsLerp(strumLineCam.alpha, scrollSpeed == 0.0 ? 0.0 : 1.0, 0.2); + previewStrumLine.updatePos(96, 50, 1, 1, 4, scrollSpeed); + + super.update(elapsed); + } + override public function destroy() { + super.destroy(); + FlxG.cameras.remove(strumLineCam); + } } \ No newline at end of file diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 0a41745a..ccd88ae9 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1,21 +1,29 @@ package funkin.editors.charter; // ! FUCK YOU CHUF (your biggest fan -lunar) <3 +// import flixel.FlxLayer; import flixel.input.keyboard.FlxKey; import flixel.math.FlxPoint; import flixel.sound.FlxSound; +import flixel.util.FlxSort; import funkin.backend.chart.*; import funkin.backend.chart.ChartData; import funkin.backend.system.Conductor; import funkin.backend.system.framerate.Framerate; -import funkin.editors.charter.CharterBackdropGroup.CharterBackdropDummy; import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; +import funkin.editors.charter.CharterBackdropGroup.CharterGridSeperatorBase; +import funkin.editors.charter.CharterEvent; import funkin.editors.charter.CharterStrumline; +import funkin.editors.extra.CameraHoverDummy; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; import funkin.editors.ui.UIContextMenu.UIContextMenuOptionSpr; import funkin.editors.ui.UIState; import funkin.editors.ui.UITopMenu.UITopMenuButton; import haxe.Json; +#if sys +import sys.FileSystem; +#end +import flixel.util.FlxColor; class Charter extends UIState { public static var __song:String; @@ -38,19 +46,24 @@ class Charter extends UIState { @:noCompletion private var playbackIndex:Int = 7; @:noCompletion private var snapIndex:Int = 6; @:noCompletion private var noteIndex:Int = 5; + @:noCompletion private var songIndex:Int = 4; public var scrollBar:UIScrollBar; public var songPosInfo:UIText; + public var shouldScroll:Bool = true; + public var quantButtons:Array<CharterQuantButton> = []; public var playBackSlider:UISlider; public var topMenuSpr:UITopMenu; public var gridBackdrops:CharterBackdropGroup; - public var eventsBackdrop:EventBackdrop; - public var addEventSpr:CharterEventAdd; + public var leftEventsBackdrop:EventBackdrop; + public var localAddEventSpr:CharterEventAdd; + public var rightEventsBackdrop:EventBackdrop; + public var globalAddEventSpr:CharterEventAdd; - public var gridBackdropDummy:CharterBackdropDummy; + public var gridBackdropDummy:CameraHoverDummy; public var noteHoverer:CharterNoteHoverer; public var noteDeleteAnims:CharterDeleteAnim; @@ -72,7 +85,11 @@ class Charter extends UIState { public var strumLines:CharterStrumLineGroup = new CharterStrumLineGroup(); public var notesGroup:CharterNoteGroup = new CharterNoteGroup(); - public var eventsGroup:CharterEventGroup = new CharterEventGroup(); + + public var leftEventRowText:UIText; + public var rightEventRowText:UIText; + public var leftEventsGroup:CharterEventGroup = new CharterEventGroup(); + public var rightEventsGroup:CharterEventGroup = new CharterEventGroup(); public var charterCamera:FlxCamera; public var uiCamera:FlxCamera; @@ -108,19 +125,25 @@ class Charter extends UIState { WindowUtils.suffix = " (" + TU.translate("editor.chart.name") + ")"; SaveWarning.selectionClass = CharterSelection; - SaveWarning.saveFunc = () -> {_file_save(null);}; + SaveWarning.saveFunc = () -> saveEverything(); topMenu = [ { label: TU.translate("chart.topBar.file"), childs: [ - { + /*{ label: TU.translate("chart.file.new") }, - null, + null,*/ { label: TU.translate("chart.file.save"), keybind: [CONTROL, S], + onSelect: _file_save_all, + }, + null, + { + label: "Save chart", + keybind: [CONTROL, SHIFT, S], onSelect: _file_save, }, { @@ -130,34 +153,31 @@ class Charter extends UIState { }, null, { - label: TU.translate("chart.file.saveWithoutEvents"), - keybind: [CONTROL, ALT, TAB, S], - onSelect: _file_save_no_events, - }, - { - label: TU.translate("chart.file.saveWithoutEventsAs"), - keybind: [CONTROL, SHIFT, ALT, TAB, S], - onSelect: _file_saveas_no_events, - }, - { - label: TU.translate("chart.file.saveEventsSeparately"), - keybind: [CONTROL, TAB, S], + label: "Save global events", + keybind: [CONTROL, B, S], onSelect: _file_events_save, }, { - label: TU.translate("chart.file.saveEventsSeparatelyAs"), - keybind: [CONTROL, SHIFT, TAB, S], + label: "Save global events as...", onSelect: _file_events_saveas, }, + { + label: "Save chart without local events", + keybind: [CONTROL, ALT, S], + onSelect: _file_save_no_events, + }, + { + label: "Save chart without local events as...", + onSelect: _file_saveas_no_events, + }, null, { label: TU.translate("chart.file.saveMeta"), - keybind: [CONTROL, ALT, S], + keybind: [CONTROL, M, S], onSelect: _file_meta_save, }, { label: TU.translate("chart.file.saveMetaAs"), - keybind: [CONTROL, ALT ,SHIFT, S], onSelect: _file_meta_saveas, }, null, @@ -288,36 +308,37 @@ class Charter extends UIState { icon: Options.charterShowBeats ? 1 : 0 }, null, + { + label: 'Rainbow Waveforms', + onSelect: _view_switchWaveformRainbow, + icon: Options.charterRainbowWaveforms ? 1 : 0 + }, { label: TU.translate("chart.view.lowDetailWaveforms"), onSelect: _view_switchWaveformDetail, icon: Options.charterLowDetailWaveforms ? 1 : 0 + }, + null, + { + label: "Scroll left", + keybind: [SHIFT, LEFT], + onSelect: _view_scrollleft + }, + { + label: "Scroll right", + keybind: [SHIFT, RIGHT], + onSelect: _view_scrollright + }, + { + label: "Reset scroll", + keybind: [SHIFT, DOWN], + onSelect: _view_scrollreset } ] }, { label: TU.translate("chart.topBar.song"), - childs: [ - { - label: TU.translate("chart.song.goStart"), - keybind: [HOME], - onSelect: _song_start - }, - { - label: TU.translate("chart.song.goEnd"), - keybind: [END], - onSelect: _song_end - }, - null, - { - label: TU.translate("chart.song.muteInst"), - onSelect: _song_muteinst - }, - { - label: TU.translate("chart.song.muteVoices"), - onSelect: _song_mutevoices - } - ] + childs: buildSongUI() }, { label: TU.translate("chart.topBar.note") + " >", @@ -383,9 +404,9 @@ class Charter extends UIState { onSelect: _playback_metronome, icon: Options.charterMetronomeEnabled ? 1 : 0 }, - { + /*{ label: TU.translate("chart.playback.visualMetronome") - }, + },*/ ] } ]; @@ -410,12 +431,30 @@ class Charter extends UIState { gridBackdrops = new CharterBackdropGroup(strumLines); gridBackdrops.notesGroup = this.notesGroup; - eventsBackdrop = new EventBackdrop(); - eventsBackdrop.x = -eventsBackdrop.width; - eventsBackdrop.cameras = [charterCamera]; - eventsGroup.eventsBackdrop = eventsBackdrop; + leftEventRowText = new UIText(0, -40, 0, "Local Events\n(Only This Difficulty)", 12); + leftEventRowText.alignment = "center"; leftEventRowText.alpha = 0.75; - add(gridBackdropDummy = new CharterBackdropDummy(gridBackdrops)); + leftEventsBackdrop = new EventBackdrop(false); + leftEventsBackdrop.x = -leftEventsBackdrop.width; + + leftEventRowText.cameras = leftEventsBackdrop.cameras = leftEventsGroup.cameras = [charterCamera]; + leftEventsGroup.eventsBackdrop = leftEventsBackdrop; + leftEventsGroup.eventsRowText = leftEventRowText; + + rightEventRowText = new UIText(0, -40, 0, "Global Events\n(All Difficulties)", 12); + rightEventRowText.alignment = "center"; rightEventRowText.alpha = 0.75; + + rightEventsBackdrop = new EventBackdrop(true); + rightEventsBackdrop.x = 0; + + rightEventRowText.cameras = rightEventsBackdrop.cameras = rightEventsGroup.cameras = [charterCamera]; + rightEventsGroup.eventsBackdrop = rightEventsBackdrop; + rightEventsGroup.eventsRowText = rightEventRowText; + + // thank you neo for pointing out im stupid -lunar + // this is future lunar i completely forgot what neo pointed out but hes awesome go follow him on twitter + + add(gridBackdropDummy = new CameraHoverDummy(gridBackdrops, FlxPoint.weak(1, 0))); selectionBox = new UISliceSprite(0, 0, 2, 2, 'editors/ui/selection'); selectionBox.visible = false; selectionBox.scrollFactor.set(1, 1); @@ -441,7 +480,7 @@ class Charter extends UIState { }; uiGroup.add(scrollBar); - songPosInfo = new UIText(FlxG.width - 30 - 400, scrollBar.y + 10, 400, "00:00\nBeat: 0\nStep: 0\nMeasure: 0\nBPM: 0\nTime Signature: 4/4"); + songPosInfo = new UIText(FlxG.width - 30 - 400, scrollBar.y + 10, 400, "00:00 / 00:00\nBeat: 0\nStep: 0\nMeasure: 0\nBPM: 0\nTime Signature: 4/4"); songPosInfo.alignment = RIGHT; uiGroup.add(songPosInfo); @@ -485,16 +524,29 @@ class Charter extends UIState { strumlineInfoBG.cameras = [charterCamera]; strumLines.cameras = [charterCamera]; - addEventSpr = new CharterEventAdd(); - addEventSpr.x -= addEventSpr.bWidth; - addEventSpr.cameras = [charterCamera]; - addEventSpr.alpha = 0; + localAddEventSpr = new CharterEventAdd(false); + localAddEventSpr.x -= localAddEventSpr.bWidth; + localAddEventSpr.cameras = [charterCamera]; + localAddEventSpr.alpha = 0; + + globalAddEventSpr = new CharterEventAdd(true); + globalAddEventSpr.x = 0; + globalAddEventSpr.cameras = [charterCamera]; + globalAddEventSpr.alpha = 0; // adds grid and notes so that they're ALWAYS behind the UI add(gridBackdrops); - add(eventsBackdrop); - add(addEventSpr); - add(eventsGroup); + add(leftEventsBackdrop); + add(rightEventsBackdrop); + add(localAddEventSpr); + add(globalAddEventSpr); + add(leftEventsGroup); + add(rightEventsGroup); + add(leftEventRowText); + add(localAddEventSpr.sideText); + add(rightEventRowText); + add(globalAddEventSpr.sideText); + add(noteHoverer); add(noteDeleteAnims); add(notesGroup); @@ -511,12 +563,14 @@ class Charter extends UIState { loadSong(); - if(Framerate.isLoaded) { + if (Framerate.isLoaded) { Framerate.fpsCounter.alpha = 0.4; Framerate.memoryCounter.alpha = 0.4; Framerate.codenameBuildField.alpha = 0.4; } - updateDisplaySprites(); + + if (Options.editorsResizable) + UIState.setResolutionAware(); // ! IF YOU EVER WANNA VIEW IN THE FUTURE, JUST USE A FLXSPRITE :D -lunar /*var dataDisplay:FlxSprite = new FlxSprite().loadGraphic(waveformHandler.waveDatas.get("Voices.ogg")); @@ -580,32 +634,33 @@ class Charter extends UIState { notesGroup.autoSort = true; // create events - eventsGroup.autoSort = false; + rightEventsGroup.autoSort = leftEventsGroup.autoSort = false; var __last:CharterEvent = null; var __lastTime:Float = Math.NaN; - for(e in PlayState.SONG.events) { + for (e in PlayState.SONG.events) { if (e == null) continue; if (__last != null && __lastTime == e.time) { __last.events.push(e); } else { __last = new CharterEvent(Conductor.getStepForTime(e.time), [e]); __lastTime = e.time; - eventsGroup.add(__last); + (__last.global ? rightEventsGroup : leftEventsGroup).add(__last); } } - eventsGroup.sortEvents(); - eventsGroup.autoSort = true; - - for(e in eventsGroup.members) - e.refreshEventIcons(); + for (grp in [leftEventsGroup, rightEventsGroup]) { + grp.sortEvents(); + grp.autoSort = true; + for (e in grp.members) e.refreshEventIcons(); + } buildNoteTypesUI(); + updateBookmarks(); refreshBPMSensitive(); // Just for now until i add event stacking -lunar try {__relinkUndos();} - catch (e) {Logs.error('Failed to relink undos: ${Std.string(e)}');} + catch (e) {Logs.trace('Failed to relink undos: ${Std.string(e)}', ERROR);} __applyPlaytestInfo(); } @@ -617,32 +672,35 @@ class Charter extends UIState { scrollBar.length = __endStep = Conductor.getStepForTime(length); gridBackdrops.bottomLimitY = __endStep * 40; - eventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2; + leftEventsBackdrop.bottomSeparator.y = rightEventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2; + + CharterGridSeperatorBase.lastConductorSprY = Math.NEGATIVE_INFINITY; updateWaveforms(); } - inline function isSoundLoaded(sound:FlxSound) { - @:privateAccess - return sound != null && sound._sound != null && sound._sound.length > 0; - } - - public function updateWaveforms() { + public function getWavesToGenerate():Array<{name:String, sound:FlxSound}> { var wavesToGenerate:Array<{name:String, sound:FlxSound}> = []; - if(isSoundLoaded(FlxG.sound.music)) + if (FlxG.sound.music.loaded) wavesToGenerate.push({name: "Inst.ogg", sound: FlxG.sound.music}); - if (PlayState.SONG.meta.needsVoices != false && isSoundLoaded(vocals)) + if (PlayState.SONG.meta.needsVoices != false && vocals.loaded) wavesToGenerate.push({name: "Voices.ogg", sound: vocals}); for (strumLine in strumLines) - if (strumLine.vocals != null && strumLine.strumLine.vocalsSuffix != null && strumLine.strumLine.vocalsSuffix != "" && isSoundLoaded(strumLine.vocals)) + if (strumLine.vocals != null && strumLine.strumLine.vocalsSuffix != null && strumLine.strumLine.vocalsSuffix != "" && strumLine.vocals.loaded) wavesToGenerate.push({ name: 'Voices${strumLine.strumLine.vocalsSuffix}.ogg', sound: strumLine.vocals }); + return wavesToGenerate; + } + + public function updateWaveforms() { + var wavesToGenerate:Array<{name:String, sound:FlxSound}> = getWavesToGenerate(); + var oldWaveformList:Array<String> = waveformHandler.waveformList; var newWaveformList:Array<String> = [for (data in wavesToGenerate) data.name]; @@ -699,7 +757,7 @@ class Charter extends UIState { else selection = [s]; } - for (group in [notesGroup, eventsGroup]) { + for (group in [notesGroup, leftEventsGroup, rightEventsGroup]) { var group:FlxTypedGroup<Dynamic> = cast group; group.forEach(function(s) { s.selected = false; @@ -724,19 +782,18 @@ class Charter extends UIState { if (Options.charterAutoSavesSeparateFolder) __autoSaveLocation = __diff.toLowerCase() + DateTools.format(Date.now(), "%m-%d_%H-%M"); var filename = !Options.charterAutoSavesSeparateFolder ? '${__diff.toLowerCase()}.json' : '${__autoSaveLocation}.json'; - autoSaveNotif.startAutoSave(autoSaveTimer, - TU.translate("chart.popup.savedChartAt", [filename]) - ); + autoSaveNotif.startAutoSave(autoSaveTimer, TU.translate("chart.popup.savedChartAt", [filename])); } if (autoSaveTimer <= 0) { autoSaveTimer = Options.charterAutoSaveTime; if (!autoSaveNotif.cancelled) { buildChart(); var songPath:String = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}'; + if (Options.charterAutoSavesSeparateFolder) - Chart.save(songPath, PlayState.SONG, __autoSaveLocation, {saveMetaInChart: false, folder: "autosaves", prettyPrint: Options.editorPrettyPrint}); - else - Chart.save(songPath, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, prettyPrint: Options.editorPrettyPrint}); + Chart.save(songPath, PlayState.SONG, __autoSaveLocation, {saveMetaInChart: true, saveLocalEvents: true, saveGlobalEvents: true, folder: "autosaves", prettyPrint: Options.editorPrettyPrint}); + else // These two chart saves are particular, to avoid any kind of loss, stuff like meta, global and local events will be save all together - Nex + Chart.save(songPath, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: true, saveLocalEvents: true, saveGlobalEvents: true, prettyPrint: Options.editorPrettyPrint}); undos.save(); } autoSaveNotif.cancelled = false; @@ -750,7 +807,7 @@ class Charter extends UIState { /** * NOTE DRAG HANDLING */ - mousePos = FlxG.mouse.getWorldPosition(charterCamera); + FlxG.mouse.getWorldPosition(charterCamera, mousePos); if (!gridBackdropDummy.hoveredByChild && !FlxG.mouse.pressed) gridActionType = NONE; selectionBox.visible = false; @@ -766,14 +823,14 @@ class Charter extends UIState { if (FlxG.mouse.justReleased) isSelecting = false; } else { if (FlxG.keys.pressed.SHIFT) { - for (group in [notesGroup, eventsGroup]) { + for (group in [notesGroup, leftEventsGroup, rightEventsGroup]) { var group:FlxTypedGroup<Dynamic> = cast group; for(n in group) if (n.handleSelection(selectionBox) && selection.contains(n)) selection.remove(n); } } else if (FlxG.keys.pressed.CONTROL) { - for (group in [notesGroup, eventsGroup]) { + for (group in [notesGroup, leftEventsGroup, rightEventsGroup]) { var group:FlxTypedGroup<Dynamic> = cast group; for(n in group) if (n.handleSelection(selectionBox) && !selection.contains(n)) @@ -781,7 +838,7 @@ class Charter extends UIState { } } else { selection = []; - for (group in [notesGroup, eventsGroup]) { + for (group in [notesGroup, leftEventsGroup, rightEventsGroup]) { var group:FlxTypedGroup<Dynamic> = cast group; for(n in group) if (n.handleSelection(selectionBox)) @@ -802,15 +859,18 @@ class Charter extends UIState { if (selectionDragging) { gridBackdrops.draggingObj = null; selection.loop(function (n:CharterNote) { - n.snappedToStrumline = false; + n.snappedToGrid = false; n.setPosition(n.fullID * 40 + (mousePos.x - dragStartPos.x), n.step * 40 + (mousePos.y - dragStartPos.y)); - n.y = FlxMath.bound(n.y, 0, (__endStep*40) - n.height); - n.x = FlxMath.bound(n.x, 0, ((strumLines.members.length * 4)-1) * 40); + n.y = CoolUtil.bound(n.y, 0, (__endStep*40) - n.height); + n.x = CoolUtil.bound(n.x, 0, (strumLines.totalKeyCount-1) * 40); n.cursor = HAND; }, function (e:CharterEvent) { - e.y = e.step * 40 + (mousePos.y - dragStartPos.y) - 17; - e.y = FlxMath.bound(e.y, -17, (__endStep*40)-17); + e.snappedToGrid = false; + e.setPosition(e.eventsBackdrop.x + (e.global ? 0 : e.eventsBackdrop.width - e.bWidth) + (mousePos.x - dragStartPos.x), e.step * 40 + (mousePos.y - dragStartPos.y) - 17); + e.y = CoolUtil.bound(e.y, -17, (__endStep*40)-17); e.cursor = HAND; + + e.displayGlobal = e.x + (e.bWidth/2) > ((strumLines.totalKeyCount*40)/2); }); currentCursor = HAND; } else { @@ -834,7 +894,7 @@ class Charter extends UIState { if (s is CharterNote) { var note:CharterNote = cast s; if (note.fullID + changePoint.y < 0) boundedChange.y += Math.abs(note.fullID + changePoint.y); - if (note.fullID + changePoint.y > (strumLines.members.length*4)-1) boundedChange.y -= (note.fullID + changePoint.y) - ((strumLines.members.length*4)-1); + if (note.fullID + changePoint.y > strumLines.totalKeyCount-1) boundedChange.y -= (note.fullID + changePoint.y) - (strumLines.totalKeyCount-1); } s.handleDrag(boundedChange); @@ -842,18 +902,20 @@ class Charter extends UIState { changePoint.put(); } - if (s is CharterNote) { - var s:CharterNote = cast s; - s.snappedToStrumline = true; - } + s.snappedToGrid = true; if (s is UISprite) { var s:UISprite = cast s; - s.cursor = BUTTON; + s.cursor = CLICK; } } + checkSelectionForBPMUpdates(); if (!(verticalChange == 0 && horizontalChange == 0)) { - notesGroup.sortNotes(); eventsGroup.sortEvents(); - undos.addToUndo(CSelectionDrag(undoDrags)); + notesGroup.sortNotes(); + + undos.addToUndo(CChangeBundle([ + CSelectionDrag(undoDrags), + updateEventsGroups(selection) + ])); } gridActionType = NONE; @@ -875,7 +937,7 @@ class Charter extends UIState { } var id = Math.floor(mousePos.x / 40); - var mouseOnGrid = id >= 0 && id < 4 * gridBackdrops.strumlinesAmount && mousePos.y >= 0; + var mouseOnGrid = id >= 0 && id < strumLines.totalKeyCount && mousePos.y >= 0; if (FlxG.mouse.justReleased) { for (n in selection) n.selected = false; @@ -883,9 +945,10 @@ class Charter extends UIState { if (mouseOnGrid && mousePos.y > 0 && mousePos.y < (__endStep)*40) { var note = new CharterNote(); + var targetStrumline = strumLines.getStrumlineFromID(id); note.updatePos( - FlxMath.bound(FlxG.keys.pressed.SHIFT ? ((mousePos.y-20) / 40) : quantStep(mousePos.y/40), 0, __endStep-1), - id % 4, 0, noteType, strumLines.members[Std.int(id/4)] + CoolUtil.bound(FlxG.keys.pressed.SHIFT ? ((mousePos.y-20) / 40) : quantStep(mousePos.y/40), 0, __endStep-1), + (id-targetStrumline.startingID) % targetStrumline.keyCount, 0, noteType, targetStrumline ); notesGroup.add(note); selection = [note]; @@ -914,7 +977,7 @@ class Charter extends UIState { case SUSTAIN_DRAG: selectionDragging = FlxG.mouse.pressed; if (selectionDragging) { - currentCursor = BUTTON; + currentCursor = CLICK; selection.loop(function (n:CharterNote) { var change:Float = Math.max((mousePos.y-(FlxG.keys.pressed.SHIFT ? dragStartPos.y : quantStep(dragStartPos.y))) / 40, -n.susLength); n.tempSusLength = change; @@ -959,24 +1022,31 @@ class Charter extends UIState { var mousePos = FlxG.mouse.getScreenPosition(uiCamera); closeCurrentContextMenu(); openContextMenu(topMenu[1].childs, null, mousePos.x, mousePos.y); - mousePos.put(); } gridActionType = NONE; deletedNotes = []; } } - addEventSpr.selectable = !selectionBox.visible; + localAddEventSpr.selectable = !selectionBox.visible; + globalAddEventSpr.selectable = !selectionBox.visible; var inBoundsY:Bool = (mousePos.y > 0 && mousePos.y < (__endStep)*40); // Event Spr - if (mousePos.x < 0 && mousePos.x > -addEventSpr.bWidth && gridActionType == NONE && inBoundsY) { - addEventSpr.incorporeal = false; - addEventSpr.sprAlpha = lerp(addEventSpr.sprAlpha, 0.75, 0.25); - var event = getHoveredEvent(mousePos.y); - if (event != null) addEventSpr.updateEdit(event); - else addEventSpr.updatePos(FlxG.keys.pressed.SHIFT ? ((mousePos.y) / 40) : quantStepRounded(mousePos.y/40)); - } else addEventSpr.sprAlpha = lerp(addEventSpr.sprAlpha, 0, 0.25); + for (addEventSpr in [localAddEventSpr, globalAddEventSpr]) { + addEventSpr.incorporeal = true; + if ((!addEventSpr.global ? mousePos.x < 0 : mousePos.x > strumLines.totalKeyCount * 40) && gridActionType == NONE && inBoundsY) { + var event = getHoveredEvent(mousePos.y, !addEventSpr.global ? leftEventsGroup : rightEventsGroup); + var hoveredWidth:Float = event != null ? 27 + 40 + event.bWidth : addEventSpr.bWidth; + if ((!addEventSpr.global ? mousePos.x > -hoveredWidth : mousePos.x < strumLines.totalKeyCount * 40 + hoveredWidth)) { + addEventSpr.incorporeal = false; + + if (event != null) addEventSpr.updateEdit(event); + else addEventSpr.updatePos(FlxG.keys.pressed.SHIFT ? ((mousePos.y) / 40) : quantStepRounded(mousePos.y/40)); + } + } + addEventSpr.sprAlpha = lerp(addEventSpr.sprAlpha, !addEventSpr.incorporeal ? 0.75 : 0, 0.25); + } noteHoverer.showHoverer = Charter.instance.gridBackdropDummy.hovered; } @@ -993,9 +1063,9 @@ class Charter extends UIState { public function ratioRound(val:Float, ratio:Float):Int return Math.floor(val) + ((Math.abs(val % 1) > ratio ? 1 : 0) * (val > 0 ? 1 : -1)); - public function getHoveredEvent(y:Float) { + public function getHoveredEvent(y:Float, group:CharterEventGroup) { var eventHovered:CharterEvent = null; - eventsGroup.forEach(function(e) { + group.forEach(function(e) { if (eventHovered != null) return; @@ -1016,7 +1086,7 @@ class Charter extends UIState { note.kill(); } else if (selected is CharterEvent) { var event:CharterEvent = cast selected; - eventsGroup.remove(event); + (event.global ? rightEventsGroup : leftEventsGroup).remove(event); event.kill(); } @@ -1036,17 +1106,13 @@ class Charter extends UIState { notesGroup.add(n); }, function (e:CharterEvent) { e.revive(); - eventsGroup.add(e); + (e.global ? rightEventsGroup : leftEventsGroup).add(e); e.refreshEventIcons(); }, false); notesGroup.sortNotes(); notesGroup.autoSort = true; - for (s in selection) - if (s is CharterEvent) { - Charter.instance.updateBPMEvents(); - break; - } + checkSelectionForBPMUpdates(); if (addToUndo) undos.addToUndo(CCreateSelection(selection)); @@ -1057,7 +1123,7 @@ class Charter extends UIState { if (selection.length <= 0) return []; notesGroup.autoSort = false; - for (group in [notesGroup, eventsGroup]) { + for (group in [notesGroup, leftEventsGroup, rightEventsGroup]) { var group:FlxTypedGroup<Dynamic> = cast group; var member:Int = 0; while(member < group.members.length) { @@ -1070,21 +1136,38 @@ class Charter extends UIState { notesGroup.sortNotes(); notesGroup.autoSort = true; - for (s in selection) - if (s is CharterEvent) { - Charter.instance.updateBPMEvents(); - break; - } + checkSelectionForBPMUpdates(); if (addToUndo) undos.addToUndo(CDeleteSelection(selection)); return []; } + public function updateEventsGroups(selection:Selection):CharterChange { + if (selection.length <= 0) return CEditEventGroups([]); + var eventsChanged:Array<CharterEvent> = []; + + selection.loop(function (n:CharterNote) {}, function (e:CharterEvent) { + if (e.displayGlobal != e.global) { + (e.global ? rightEventsGroup : leftEventsGroup).remove(e); + (e.displayGlobal ? rightEventsGroup : leftEventsGroup).add(e); + + e.global = e.displayGlobal; + eventsChanged.push(e); + } + }, true); + + leftEventsGroup.sortEvents(); rightEventsGroup.sortEvents(); + for (e in eventsChanged) e.update(0); // remove little stutter + + return CEditEventGroups(eventsChanged); + } + // STRUMLINE DELETION/CREATION public function createStrumline(strumLineID:Int, strL:ChartStrumLine, addToUndo:Bool = true, ?__createNotes:Bool = true) { var cStr = new CharterStrumline(strL); strumLines.insert(strumLineID, cStr); + strumLines.refreshStrumlineIDs(); strumLines.snapStrums(); if (__createNotes) { @@ -1098,6 +1181,8 @@ class Charter extends UIState { createSelection(toBeCreated, false); } + updateBookmarks(); + if (addToUndo) undos.addToUndo(CCreateStrumLine(strumLineID, strL)); } @@ -1118,8 +1203,11 @@ class Charter extends UIState { var strL = strumLines.members[strumLineID].strumLine; strumLines.members[strumLineID].destroy(); strumLines.members.remove(strumLines.members[strumLineID]); + strumLines.refreshStrumlineIDs(); strumLines.snapStrums(); + updateBookmarks(); + if (addToUndo) { var newStrL = Reflect.copy(strL); newStrL.notes = undoNotes; @@ -1157,6 +1245,7 @@ class Charter extends UIState { FlxG.state.openSubState(new CharterStrumlineScreen(strID, strL, (_) -> { strumLines.members[strID].strumLine = _; strumLines.members[strID].updateInfo(); + strumLines.refreshStrumlineIDs(); undos.addToUndo(CEditStrumLine(strID, oldData, _)); @@ -1179,17 +1268,37 @@ class Charter extends UIState { var __crochet:Float; var __firstFrame:Bool = true; + var __timer:Float = 0; public override function update(elapsed:Float) { + if (Options.charterRainbowWaveforms) { + __timer += elapsed/8; + for (shader in waveformHandler.waveShaders) + shader.data.time.value = [__timer]; + } + updateNoteLogic(elapsed); updateAutoSaving(elapsed); + for (bs in __bookmarkObjects) + { + var bars:Array<FlxSprite> = bs[0]; + var text:UIText = bs[1]; + if (bars == null || bars.length == 0) continue; + for (i => spr in bars) { + if (spr == null || strumLines.members[i] == null) continue; + spr.x = strumLines.members[i].x; + } + if (text != null) + text.x = strumLines.members[0].x + 4; + } + if (FlxG.sound.music.playing || __firstFrame) { gridBackdrops.conductorSprY = curStepFloat * 40; } else { gridBackdrops.conductorSprY = lerp(gridBackdrops.conductorSprY, curStepFloat * 40, __firstFrame ? 1 : 1/3); } charterCamera.scroll.set( - ((((40*4) * gridBackdrops.strumlinesAmount) - FlxG.width) / 2), + lerp(charterCamera.scroll.x, (((40*strumLines.totalKeyCount) - FlxG.width) / 2) + sideScroll, __firstFrame ? 1 : 1/3), gridBackdrops.conductorSprY - (FlxG.height * 0.5) ); @@ -1202,7 +1311,6 @@ class Charter extends UIState { var snapButton:UITopMenuButton = cast topMenuSpr.members[snapIndex]; var lastButtonX = playBackButton.x-10; - var buttonI:Int = 0; for (button in quantButtons) { button.visible = ((button.quant == quant) || (button.quant == quants[FlxMath.wrap(quants.indexOf(quant)-1, 0, quants.length-1)]) || @@ -1237,23 +1345,27 @@ class Charter extends UIState { if (true) { __crochet = ((60 / Conductor.bpm) * 1000); - if(FlxG.keys.justPressed.ANY && !strumLines.isDragging && this.currentFocus == null) + if(FlxG.keys.justPressed.ANY && !strumLines.isDragging && this.currentFocus == null && (this.subState == null || !(this.subState is CharterEventScreenNew))) UIUtil.processShortcuts(topMenu); - if (FlxG.keys.pressed.CONTROL) { - if (FlxG.mouse.wheel != 0) { - zoom += 0.25 * FlxG.mouse.wheel; - __camZoom = Math.pow(2, zoom); - } - } else { - if (!FlxG.sound.music.playing && FlxG.mouse.wheel != 0) { - Conductor.songPosition -= (__crochet * FlxG.mouse.wheel) - Conductor.songOffset; + if (!topMenuSpr.anyMenuOpened) { + if (FlxG.mouse.wheel != 0 && shouldScroll) { + if (FlxG.keys.pressed.CONTROL) { + zoom += 0.25 * FlxG.mouse.wheel; + __camZoom = Math.pow(2, zoom); + } else if (FlxG.keys.pressed.SHIFT) { + sideScroll -= 40 * FlxG.mouse.wheel; + } else { + if (!FlxG.sound.music.playing) { + Conductor.songPosition -= (__crochet * FlxG.mouse.wheel) - Conductor.songOffset; + } + } } } } var songLength = FlxG.sound.music.getDefault(vocals).length; - Conductor.songPosition = FlxMath.bound(Conductor.songPosition + Conductor.songOffset, 0, songLength); + Conductor.songPosition = CoolUtil.bound(Conductor.songPosition + Conductor.songOffset, 0, songLength); if (Conductor.songPosition >= songLength - Conductor.songOffset) { FlxG.sound.music.pause(); @@ -1261,14 +1373,15 @@ class Charter extends UIState { for (strumLine in strumLines.members) strumLine.vocals.pause(); } + var curChange = Conductor.curChange; songPosInfo.text = [ // no need to translate the time text since it has no text only numbers '${CoolUtil.timeToStr(Conductor.songPosition)} / ${CoolUtil.timeToStr(songLength)}', SONGPOSINFO_STEP.format([curStep]), SONGPOSINFO_BEAT.format([curBeat]), SONGPOSINFO_MEASURE.format([curMeasure]), - SONGPOSINFO_BPM.format([Conductor.bpm]), - SONGPOSINFO_TIMESIGNATURE.format([Conductor.beatsPerMeasure, Conductor.stepsPerBeat]) + SONGPOSINFO_BPM.format([(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? FlxMath.roundDecimal(Conductor.bpm, 3) : Conductor.bpm]), + SONGPOSINFO_TIMESIGNATURE.format([Conductor.beatsPerMeasure, Conductor.denominator]) ].join("\n"); if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, __firstFrame ? 1 : 0.125))) @@ -1289,7 +1402,15 @@ class Charter extends UIState { function updateDisplaySprites() { gridBackdrops.strumlinesAmount = strumLines.members.length; - charterBG.scale.set(1 / charterCamera.zoom, 1 / charterCamera.zoom); + var scaleX:Float = (FlxG.width/charterBG.width); + var scaleY:Float = (FlxG.height/charterBG.height); + + var bgScale:Float = scaleX > scaleY ? scaleX : scaleY; + + charterBG.scale.set( + (1 / charterCamera.zoom) * bgScale, + (1 / charterCamera.zoom) * bgScale + ); strumlineInfoBG.scale.set(FlxG.width / charterCamera.zoom, 1); strumlineInfoBG.updateHitbox(); @@ -1299,112 +1420,159 @@ class Charter extends UIState { for(id=>str in strumLines.members) if (str != null) str.y = strumlineInfoBG.y; + //strumlineAddButton.x = 0; strumlineAddButton.y = strumlineInfoBG.y; strumlineLockButton.y = strumlineInfoBG.y; + + strumlineLockButton.text.visible = strumlineLockButton.button.selectable = strumlineLockButton.button.visible = strumLines.members.length > 0; + } + + public override function onResize(width:Int, height:Int) { + super.onResize(width, height); + if (!UIState.resolutionAware) return; + + if (width < FlxG.initialWidth || height < FlxG.initialHeight) { + width = FlxG.initialWidth; height = FlxG.initialHeight; + } + + scrollBar.x = width - 20; + scrollBar.scale.y = Std.int(height - scrollBar.y); + scrollBar.updateHitbox(); + + songPosInfo.x = width - 30 - 400; + playBackSlider.x = width - 160 - 26 - 20; + + updateDisplaySprites(); + updateBookmarks(); + charterBG.screenCenter(); } var zoom(default, set):Float = 0; var __camZoom(default, set):Float = 1; function set_zoom(val:Float) { - return zoom = FlxMath.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling + return zoom = CoolUtil.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling } function set___camZoom(val:Float) { - return __camZoom = FlxMath.bound(val, 0.1, 3); + return __camZoom = CoolUtil.bound(val, 0.1, 3); } + var sideScroll(default, set):Float = 0; + function set_sideScroll(val:Float) { + return sideScroll = FlxMath.bound(val, -(40*strumLines.totalKeyCount) / 2, (40*strumLines.totalKeyCount) / 2); + } + + // SAVE FUNCS + #if REGION + public static function saveEverything(shouldBuild:Bool = true) { + if (shouldBuild && instance != null) instance.buildChart(); + saveChart(false); + saveEvents(false); + saveMeta(false); + } + + public static function saveChart(shouldBuild:Bool = true, withEvents:Bool = true) { + #if sys + saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', !withEvents, shouldBuild); + if (undos != null) undos.save(); + #else + saveChartAs(shouldBuild, withEvents); + #end + } + + public static function saveChartAs(shouldBuild:Bool = true, withEvents:Bool = true) { + saveAs(Chart.filterChartForSaving(PlayState.SONG, false, withEvents, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, { + defaultSaveFile: '${__diff.toLowerCase()}.json' + }, null, shouldBuild); + if (undos != null) undos.save(); + } + + public static function saveEvents(shouldBuild:Bool = true) { + #if sys + if (shouldBuild && instance != null) instance.buildChart(); + var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events}; + + var path = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json'; + if (data.events != null && data.events.length > 0) CoolUtil.safeSaveFile(path, Json.stringify(data, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null)); + else if (FileSystem.exists(path)) FileSystem.deleteFile(path); // Instead of replacing with a useless empty file, deletes the file directly - Nex + #else + saveEventsAs(shouldBuild); + #end + } + + public static function saveEventsAs(shouldBuild:Bool = true) { + if (shouldBuild && instance != null) instance.buildChart(); + var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events}; + + saveAs(data, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, { + defaultSaveFile: 'events.json' + }, null, false); + } + + public static function saveMeta(shouldBuild:Bool = true) { + #if sys + if (shouldBuild && instance != null) instance.buildChart(); + CoolUtil.safeSaveFile( + '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json', + Json.stringify(PlayState.SONG.meta == null ? {} : Chart.filterChartForSaving(PlayState.SONG, true, false, false).meta, null, Flags.JSON_PRETTY_PRINT) + ); + #else + saveMetaAs(shouldBuild); + #end + } + + public static function saveMetaAs(shouldBuild:Bool = true) { + saveAs(PlayState.SONG.meta == null ? {} : Chart.filterChartForSaving(PlayState.SONG, true, false, false).meta, null, Flags.JSON_PRETTY_PRINT, { // always pretty print meta + defaultSaveFile: 'meta.json' + }, null, shouldBuild); + } + + public static function saveLegacyChartAs(shouldBuild:Bool = true) { + saveAs(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, { + defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json', + }, null, shouldBuild); + } + + public static function savePsychChartAs(shouldBuild:Bool = true) { + saveAs(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, { + defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json', + }, null, shouldBuild); + } + + public static function saveAs(data:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String, ?options:SaveSubstate.SaveSubstateData, ?saveOptions:Map<String, Bool>, shouldBuild:Bool = true) { + if (shouldBuild && instance != null) instance.buildChart(); + var cur = FlxG.state; + while(true) { + if (instance != null || cur.subState == null) return cur.openSubState(new SaveSubstate(Json.stringify(data, replacer, space), options, saveOptions)); + else cur = cur.subState; + } + } + + #if sys + public static function saveTo(path:String, separateEvents:Bool = false, shouldBuild:Bool = true) { + if (shouldBuild && instance != null) instance.buildChart(); + Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveLocalEvents: !separateEvents, prettyPrint: Options.editorPrettyPrint}); + } + #end + #end + // TOP MENU OPTIONS #if REGION function _file_exit(_) { if (undos.unsaved) SaveWarning.triggerWarning(); - else {undos = null; FlxG.switchState(new CharterSelection()); PlayState.resetSongInfos(); Charter.instance.__clearStatics();} + else {undos = null; FlxG.switchState(new CharterSelection()); Charter.instance.__clearStatics();} } - function _file_save(_) { - #if sys - saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}'); - undos.save(); - return; - #end - _file_saveas(_); - } - - function _file_saveas(_) { - openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), { - defaultSaveFile: '${__diff.toLowerCase()}.json' - })); - undos.save(); - } - - function _file_save_no_events(_) { - #if sys - saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', true); - undos.save(); - return; - #end - _file_saveas(_); - } - - function _file_saveas_no_events(_) { - openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), { - defaultSaveFile: '${__diff.toLowerCase()}.json' - })); - undos.save(); - } - - function _file_meta_save(_) { - #if sys - CoolUtil.safeSaveFile( - '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json', - Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT) - ); - #else - _file_meta_saveas(_); - #end - } - - function _file_meta_saveas(_) { - openSubState(new SaveSubstate(Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT), { // always pretty print meta - defaultSaveFile: 'meta.json' - })); - } - - function _file_saveas_fnflegacy(_) { - openSubState(new SaveSubstate(Json.stringify(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), { - defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json', - })); - } - - function _file_saveas_psych(_) { - openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), { - defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json', - })); - } - - function _file_events_save(_) { - #if sys - CoolUtil.safeSaveFile( - '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json', - Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null) - ); - #else - _file_events_saveas(_); - #end - } - - function _file_events_saveas(_) { - #if sys - openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), { - defaultSaveFile: 'events.json' - })); - #end - } - - #if sys - function saveTo(path:String, separateEvents:Bool = false) { - buildChart(); - Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: !separateEvents, prettyPrint: Options.editorPrettyPrint}); - } - #end + function _file_save_all(_) saveEverything(); + function _file_save(_) saveChart(); + function _file_saveas(_) saveChartAs(); + function _file_events_save(_) saveEvents(); + function _file_events_saveas(_) saveEventsAs(); + function _file_save_no_events(_) saveChart(true, false); + function _file_saveas_no_events(_) saveChartAs(true, false); + function _file_meta_save(_) saveMeta(); + function _file_meta_saveas(_) saveMetaAs(); + function _file_saveas_fnflegacy(_) saveLegacyChartAs(); + function _file_saveas_psych(_) savePsychChartAs(); function _edit_copy(_) { if(selection.length == 0) return; @@ -1420,7 +1588,7 @@ class Charter extends UIState { CNote(note.step - minStep, note.id, note.strumLineID, note.susLength, note.type); } else if (s is CharterEvent) { var event:CharterEvent = cast s; - CEvent(event.step - minStep, [for (event in event.events) Reflect.copy(event)]); + CEvent(event.step - minStep, [for (event in event.events) Reflect.copy(event)], event.global); } ]; } @@ -1433,19 +1601,21 @@ class Charter extends UIState { switch(c) { case CNote(step, id, strumLineID, susLength, type): var note = new CharterNote(); - note.updatePos(minStep + step, id, susLength, type, strumLines.members[Std.int(FlxMath.bound(strumLineID, 0, strumLines.length-1))]); + note.updatePos(minStep + step, id, susLength, type, strumLines.members[CoolUtil.boundInt(strumLineID, 0, strumLines.length-1)]); notesGroup.add(note); sObjects.push(note); - case CEvent(step, events): - var event = new CharterEvent(minStep + step, events); + case CEvent(step, events, global): + var event = new CharterEvent(minStep + step, events, global); event.refreshEventIcons(); - eventsGroup.add(event); + (global ? rightEventsGroup : leftEventsGroup).add(event); sObjects.push(event); } } selection = sObjects; _edit_copy(_); // to fix stupid bugs + checkSelectionForBPMUpdates(); + undos.addToUndo(CCreateSelection(sObjects.copy())); } @@ -1464,11 +1634,7 @@ class Charter extends UIState { selection = deleteSelection(selection, true); } - function _edit_undo(_) { - if (strumLines.isDragging || selectionDragging || (subState != null && !(subState is UIContextMenu))) return; - - selection = []; - var undo = undos.undo(); + function _undo(undo:CharterChange) { switch(undo) { case null: // do nothing case CDeleteStrumLine(strumLineID, strumLine): @@ -1482,6 +1648,7 @@ class Charter extends UIState { case CEditStrumLine(strumLineID, oldStrumLine, newStrumLine): strumLines.members[strumLineID].strumLine = oldStrumLine; strumLines.members[strumLineID].updateInfo(); + strumLines.refreshStrumlineIDs(); case CCreateSelection(selection): deleteSelection(selection, false); case CDeleteSelection(selection): @@ -1491,6 +1658,7 @@ class Charter extends UIState { if (s.selectable.draggable) s.selectable.handleDrag(s.change * -1); selection = [for (s in selectionDrags) s.selectable]; + checkSelectionForBPMUpdates(); case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.before, n.note.type); @@ -1499,22 +1667,34 @@ class Charter extends UIState { event.refreshEventIcons(); Charter.instance.updateBPMEvents(); + case CEditEventGroups(events): + for (event in events) event.displayGlobal = !event.global; + updateEventsGroups(cast events); + case CEditChartData(oldData, newData): PlayState.SONG.stage = oldData.stage; PlayState.SONG.scrollSpeed = oldData.speed; case CEditNoteTypes(oldArray, newArray): noteTypes = oldArray; changeNoteType(null, false); + case CEditBookmarks(oldArray, newArray): + PlayState.SONG.bookmarks = oldArray; + updateBookmarks(); case CEditSpecNotesType(notes, oldTypes, newTypes): for(i=>note in notes) note.updatePos(note.step, note.id, note.susLength, oldTypes[i]); + case CChangeBundle(changes): + for (change in changes) _undo(change); } } - function _edit_redo(_) { + function _edit_undo(_) { if (strumLines.isDragging || selectionDragging || (subState != null && !(subState is UIContextMenu))) return; selection = []; - var redo = undos.redo(); + _undo(undos.undo()); + } + + function _redo(redo:CharterChange) { switch(redo) { case null: // do nothing case CDeleteStrumLine(strumLineID, strumLine): @@ -1528,6 +1708,7 @@ class Charter extends UIState { case CEditStrumLine(strumLineID, oldStrumLine, newStrumLine): strumLines.members[strumLineID].strumLine = newStrumLine; strumLines.members[strumLineID].updateInfo(); + strumLines.refreshStrumlineIDs(); case CCreateSelection(selection): createSelection(selection, false); case CDeleteSelection(selection): @@ -1536,6 +1717,7 @@ class Charter extends UIState { for (s in selectionDrags) if (s.selectable.draggable) s.selectable.handleDrag(s.change); //this.selection = selection; + checkSelectionForBPMUpdates(); case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.after, n.note.type); @@ -1544,17 +1726,33 @@ class Charter extends UIState { event.refreshEventIcons(); Charter.instance.updateBPMEvents(); + case CEditEventGroups(events): + for (event in events) event.displayGlobal = !event.global; + updateEventsGroups(cast events); + case CEditChartData(oldData, newData): PlayState.SONG.stage = newData.stage; PlayState.SONG.scrollSpeed = newData.speed; case CEditNoteTypes(oldArray, newArray): noteTypes = newArray; changeNoteType(null, false); + case CEditBookmarks(oldArray, newArray): + PlayState.SONG.bookmarks = newArray; + updateBookmarks(); case CEditSpecNotesType(notes, oldTypes, newTypes): for(i=>note in notes) note.updatePos(note.step, note.id, note.susLength, newTypes[i]); + case CChangeBundle(changes): + for (change in changes) _redo(change); } } + function _edit_redo(_) { + if (strumLines.isDragging || selectionDragging || (subState != null && !(subState is UIContextMenu))) return; + + selection = []; + _redo(undos.redo()); + } + inline function _chart_playtest(_) playtestChart(0, false); inline function _chart_playtest_here(_) @@ -1580,12 +1778,10 @@ class Charter extends UIState { vocals.pause(); for (strumLine in strumLines.members) strumLine.vocals.pause(); } else { - FlxG.sound.music.play(); - vocals.play(); - vocals.time = FlxG.sound.music.time = Conductor.songPosition + Conductor.songOffset * 2; + FlxG.sound.music.play(true, Conductor.songPosition + Conductor.songOffset); + vocals.play(true, FlxG.sound.music.getActualTime()); for (strumLine in strumLines.members) { - strumLine.vocals.play(); - strumLine.vocals.time = vocals.time; + strumLine.vocals.play(true, FlxG.sound.music.getActualTime()); } } } @@ -1634,6 +1830,171 @@ class Charter extends UIState { if (FlxG.sound.music.playing) return; Conductor.songPosition = FlxG.sound.music.length; } + + public function getBookmarkList():Array<ChartBookmark> { + var bookmarks:Array<ChartBookmark> = []; + try { + if (PlayState.SONG.bookmarks != null) + bookmarks = PlayState.SONG.bookmarks; + } catch (e) {} + + return bookmarks; + } + + function _bookmarks_add(_) { + var addBookmarkAt = function(name:String, color:FlxColor, daStep:Float) + { + var currentBookmarks:Array<ChartBookmark> = getBookmarkList(); + var newBookmarks:Array<ChartBookmark> = getBookmarkList(); + newBookmarks.push({time: daStep, name: name, color: color.toWebString()}); + + PlayState.SONG.bookmarks = newBookmarks; + updateBookmarks(); + undos.addToUndo(CEditBookmarks(currentBookmarks, newBookmarks)); + } + + if (FlxG.keys.pressed.SHIFT) + addBookmarkAt("New Bookmark", 0xFF911DD9, curStepFloat); + else { + FlxG.state.openSubState(new CharterBookmarkCreation(curStepFloat, (success, n, c, s) -> { + if (success) + addBookmarkAt(n, c, s); + })); + } + } + function _bookmarks_edit_list(_) + FlxG.state.openSubState(new CharterBookmarkList()); //idk why its FlxG.state but it looks so off lmfao + + public var __bookmarkObjects:Array<Dynamic> = []; + public var __scrollbarBookmarks:Array<Dynamic> = []; + public function updateBookmarks() + { + for (bs in __bookmarkObjects) + { + var bars:Array<FlxSprite> = bs[0]; + var text:UIText = bs[1]; + + if (bars != null) { + for (spr in bars) { + if (spr == null) continue; + remove(spr); + spr.kill(); + } + } + if (text != null) { + remove(text); + text.kill(); + } + } + for (bar in __scrollbarBookmarks) { + if (bar == null) continue; + uiGroup.remove(bar); + bar.kill(); + } + __bookmarkObjects.clear(); + __scrollbarBookmarks.clear(); + for (b in getBookmarkList()) + { + var bookmarkcolor:FlxColor = b.color != null ? FlxColor.fromString(b.color) : 0xff9d00ff; + var luminance = CoolUtil.getLuminance(bookmarkcolor); + + var sprites = []; + for (str in strumLines.members) + { + var bookmarkspr = new FlxSprite(str.x, (b.time * 40)).makeSolid(str.keyCount * 40, 4, bookmarkcolor); + bookmarkspr.updateHitbox(); + bookmarkspr.camera = charterCamera; + add(bookmarkspr); + sprites.push(bookmarkspr); + } + + var bookmarkText = new UIText(strumLines.members[0].x + 4, 0, 400, b.name, 15, bookmarkcolor, true); + bookmarkText.y = sprites[0].y - (bookmarkText.height + 2); + bookmarkText.camera = charterCamera; + add(bookmarkText); + + if (luminance < 0.5) + bookmarkText.borderColor = 0x88FFFFFF; + + __bookmarkObjects.push([sprites, bookmarkText]); + + var yPos = scrollBar.y + CoolUtil.bound( + FlxMath.remapToRange( + b.time, + 0, + scrollBar.length + scrollBar.size, + 0, + scrollBar.height + ), + 0, + scrollBar.height + ); + + var bookmarkspr = new FlxSprite(scrollBar.x - 10, yPos).makeSolid(40, 4, bookmarkcolor); + uiGroup.add(bookmarkspr); + sprites.push(bookmarkspr); + __scrollbarBookmarks.push(bookmarkspr); + } + + buildSongUI(); + } + + function buildSongUI():Array<UIContextMenuOption> { + var songTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[songIndex]; + var newChilds:Array<UIContextMenuOption> = [ + { + label: TU.translate("chart.song.goStart"), + keybind: [HOME], + onSelect: _song_start + }, + { + label: TU.translate("chart.song.goEnd"), + keybind: [END], + onSelect: _song_end + }, + null, + { + label: "Add Bookmark Here", + onSelect: _bookmarks_add + }, + { + label: "Edit Bookmark List", + color: 0xFF959829, icon: 4, + onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2; button.icon.offset.y = -1;}, + onSelect: _bookmarks_edit_list + }, + null + ]; + + var bookmarks:Array<ChartBookmark> = getBookmarkList(); + + if (bookmarks.length > 0) + { + for (b in bookmarks) + { + newChilds.push({ + label: "Go To \"" + b.name + "\"", + onSelect: function(_) { Conductor.songPosition = Conductor.getTimeForStep(b.time); } + }); + } + newChilds.push(null); + } + + + newChilds.push({ + label: TU.translate("chart.song.muteInst"), + onSelect: _song_muteinst + }); + + newChilds.push({ + label: TU.translate("chart.song.muteVoices"), + onSelect: _song_mutevoices + }); + + if (songTopButton != null) songTopButton.contextMenu = newChilds; + return newChilds; + } + function _view_zoomin(_) { zoom += 0.25; __camZoom = Math.pow(2, zoom); @@ -1648,17 +2009,31 @@ class Charter extends UIState { } function _view_showeventSecSeparator(t) { t.icon = (Options.charterShowSections = !Options.charterShowSections) ? 1 : 0; - eventsBackdrop.eventSecSeparator.visible = gridBackdrops.sectionsVisible = Options.charterShowSections; } function _view_showeventBeatSeparator(t) { t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0; - eventsBackdrop.eventBeatSeparator.visible = gridBackdrops.beatsVisible = Options.charterShowBeats; + } + function _view_switchWaveformRainbow(t) { + t.icon = (Options.charterRainbowWaveforms = !Options.charterRainbowWaveforms) ? 1 : 0; + + waveformHandler.clearWaveforms(); + updateWaveforms(); } function _view_switchWaveformDetail(t) { t.icon = (Options.charterLowDetailWaveforms = !Options.charterLowDetailWaveforms) ? 1 : 0; for (shader in waveformHandler.waveShaders) shader.data.lowDetail.value = [Options.charterLowDetailWaveforms]; } + function _view_scrollleft(_) { + sideScroll -= 40; + } + function _view_scrollright(_) { + sideScroll += 40; + } + function _view_scrollreset(_) { + sideScroll = 0; + } + inline function _snap_increasesnap(_) changequant(1); inline function _snap_decreasesnap(_) changequant(-1); inline function _snap_resetsnap(_) setquant(16); @@ -1733,7 +2108,7 @@ class Charter extends UIState { inline public function changeNoteType(?newID:Int, checkSelection:Bool = true) { if(newID != null) noteType = newID; - noteType = Std.int(FlxMath.bound(noteType, 0, noteTypes.length)); + noteType = CoolUtil.boundInt(noteType, 0, noteTypes.length); buildNoteTypesUI(); var changedNotes:{notes:Array<CharterNote>, oldTypes:Array<Int>, newTypes:Array<Int>} = {notes:[], oldTypes:[], newTypes:[]}; @@ -1831,6 +2206,7 @@ class Charter extends UIState { public function buildChart() { PlayState.SONG.strumLines = []; PlayState.SONG.noteTypes = this.noteTypes; + PlayState.SONG.chartVersion = Chart.version; for(s in strumLines) { s.strumLine.notes = []; PlayState.SONG.strumLines.push(s.strumLine); @@ -1845,24 +2221,50 @@ class Charter extends UIState { public function buildEvents() { PlayState.SONG.events = []; - eventsGroup.sortEvents(); - for(e in eventsGroup.members) { - for(event in e.events) { - event.time = Conductor.getTimeForStep(e.step); - PlayState.SONG.events.push(event); - } + + var events:Array<CharterEvent> = leftEventsGroup.members.concat(rightEventsGroup.members); + events.sort(rightEventsGroup.sortEventsFilter.bind(FlxSort.ASCENDING)); + for (e in events) for (event in e.events) { + event.time = Conductor.getTimeForStep(e.step); + PlayState.SONG.events.push(event); } } public function updateBPMEvents() { + leftEventsGroup.sortEvents(); + rightEventsGroup.sortEvents(); + Conductor.mapCharterBPMChanges(PlayState.SONG); buildEvents(); - Conductor.mapBPMChanges(PlayState.SONG); - Conductor.changeBPM(PlayState.SONG.meta.bpm, cast PlayState.SONG.meta.beatsPerMeasure.getDefault(4), cast PlayState.SONG.meta.stepsPerBeat.getDefault(4)); + for(e in leftEventsGroup.members) { + for(event in e.events) { + if (event.name == "BPM Change" || event.name == "Time Signature Change" || event.name == "Continuous BPM Change") { + e.refreshEventIcons(); + break; + } + } + } + + for(e in rightEventsGroup.members) { + for(event in e.events) { + if (event.name == "BPM Change" || event.name == "Time Signature Change" || event.name == "Continuous BPM Change") { + e.refreshEventIcons(); + break; + } + } + } refreshBPMSensitive(); } + public inline function checkSelectionForBPMUpdates() { + for (s in selection) + if (s is CharterEvent) { + updateBPMEvents(); + break; + } + } + public inline function hitsoundsEnabled(id:Int) return strumLines.members[id] != null && strumLines.members[id].hitsounds; @@ -1877,8 +2279,10 @@ class Charter extends UIState { @:noCompletion public function __relinkSingleSelection(selectable:ICharterSelectable):ICharterSelectable { if (selectable is CharterNote) return selectable.ID == -1 ? cast(selectable, CharterNote) : notesGroup.members[selectable.ID]; - else if (selectable is CharterEvent) - return selectable.ID == -1 ? cast(selectable, CharterEvent) : eventsGroup.members[selectable.ID]; + else if (selectable is CharterEvent) { + var event:CharterEvent = cast(selectable, CharterEvent); + return selectable.ID == -1 ? event : (event.global ? rightEventsGroup : leftEventsGroup).members[selectable.ID]; + } return null; } @@ -1987,14 +2391,18 @@ enum CharterChange { CSelectionDrag(selectionDrags:Array<SelectionDragChange>); CEditSustains(notes:Array<NoteSustainChange>); CEditEvent(event:CharterEvent, oldEvents:Array<ChartEvent>, newEvents:Array<ChartEvent>); + CEditEventGroups(events:Array<CharterEvent>); CEditChartData(oldData:{stage:String, speed:Float}, newData:{stage:String, speed:Float}); CEditNoteTypes(oldArray:Array<String>, newArray:Array<String>); + CEditBookmarks(oldArray:Array<ChartBookmark>, newArray:Array<ChartBookmark>); CEditSpecNotesType(notes:Array<CharterNote>, oldNoteTypes:Array<Int>, newNoteTypes:Array<Int>); + + CChangeBundle(changes:Array<CharterChange>); } enum CharterCopyboardObject { CNote(step:Float, id:Int, strumLineID:Int, susLength:Float, type:Int); - CEvent(step:Float, events:Array<ChartEvent>); + CEvent(step:Float, events:Array<ChartEvent>, global:Bool); } typedef NoteSustainChange = { @@ -2032,6 +2440,7 @@ interface ICharterSelectable { public var selected:Bool; public var hovered:Bool; public var draggable:Bool; + public var snappedToGrid:Bool; public function handleSelection(selectionBox:UISliceSprite):Bool; public function handleDrag(change:FlxPoint):Void; diff --git a/source/funkin/editors/charter/CharterAutoSaveUI.hx b/source/funkin/editors/charter/CharterAutoSaveUI.hx index e4c8989e..6f2871b0 100644 --- a/source/funkin/editors/charter/CharterAutoSaveUI.hx +++ b/source/funkin/editors/charter/CharterAutoSaveUI.hx @@ -39,7 +39,7 @@ class CharterAutoSaveUI extends UISliceSprite { progressBar.antialiasing = false; progressBar.clipRect = new FlxRect(0, 0, progressBar.width, progressBar.height); members.push(progressBar); - cancelButton = new UIButton(x-(10+16), y+8, "", () -> { + cancelButton = new UIButton(x-(10+16), y+8, null, () -> { if (cancelled || !showedAnimation) return; cancelled = true; __timer.cancel(); __tween.cancel(); @@ -108,7 +108,7 @@ class CharterAutoSaveUI extends UISliceSprite { bar.follow(this, 10, bHeight-20); bar.alpha = alpha; } - progressBar.clipRect = progressBar.clipRect.set(0, 0, progressBar.width*progress,progressBar.height); + progressBar.rawClipRect = progressBar.rawClipRect.set(0, 0, progressBar.frameWidth*progress,progressBar.frameHeight); } public function appearAnimation() { diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index 79dbc25c..a4e35ce6 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -4,16 +4,45 @@ import flixel.addons.display.FlxBackdrop; import flixel.graphics.FlxGraphic; import funkin.backend.system.Conductor; import openfl.geom.Rectangle; +import funkin.backend.shaders.CustomShader; + +class FlxFastTypedGroup<T:FlxBasic> extends FlxTypedGroup<T> { + public function new(?maxSize:Int = 0) { + super(maxSize); + } + + override public function add(Object:T):T + { + if (Object == null) + { + FlxG.log.warn("Cannot add a `null` object to a FlxGroup."); + return null; + } + + // removed the check for multiple + // removed the check for null reuse + + // If the group is full, return the Object + if (maxSize > 0 && length >= maxSize) + return Object; + + // If we made it this far, we need to add the object to the group. + members.push(Object); + length++; + + if (_memberAdded != null) + _memberAdded.dispatch(Object); + + return Object; + } +} class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { public var strumLineGroup:CharterStrumLineGroup; public var notesGroup:CharterNoteGroup; - var __gridGraphic:FlxGraphic; public var conductorSprY:Float = 0; public var bottomLimitY:Float = 0; - public var sectionsVisible:Bool = true; - public var beatsVisible:Bool = true; // Just here so you can update display sprites all dat and above public var strumlinesAmount:Int = 0; @@ -21,23 +50,11 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { public function new(strumLineGroup:CharterStrumLineGroup) { super(); this.strumLineGroup = strumLineGroup; - - __gridGraphic = FlxG.bitmap.create(160, 160, 0xFF272727, true); - __gridGraphic.bitmap.lock(); - - // Checkerboard - for(y in 0...4) - for(x in 0...2) __gridGraphic.bitmap.fillRect(new Rectangle(40*((x*2)+(y%2)), 40*y, 40, 40), 0xFF545454); - - // Edges - __gridGraphic.bitmap.fillRect(new Rectangle(0, 0, 1, 160), 0xFFDDDDDD); - __gridGraphic.bitmap.fillRect(new Rectangle(159, 0, 1, 160), 0xFFDDDDDD); - __gridGraphic.bitmap.unlock(); } public function createGrids(amount:Int = 0) { for (i in 0...amount) { - var grid = new CharterBackdrop(__gridGraphic); + var grid = new CharterBackdrop(); grid.active = grid.visible = false; add(grid); } @@ -53,7 +70,7 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { if (strumLine == null) continue; if (members[i] == null) - members[i] = recycle(CharterBackdrop, () -> {return new CharterBackdrop(__gridGraphic);}); + members[i] = recycle(CharterBackdrop, () -> {return new CharterBackdrop();}); var grid = members[i]; grid.cameras = this.cameras; @@ -61,16 +78,15 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { grid.conductorFollowerSpr.y = conductorSprY; grid.bottomSeparator.y = (grid.bottomLimit.y = bottomLimitY)-2; - grid.sectionSeparator.visible = sectionsVisible; - grid.beatSeparator.visible = beatsVisible; grid.waveformSprite.shader = strumLine.waveformShader; grid.notesGroup.clear(); notesGroup.forEach((n) -> { - var onStr:Bool = (n.snappedToStrumline ? n.strumLineID : Std.int(FlxMath.bound((n.x+n.width)/(40*4), 0, strumLineGroup.members.length-1))) == i; - if(n.exists && n.visible && onStr) - grid.notesGroup.add(n); + if(n.exists && n.visible) { + var onStr:Bool = (n.snappedToGrid ? n.strumLineID : CoolUtil.boundInt(Std.int((n.x+n.width)/(40*strumLine.keyCount)), 0, strumLineGroup.members.length-1)) == i; + if(onStr) grid.notesGroup.add(n); + } }); grid.active = grid.visible = true; @@ -97,8 +113,7 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { if (cameras != null) FlxCamera._defaultCameras = cameras; - while (i < length) - { + while (i < length) { basic = members[i++]; if (basic != null && basic != draggingObj && basic.exists && basic.visible) basic.draw(); @@ -109,7 +124,43 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { } } -class CharterBackdrop extends FlxTypedGroup<Dynamic> { +// Batches note draws (neos idea) >:D -lunar +class NotesDrawGroup extends FlxFastTypedGroup<CharterNote> { + public override function draw() @:privateAccess { + var oldDefaultCameras = FlxCamera._defaultCameras; + if (cameras != null) + FlxCamera._defaultCameras = cameras; + + var i:Int = 0; + var note:CharterNote = null; + + while (i < length) { + note = members[i++]; + if (note != null && note.exists && note.visible) { + if (note.snappedToGrid) note.x = (note.strumLine != null ? note.strumLine.x : 0) + (note.id % (note.strumLine != null ? note.strumLine.keyCount : 4)) * 40; + note.drawMembers(); + } + } + + i = 0; note = null; + while (i < length) { + note = members[i++]; + if (note != null && note.exists && note.visible) + note.drawSuper(); + } + + i = 0; note = null; + while (i < length) { + note = members[i++]; + if (note != null && note.exists && note.visible) + note.drawNoteTypeText(); + } + + FlxCamera._defaultCameras = oldDefaultCameras; + } +} + +class CharterBackdrop extends FlxTypedGroup<FlxBasic> { public var gridBackDrop:FlxBackdrop; public var topLimit:FlxSprite; public var topSeparator:FlxSprite; @@ -119,40 +170,36 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> { public var waveformSprite:FlxSprite; public var conductorFollowerSpr:FlxSprite; - public var beatSeparator:FlxBackdrop; - public var sectionSeparator:FlxBackdrop; + public var beatSeparator:CharterGridSeperator; - public var notesGroup:FlxTypedGroup<CharterNote> = new FlxTypedGroup<CharterNote>(); + public var notesGroup:NotesDrawGroup = new NotesDrawGroup(); public var strumLine:CharterStrumline; - public function new(gridGraphic:FlxGraphic) { + public var gridShader:CustomShader = new CustomShader("engine/charterGrid"); + var __lastKeyCount:Int = 4; + + public function new() { super(); - gridBackDrop = new FlxBackdrop(gridGraphic, Y, 0, 0); + gridBackDrop = new FlxBackdrop(null, Y, 0, 0); + gridBackDrop.makeSolid(1, 1, -1); + gridBackDrop.shader = gridShader; add(gridBackDrop); + gridShader.hset("segments", 4); waveformSprite = new FlxSprite().makeSolid(1, 1, 0xFF000000); waveformSprite.scale.set(160, 1); waveformSprite.updateHitbox(); add(waveformSprite); - sectionSeparator = new FlxBackdrop(null, Y, 0, 0); - sectionSeparator.y = -2; - sectionSeparator.visible = Options.charterShowSections; + beatSeparator = new CharterGridSeperator(); + beatSeparator.makeSolid(1, 1, -1); + beatSeparator.alpha = 0.5; + beatSeparator.scrollFactor.set(1, 1); + beatSeparator.scale.set((4 * 40), 2); + beatSeparator.updateHitbox(); - beatSeparator = new FlxBackdrop(null, Y, 0, 0); - beatSeparator.y = -1; - beatSeparator.visible = Options.charterShowBeats; - - for(sep in [sectionSeparator, beatSeparator]) { - sep.makeSolid(1, 1, -1); - sep.alpha = 0.5; - sep.scrollFactor.set(1, 1); - sep.scale.set((4 * 40), sep == sectionSeparator ? 4 : 2); - sep.updateHitbox(); - } add(beatSeparator); - add(sectionSeparator); add(notesGroup); bottomSeparator = new FlxSprite(0,-2); @@ -194,32 +241,41 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> { public function updateSprites() { var x:Float = 0; // fuck you var alpha:Float = 0.9; + var keyCount:Int = 4; if (strumLine != null) { x = strumLine.x; alpha = strumLine.strumLine.visible ? 0.9 : 0.4; + keyCount = strumLine.keyCount; } else alpha = 0.9; - for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit, + for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit, topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) { spr.x = x; if (spr != waveformSprite) spr.alpha = alpha; spr.cameras = this.cameras; } - sectionSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1; - beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; + gridBackDrop.setGraphicSize(40*keyCount, 160); + gridBackDrop.updateHitbox(); + if (__lastKeyCount != keyCount) gridShader.hset("segments", keyCount); + __lastKeyCount = keyCount; - topLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom)); + topLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom)); topLimit.updateHitbox(); topLimit.y = -topLimit.height; - bottomLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom)); + bottomLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom)); bottomLimit.updateHitbox(); + for (spr in [conductorFollowerSpr, beatSeparator, topSeparator, bottomSeparator]) { + spr.scale.x = keyCount * 40; + spr.updateHitbox(); + } + waveformSprite.visible = waveformSprite.shader != null; if (waveformSprite.shader == null) return; - waveformSprite.scale.set(160, FlxG.height * (1/cameras[0].zoom)); + waveformSprite.scale.set(keyCount * 40, FlxG.height * (1/cameras[0].zoom)); waveformSprite.updateHitbox(); waveformSprite.y = (cameras[0].scroll.y+FlxG.height/2)-(waveformSprite.height/2); @@ -237,57 +293,178 @@ class CharterBackdrop extends FlxTypedGroup<Dynamic> { } } -class CharterBackdropDummy extends UISprite { - var parent:CharterBackdropGroup; - public function new(parent:CharterBackdropGroup) { - super(); - this.parent = parent; - cameras = parent.cameras; - scrollFactor.set(1, 0); +class CharterGridSeperatorBase extends FlxSprite { + + private static var minStep:Float = 0; + private static var maxStep:Float = 0; + + private static var minBeat:Float = 0; + private static var maxBeat:Float = 0; + + private static var minMeasure:Float = 0; + private static var maxMeasure:Float = 0; + + private static var lastMinBeat:Float = -1; + private static var lastMaxBeat:Float = -1; + + private static var lastMinMeasure:Float = -1; + private static var lastMaxMeasure:Float = -1; + + public static var lastConductorSprY:Float = Math.NEGATIVE_INFINITY; + + private static var beatStepTimes:Array<Float> = []; + private static var measureStepTimes:Array<Float> = []; + private static var timeSignatureChangeGaps:Array<Float> = []; + + private function recalculateBeats() { + var conductorSprY = Charter.instance.gridBackdrops.conductorSprY; + if (conductorSprY == lastConductorSprY) return; + + var zoomOffset = ((FlxG.height * (1/cameras[0].zoom)) * 0.5); + + minStep = (conductorSprY - zoomOffset)/40; + maxStep = (conductorSprY + zoomOffset)/40; + + var minTime:Float = Conductor.getStepsInTime(minStep); + var maxTime:Float = Conductor.getStepsInTime(maxStep); + + var minBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(minTime)]; + var maxBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(maxTime)]; + + minBeat = Conductor.getTimeInBeats(minTime); + maxBeat = Conductor.getTimeInBeats(maxTime); + + minMeasure = minBpmChange.measureTime + (minBeat - minBpmChange.beatTime) / minBpmChange.beatsPerMeasure; + maxMeasure = maxBpmChange.measureTime + (maxBeat - maxBpmChange.beatTime) / maxBpmChange.beatsPerMeasure; + + //cap out the beats/measures at the end of the song + var endTime = Conductor.getStepsInTime(Charter.instance.__endStep); + var endBeat = Conductor.getTimeInBeats(endTime); + var endBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(endTime)]; + var endMeasure = endBpmChange.measureTime + (endBeat - endBpmChange.beatTime) / endBpmChange.beatsPerMeasure; + + if (maxBeat > endBeat) maxBeat = endBeat; + if (maxMeasure > endMeasure) maxMeasure = endMeasure; + if (minMeasure < 0) minMeasure = 0; + if (minBeat < 0) minBeat = 0; + + //only calculate if needed + if ((minBeat != lastMinBeat) || (maxBeat != lastMaxBeat) || (minMeasure != lastMinMeasure) || (maxMeasure != lastMaxMeasure) || lastConductorSprY == Math.NEGATIVE_INFINITY) { + calculateTimeSignatureGaps(); + calculateStepTimes(); + lastMinBeat = minBeat; + lastMaxBeat = maxBeat; + lastMinMeasure = minMeasure; + lastMaxMeasure = maxMeasure; + } + + lastConductorSprY = conductorSprY; } - public override function updateButton() { - camera.getViewRect(__rect); - UIState.state.updateRectButtonHandler(this, __rect, onHovered); + private inline function calculateTimeSignatureGaps() { + //for time signatures that start mid step + timeSignatureChangeGaps.splice(0, timeSignatureChangeGaps.length); + for (i => change in Conductor.bpmChangeMap) { + if (change.stepTime >= minStep && change.stepTime <= maxStep) { + //get step while ignoring the current change + var index:Int = CoolUtil.boundInt(i-1, 0, Conductor.bpmChangeMap.length - 1); + var step:Float = Conductor.getTimeWithBPMInSteps(change.songTime, index, Conductor.getTimeWithIndexInBPM(change.songTime, index)); + + if (Math.ceil(step) - step > 0 && (step - Math.floor(step)) > FlxMath.EPSILON) { //mid step change + timeSignatureChangeGaps.push(step); + } + } + } } - public override function draw() { - @:privateAccess - __lastDrawCameras = cameras.copy(); + private inline function calculateStepTimes() { + beatStepTimes.splice(0, beatStepTimes.length); + for (i in Math.floor(minBeat)...Math.ceil(maxBeat)) { + beatStepTimes.push(Conductor.getTimeInSteps(Conductor.getBeatsInTime(i))); + } + measureStepTimes.splice(0, measureStepTimes.length); + for (i in Math.floor(minMeasure)...Math.ceil(maxMeasure)) { + measureStepTimes.push(Conductor.getTimeInSteps(Conductor.getMeasuresInTime(i))); + } + } + + override public function draw() { + + //should only need to recalculate once per frame and will be shared across each instance + recalculateBeats(); + + drawTimeSignatureChangeGaps(); + + if (Options.charterShowBeats) drawBeats(); + if (Options.charterShowSections) drawMeasures(); + } + + private function drawBeats(offset:Float = 0.0) { + for (i in beatStepTimes) { + y = (i*40)+offset; + super.draw(); + } + } + private function drawMeasures(offset:Float = 0.0) { + for (i in measureStepTimes) { + y = (i*40)+offset; + super.draw(); + } + } + private function drawTimeSignatureChangeGaps() { + if (timeSignatureChangeGaps.length == 0) return; + var prevColor = color; + var prevBlend = blend; + + color = 0xFF888888; + blend = MULTIPLY; + + for (step in timeSignatureChangeGaps) { + y = step*40; + var diff = Math.ceil(step) - step; + scale.y = diff*40; + updateHitbox(); + + super.draw(); + } + + color = prevColor; + blend = prevBlend; + } +} + +class CharterGridSeperator extends CharterGridSeperatorBase { + override private function drawBeats(offset:Float = 0.0) { + scale.y = 2; + updateHitbox(); + super.drawBeats(-2); + } + override private function drawMeasures(offset:Float = 0.0) { + scale.y = 4; + updateHitbox(); + super.drawMeasures(-3); } } class EventBackdrop extends FlxBackdrop { - public var eventBeatSeparator:FlxBackdrop; - public var eventSecSeparator:FlxBackdrop; + public var eventBeatSeparator:CharterEventGridSeperator; public var topSeparator:FlxSprite; public var bottomSeparator:FlxSprite; - public function new() { + public var global:Bool = false; + + public function new(global:Bool) { super(Paths.image('editors/charter/events-grid'), Y, 0, 0); + this.global = flipX = flipY = global; alpha = 0.9; // Separators - eventSecSeparator = new FlxBackdrop(null, Y, 0, 0); - eventSecSeparator.y = -2; - eventSecSeparator.visible = Options.charterShowSections; - - eventBeatSeparator = new FlxBackdrop(null, Y, 0, 0); - eventBeatSeparator.y = -1; - eventBeatSeparator.visible = Options.charterShowBeats; - - for(sep in [eventSecSeparator, eventBeatSeparator]) { - sep.makeSolid(1, 1, -1); - sep.alpha = 0.5; - sep.scrollFactor.set(1, 1); - } - - eventSecSeparator.scale.set(20, 4); - eventSecSeparator.updateHitbox(); - - eventBeatSeparator.scale.set(10, 2); - eventBeatSeparator.updateHitbox(); + eventBeatSeparator = new CharterEventGridSeperator(); + eventBeatSeparator.makeSolid(1, 1, -1); + eventBeatSeparator.alpha = 0.5; + eventBeatSeparator.scrollFactor.set(1, 1); + eventBeatSeparator.global = global; bottomSeparator = new FlxSprite(0,-2); bottomSeparator.makeSolid(1, 1, -1); @@ -308,23 +485,34 @@ class EventBackdrop extends FlxBackdrop { public override function draw() { super.draw(); - eventSecSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1; - eventBeatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; - - eventSecSeparator.cameras = cameras; - eventSecSeparator.x = (x+width) - 20; - if (eventSecSeparator.visible) eventSecSeparator.draw(); - eventBeatSeparator.cameras = cameras; - eventBeatSeparator.x = (x+width) - 10; - if (eventBeatSeparator.visible) eventBeatSeparator.draw(); + eventBeatSeparator.xPos = global ? x : x+width; + eventBeatSeparator.draw(); - topSeparator.x = (x+width) - 20; + topSeparator.x = global ? x : (x+width) - 20; topSeparator.cameras = this.cameras; - if (!eventSecSeparator.visible) topSeparator.draw(); + if (!Options.charterShowSections) topSeparator.draw(); - bottomSeparator.x = (x+width) - 20; + bottomSeparator.x = global ? x : (x+width) - 20; bottomSeparator.cameras = this.cameras; bottomSeparator.draw(); } +} + +class CharterEventGridSeperator extends CharterGridSeperatorBase { + public var xPos:Float = 0.0; + public var global:Bool = false; + override private function drawBeats(offset:Float = 0.0) { + scale.set(10, 2); + updateHitbox(); + x = global ? xPos : xPos-10; + super.drawBeats(-2); + } + override private function drawMeasures(offset:Float = 0.0) { + scale.set(20, 4); + updateHitbox(); + x = global ? xPos : xPos-20; + super.drawMeasures(-3); + } + override private function drawTimeSignatureChangeGaps() {} } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterBookmarkCreation.hx b/source/funkin/editors/charter/CharterBookmarkCreation.hx new file mode 100644 index 00000000..a0a7ab19 --- /dev/null +++ b/source/funkin/editors/charter/CharterBookmarkCreation.hx @@ -0,0 +1,65 @@ +package funkin.editors.charter; + +import flixel.util.FlxColor; +import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartBookmark; + +class CharterBookmarkCreation extends UISubstateWindow { + public var saveButton:UIButton; + public var closeButton:UIButton; + public var textBox:UITextBox; + public var colorPicker:UICompactColorwheel; + + var callback:Bool->String->FlxColor->Float->Void; + + var bookmarkStep:Float = 0; + + override public function new(step:Float, close:Bool->String->FlxColor->Float->Void) + { + super(); + + winWidth = 320; + winHeight = 250; + winTitle = 'Creating New Bookmark'; + + callback = close; + + bookmarkStep = step; + } + + public override function create() { + FlxG.sound.music.pause(); + Charter.instance.vocals.pause(); + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + super.create(); + + textBox = new UITextBox(windowSpr.x + 20, windowSpr.y + 70, "New Bookmark", 276); + textBox.antialiasing = true; + add(textBox); + addLabelOn(textBox, "Bookmark Name:"); + + colorPicker = new UICompactColorwheel(textBox.x, textBox.y + textBox.bHeight + 34, flixel.util.FlxColor.RED); + add(colorPicker); + addLabelOn(colorPicker, "Bookmark Color:"); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + callback(true, textBox.label.text, colorPicker.curColor, bookmarkStep); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + callback(false, "", FlxColor.TRANSPARENT, -1); + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterBookmarkList.hx b/source/funkin/editors/charter/CharterBookmarkList.hx new file mode 100644 index 00000000..032177db --- /dev/null +++ b/source/funkin/editors/charter/CharterBookmarkList.hx @@ -0,0 +1,95 @@ +package funkin.editors.charter; + +import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartBookmark; + +class CharterBookmarkList extends UISubstateWindow { + public var bookmarkList:UIButtonList<BookmarkButton>; + public var saveButton:UIButton; + public var closeButton:UIButton; + + public override function create() { + FlxG.sound.music.pause(); + Charter.instance.vocals.pause(); + + winTitle = 'Bookmark List Editor'; + winWidth = 380; + winHeight = 390; + + super.create(); + + var title:UIText; + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Bookmarks", 28)); + + bookmarkList = new UIButtonList<BookmarkButton>(20, title.y + title.height + 10, winWidth - 40, 342 - 85 - 16, null, FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0); + bookmarkList.cameraSpacing = 0; + + for (b in Charter.instance.getBookmarkList()) + bookmarkList.add(new BookmarkButton(0, 0, b, bookmarkList)); + + add(bookmarkList); + bookmarkList.frames = Paths.getFrames('editors/ui/inputbox'); + + bookmarkList.content.remove(bookmarkList.addButton); //i dont need it + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveList(); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + } + + public function saveList() { + var oldList:Array<ChartBookmark> = Charter.instance.getBookmarkList(); + var newList:Array<ChartBookmark> = [for (btn in bookmarkList.buttons.members) {time: btn.bookmark.time, name: btn.textBox.label.text, color: btn.bookmark.color}]; + + PlayState.SONG.bookmarks = newList; + Charter.instance.updateBookmarks(); + Charter.undos.addToUndo(CEditBookmarks(oldList, newList)); + } +} + +class BookmarkButton extends UIButton { + public var textBox:UITextBox; + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + public var bookmark:ChartBookmark; + + public function new(x:Float, y:Float, bookmark:ChartBookmark, parent:UIButtonList<BookmarkButton>) { + super(x, y, '', null, Std.int(parent.buttonSize.x), Std.int(parent.buttonSize.y)); + this.bookmark = bookmark; + autoAlpha = false; + + members.push(textBox = new UITextBox(10, 10, bookmark.name, 276)); + textBox.antialiasing = true; + + deleteButton = new UIButton(textBox.x + textBox.label.width + 10, textBox.y, null, function () { + parent.remove(this); + }, 32); + deleteButton.color = 0xFFFF0000; + deleteButton.autoAlpha = false; + members.push(deleteButton); + + deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button')); + deleteIcon.antialiasing = false; + members.push(deleteIcon); + } + + override function update(elapsed:Float) { + textBox.y = (y + bHeight/2) - (textBox.bHeight/2); + deleteButton.x = textBox.x + textBox.bWidth + 14; deleteButton.y = textBox.y; + deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8; + + deleteButton.selectable = selectable; + deleteButton.shouldPress = shouldPress; + + super.update(elapsed); + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterDeleteAnim.hx b/source/funkin/editors/charter/CharterDeleteAnim.hx index 88bde691..2e23d38a 100644 --- a/source/funkin/editors/charter/CharterDeleteAnim.hx +++ b/source/funkin/editors/charter/CharterDeleteAnim.hx @@ -11,7 +11,7 @@ class CharterDeleteAnim extends CharterNote { super(); sustainSpr.color = 0xFF630000; color = 0xFF605A5A; - snappedToStrumline = selectable = autoAlpha = false; + snappedToGrid = selectable = autoAlpha = false; @:privateAccess __animSpeed = 1.25; garbageIcon = new FlxSprite().loadGraphic(Paths.image("editors/deleter")); @@ -65,12 +65,17 @@ class CharterDeleteAnim extends CharterNote { sustainSpr.updateHitbox(); sustainSpr.follow(this, 15, 20); sustainSpr.exists = deleteData.note.susLength != 0; sustainSpr.alpha = .8; - typeText.text = Std.string(deleteData.note.type); - typeText.exists = deleteData.note.type != 0; typeText.alpha = .4; - typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); + type = deleteData.note.type; + typeAlpha = .4; + //typeText.text = Std.string(deleteData.note.type); + //typeText.exists = deleteData.note.type != 0; typeAlpha = .4; + //typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); - for (member in [this, sustainSpr, typeText]) - member.alpha *= FlxEase.quadInOut(deleteData.time/deleteTime); + var mult = FlxEase.quadInOut(deleteData.time/deleteTime); + + for (member in [this, sustainSpr]) + member.alpha *= mult; + typeAlpha *= mult; super.draw(); } diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index c7092527..05b2a7e9 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,13 +1,21 @@ package funkin.editors.charter; +import openfl.display.BitmapData; +import openfl.geom.ColorTransform; +import flixel.group.FlxSpriteGroup; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxGraphicAsset; import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.scripting.DummyScript; +import funkin.backend.scripting.Script; +import funkin.backend.system.Conductor; import funkin.editors.charter.Charter.ICharterSelectable; import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; import funkin.game.Character; import funkin.game.HealthIcon; +using flixel.util.FlxColorTransformUtil; + class CharterEvent extends UISliceSprite implements ICharterSelectable { public var events:Array<ChartEvent>; public var step:Float; @@ -19,51 +27,138 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { public var eventsBackdrop:EventBackdrop; public var snappedToGrid:Bool = true; - public function new(step:Float, ?events:Array<ChartEvent>) { + public var displayGlobal:Bool = false; + public var global(default, set):Bool = false; + private function set_global(val:Bool) { + for (event in events) event.global = val; + return global = val; + } + + public function new(step:Float, ?events:Array<ChartEvent>, ?global:Bool) { super(-100, (step * 40) - 17, 100, 34, 'editors/charter/event-spr'); this.step = step; this.events = events.getDefault([]); - cursor = BUTTON; + this.global = displayGlobal = (global == null ? events[0] != null && events[0].global == true : global); + this.color = displayGlobal ? 0xffc8bd23 : 0xFFFFFFFF; + + cursor = CLICK; } public override function update(elapsed:Float) { super.update(elapsed); - if (snappedToGrid && eventsBackdrop != null) - x = eventsBackdrop.x + eventsBackdrop.width - (bWidth = 37 + (icons.length * 22)); + if (snappedToGrid && eventsBackdrop != null) { + bWidth = 37 + (icons.length * 22); + x = eventsBackdrop.x + (global ? 0 : eventsBackdrop.width - bWidth); + } for(k=>i in icons) { i.follow(this, (k * 22) + 30 - (i.width / 2), (bHeight - i.height) / 2); } - colorTransform.redMultiplier = colorTransform.greenMultiplier = colorTransform.blueMultiplier = selected ? 0.75 : 1; - colorTransform.redOffset = colorTransform.greenOffset = selected ? 96 : 0; - colorTransform.blueOffset = selected ? 168 : 0; + @:bypassAccessor color = CoolUtil.lerpColor(this.color, displayGlobal ? 0xffc8bd23 : 0xFFFFFFFF, 1/3); + colorTransform.setMultipliers(color.redFloat, color.greenFloat, color.blueFloat, alpha); + colorTransform.setOffsets(0, 0, 0, 0); + selectedColorTransform(colorTransform); + useColorTransform = true; - for (sprite in icons) - sprite.colorTransform = colorTransform; + for (sprite in icons) @:privateAccess { + sprite.colorTransform.__identity(); + selectedColorTransform(sprite.colorTransform); + } + + flipX = displayGlobal; } + @:noCompletion private inline function selectedColorTransform(transform:ColorTransform) { + transform.redMultiplier *= selected ? 0.75 : 1; + transform.greenMultiplier *= selected ? 0.75 : 1; + transform.blueMultiplier *= selected ? 0.75 : 1; + + transform.redOffset += selected ? 96 : 0; + transform.greenOffset += selected ? 96 : 0; + transform.blueOffset += selected ? 168 : 0; + } + + /** + * Pack data is a list of 4 strings separated by `________PACKSEP________` + * [0] Event Name + * [1] Event Script + * [2] Event JSON Info + * [3] Event Icon + * [4] Event UI Script / Icon Script + **/ + @:dox(hide) public static function getPackData(name:String):Array<String> { + var packFile = Paths.pack('events/${name}'); + if (Assets.exists(packFile)) { + return Assets.getText(packFile).split('________PACKSEP________'); + } + return null; + } + + @:dox(hide) public static function getUIScript(event:ChartEvent, caller:String):Script { + var uiScript = Paths.script('data/events/${event.name}.ui'); + var script:Script = null; + if(Assets.exists(uiScript)) { + script = Script.create(uiScript); + } else { + var packData = getPackData(event.name); + if(packData != null) { + var scriptFile = packData[4]; + if(scriptFile != null) { + script = Script.fromString(scriptFile, uiScript); + } + } + } + + if(script != null && !(script is DummyScript)) { + // classes and functions + script.set("EventIconGroup", EventIconGroup); // automatically imported + script.set("EventNumber", EventNumber); // automatically imported + script.set("getIconFromStrumline", getIconFromStrumline); + script.set("getIconFromCharName", getIconFromCharName); + script.set("generateDefaultIcon", generateDefaultIcon); + script.set("generateEventIconDurationArrow", generateEventIconDurationArrow); + script.set("generateEventIconNumbers", generateEventIconNumbers); + script.set("generateEventIconWarning", generateEventIconWarning); + script.set("getPackData", getPackData); + script.set("getEventComponent", getEventComponent); + // data + script.set("event", event); + script.set("caller", caller); + + script.load(); + } + + return script; + } + + /** + * Generates the default event icon for the wanted event + * @param name The name of the event + * @return The icon + **/ private static function generateDefaultIcon(name:String) { var isBase64:Bool = false; var path:String = Paths.image('editors/charter/event-icons/$name'); var defaultPath = Paths.image('editors/charter/event-icons/Unknown'); - if (!Assets.exists(path)) path = defaultPath; + if(!Assets.exists(path)) { + path = defaultPath; - var packPath = Paths.pack('events/$name'); - if (Assets.exists(packPath)) { - var packText = Assets.getText(packPath).split('________PACKSEP________'); - var packImg = packText[3]; - if(packImg != null && packImg.length > 0) { - isBase64 = !packImg.startsWith("assets/"); - path = packImg; + var packData = getPackData(name); + if(packData != null) { + var packImg = packData[3]; + if(packImg != null && packImg.length > 0) { + isBase64 = !packImg.startsWith("assets/"); + path = packImg; + } } } path = path.trim(); var graphic:FlxGraphicAsset = try { - isBase64 ? openfl.display.BitmapData.fromBase64(path, 'UTF8') : path; + isBase64 ? BitmapData.fromBase64(path, 'UTF8') : path; } catch(e:Dynamic) { Logs.trace('Failed to load event icon: ${e.toString()}', ERROR); isBase64 = false; @@ -75,27 +170,176 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { graphic = defaultPath; } - return new FlxSprite().loadGraphic(graphic); + return new FlxSprite(graphic); } - public static function generateEventIcon(event:ChartEvent) { - return switch(event.name) { - default: - generateDefaultIcon(event.name); - case "Camera Movement": - // custom icon for camera movement - var state:Charter = cast FlxG.state; - if (event.params != null && event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) { - // camera movement, use health icon - var icon = Character.getIconFromCharName(state.strumLines.members[event.params[0]].strumLine.characters[0]); - var healthIcon = new HealthIcon(icon); - healthIcon.setUnstretchedGraphicSize(32, 32, false); - healthIcon.scrollFactor.set(1, 1); - healthIcon.active = false; - healthIcon; - } else - generateDefaultIcon(event.name); + /** + * Gets a component sprite from the editors/charter/event-icons/components folder + * If you wanna use a number, please use the EventNumber class instead + * @param type The type of component to get + * @param x The x position of the sprite (optional) + * @param y The y position of the sprite (optional) + * @return The component sprite + **/ + public static function getEventComponent(type:String, x:Float = 0.0, y:Float = 0.0) { + var componentPath = Paths.image("editors/charter/event-icons/components/" + type); + if (Assets.exists(componentPath)) return new FlxSprite(x, y, componentPath); + + Logs.trace('Could not find component $type', WARNING); + return null; + } + + /** + * Expected to be called from inside of a ui script, + * calling this elsewhere might cause unexpected results or crashes + **/ + public static function getIconFromStrumline(index:Null<Int>) { + var state = cast(FlxG.state, Charter); + if (index != null && index >= 0 && index < state.strumLines.length) { + return getIconFromCharName(state.strumLines.members[index].strumLine.characters[0]); } + return null; + } + + public static function getIconFromCharName(name:String) { + var icon = Character.getIconFromCharName(name); + var healthIcon = new HealthIcon(icon); + CoolUtil.setUnstretchedGraphicSize(healthIcon, 32, 32, false); + healthIcon.scrollFactor.set(1, 1); + healthIcon.active = false; + return healthIcon; + } + + public static function generateEventIcon(event:ChartEvent, inMenu:Bool = true):FlxSprite { + var script = getUIScript(event, "event-icon"); + if(script != null && !(script is DummyScript)) { + script.set("inMenu", inMenu); + if(script.get("generateIcon") != null) { + var res:FlxSprite = script.call("generateIcon"); + if(res != null) + return res; + } + } + + switch(event.name) { + case "Time Signature Change": + if(event.params != null && (event.params[0] >= 0 || event.params[1] >= 0)) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + group.add({ // top + var num = new EventNumber(9, -1, event.params[0], EventNumber.ALIGN_CENTER); + num.active = false; + num; + }); + group.add({ // bottom + var num = new EventNumber(9, 10, event.params[1], EventNumber.ALIGN_CENTER); + num.active = false; + num; + }); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + case "Continuous BPM Change": + if(event.params != null && event.params[1] != null) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon("BPM Change Start")); + if (!inMenu) { + generateEventIconDurationArrow(group, event.params[1]); + group.members[0].y -= 2; + generateEventIconNumbers(group, event.params[0], 3); + } + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } else { + return generateDefaultIcon("BPM Change Start"); + } + case "BPM Change": + if(event.params != null && event.params[0] != null) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + if (!inMenu) { + group.members[0].y -= 2; + generateEventIconNumbers(group, event.params[0], 3); + } + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + + case "Scroll Speed Change": + if(event.params != null && !inMenu) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + if (event.params[0]) generateEventIconDurationArrow(group, event.params[2]); + group.members[0].y -= 2; + generateEventIconNumbers(group, event.params[1]); + return group; + } + + case "Camera Movement": + // camera movement, use health icon + if(event.params != null) { + var icon = getIconFromStrumline(event.params[0]); + if(icon != null) return icon; + } + } + return generateDefaultIcon(event.name); + } + + private static function generateEventIconNumbers(group:EventIconGroup, number:Float, x:Float = 4, y:Float = 15, spacing:Float = 5, precision:Int = 3) { + group.add({ + var num = new EventNumber(x, y, number, EventNumber.ALIGN_CENTER, spacing, precision); + if (num.numWidth > 20) { + num.scale.x = num.scale.y = 20 / num.numWidth; + } + num.active = false; + num; + }); + } + + private static function generateEventIconDurationArrow(group:EventIconGroup, stepDuration:Float) { + //var group = new EventIconGroup(); + //group.add(generateDefaultIcon(startIcon)); + + var xOffset = 4; + var yGap = 24; + var endGap = 2; + + if (stepDuration >= 0.55) { //min time for showing arrow + var tail = new FlxSprite(xOffset, yGap); + var arrow = new FlxSprite(xOffset, (stepDuration * 40) + endGap); + var arrowSegment = new FlxSprite(xOffset, yGap); + tail.frames = arrow.frames = arrowSegment.frames = Paths.getSparrowAtlas("editors/charter/event-icons/components/arrow-down"); + + group.add({ + tail.animation.addByPrefix("tail", "tail"); + tail.animation.play("tail"); + tail; + }); + + group.add({ + arrowSegment.animation.addByPrefix("segment", "segment"); + arrowSegment.animation.play("segment"); + arrowSegment.scale.y = endGap + (stepDuration * 40) - (tail.height + yGap); + arrowSegment.updateHitbox(); + arrowSegment.y += tail.height; + arrowSegment; + }); + + group.add({ + arrow.animation.addByPrefix("arrow", "arrow"); + arrow.animation.play("arrow"); + arrow; + }); + } + } + + private static function generateEventIconWarning(group:EventIconGroup) { + for (spr in group) { + spr.colorTransform.redMultiplier = spr.colorTransform.greenMultiplier = spr.colorTransform.blueMultiplier = 0.5; + spr.colorTransform.redOffset = 100; + } + group.add(getEventComponent("warning", 16, -8)); + group.copyColorTransformToChildren = false; } public override function onHovered() { @@ -111,7 +355,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } public function handleDrag(change:FlxPoint) { - var newStep:Float = step = FlxMath.bound(step + change.x, 0, Charter.instance.__endStep-1); + var newStep:Float = step = CoolUtil.bound(step + change.x, 0, Charter.instance.__endStep-1); y = ((newStep) * 40) - 17; } @@ -123,18 +367,152 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } for(event in events) { - var spr = generateEventIcon(event); + var spr = generateEventIcon(event, false); icons.push(spr); members.push(spr); } draggable = true; - for (event in events) - if (event.name == "BPM Change") { - draggable = false; - break; + + bWidth = 37 + (icons.length * 22); + x = (snappedToGrid && eventsBackdrop != null && global ? eventsBackdrop.x - bWidth : (global ? 0 : -bWidth)); + } +} + +class EventIconGroup extends FlxSpriteGroup { + public var forceWidth:Float = 16; + public var forceHeight:Float = 16; + public var dontTransformChildren:Bool = false; + public var copyColorTransformToChildren:Bool = true; + + public function new() { + super(); + colorTransform = new ColorTransform(); + scrollFactor.set(1, 1); + } + + override function preAdd(sprite:FlxSprite):Void + { + super.preAdd(sprite); + sprite.scrollFactor.set(1, 1); + } + + public override function transformChildren<V>(Function:FlxSprite->V->Void, Value:V):Void + { + if (dontTransformChildren) + return; + + super.transformChildren(Function, Value); + } + + override function set_x(Value:Float):Float + { + if (exists && x != Value) + transformChildren(xTransform, Value - x); // offset + return x = Value; + } + + override function set_y(Value:Float):Float + { + if (exists && y != Value) + transformChildren(yTransform, Value - y); // offset + return y = Value; + } + + override function get_width() { + return forceWidth; + } + override function get_height() { + return forceHeight; + } + + override public function draw() { + @:privateAccess + if (copyColorTransformToChildren && colorTransform != null) for (child in members) child.colorTransform.__copyFrom(colorTransform); + super.draw(); + } + + override function update(elapsed:Float) { + super.update(elapsed); + } +} + +class EventNumber extends FlxSprite { + public static inline final ALIGN_NORMAL:Int = 0; + public static inline final ALIGN_CENTER:Int = 1; + + public var digits:Array<Int> = []; + public static inline final FRAME_POINT:Int = 10; + public static inline final FRAME_NEGATIVE:Int = 11; + + public var align:Int = ALIGN_NORMAL; + public var spacing:Float = 6; + + public function new(x:Float, y:Float, number:Float, ?align:Int, spacing:Float = 6, precision:Int = 3) { + super(x, y); + this.digits = []; + this.align = align == null ? ALIGN_NORMAL : align; + this.spacing = spacing; + + if (number == 0) { + this.digits.insert(0, 0); + } + else { + var decimals:Float = FlxMath.roundDecimal(Math.abs(number % 1), precision); + if (decimals > 0) this.digits.insert(0, FRAME_POINT); + while(decimals > 0) { + this.digits.push(Math.floor(decimals * 10)); + decimals = FlxMath.roundDecimal((decimals * 10) % 1, precision); } - x = (snappedToGrid && eventsBackdrop != null ? eventsBackdrop.x : 0) - (bWidth = 37 + (icons.length * 22)); + var ints = Std.int(Math.abs(number)); + if (ints == 0) this.digits.insert(0, 0); + while (ints > 0) { + this.digits.insert(0, ints % 10); + ints = Std.int(ints / 10); + } + + if (number < 0) { + this.digits.insert(0, FRAME_NEGATIVE); + } + } + + loadGraphic(Paths.image('editors/charter/event-icons/components/eventNums'), true, 6, 7); + } + + override function update(elapsed:Float) { + super.update(elapsed); + } + + override function draw() { + var baseX = x; + var offsetX = 0.0; + if(align == ALIGN_CENTER) offsetX = -(digits.length - 1) * spacing * Math.abs(scale.x) / 2; + + x = baseX + offsetX; + for (i in 0...digits.length) { + frame = frames.frames[digits[i]]; + super.draw(); + x += spacing * Math.abs(scale.x); + } + x = baseX; + } + + public var numWidth(get, never):Float; + private function get_numWidth():Float { + return Math.abs(scale.x) * spacing * digits.length; + } + public var numHeight(get, never):Float; + private function get_numHeight():Float { + return Math.abs(scale.y) * frameHeight; + } + + public override function updateHitbox():Void { + var numWidth = this.numWidth; + var numHeight = this.numHeight; + width = numWidth; + height = numHeight; + offset.set(-0.5 * (numWidth - spacing * digits.length), -0.5 * (numHeight - frameHeight)); + centerOrigin(); } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEventAdd.hx b/source/funkin/editors/charter/CharterEventAdd.hx index 7fc74226..d3974c96 100644 --- a/source/funkin/editors/charter/CharterEventAdd.hx +++ b/source/funkin/editors/charter/CharterEventAdd.hx @@ -7,31 +7,42 @@ class CharterEventAdd extends UISliceSprite { public var curCharterEvent:CharterEvent = null; - public function new() { + public var sideText:UIText; + public var global:Bool = false; + + public function new(global:Bool) { super(0, 0, 100, 34, 'editors/charter/event-spr-add'); + this.global = flipX = global; + + sideText = new UIText(0, -40, 0, global ? "Global Event" : "Local Event", 12); + sideText.alignment = "center"; sideText.alpha = 0.75; + text = new UIText(0, 0, 0, ""); members.push(text); - cursor = BUTTON; + cursor = CLICK; } public override function onHovered() { super.onHovered(); - if (FlxG.mouse.justReleased) { + if (FlxG.mouse.justReleased && FlxG.state.subState == null) { if (curCharterEvent != null) - Charter.instance.openSubState(new CharterEventScreen(step, curCharterEvent)); + Charter.instance.openSubState(new CharterEventScreenNew(curCharterEvent)); else - Charter.instance.openSubState(new CharterEventScreen(step)); + Charter.instance.openSubState(new CharterEventScreen(step, global)); } } public override function update(elapsed:Float) { super.update(elapsed); - text.follow(this, 20, (bHeight - text.height) / 2); + text.follow(this, global ? bWidth - text.width - (text.text == "Add event" ? 15 : 20) : 20, (bHeight - text.height) / 2); + sideText.follow(this, (bWidth/2) - (sideText.fieldWidth/2), -(sideText.height + 2)); alpha = sprAlpha * 0.75; text.alpha = sprAlpha; + + sideText.alpha = sprAlpha; } public function updatePos(step:Float) { @@ -39,15 +50,15 @@ class CharterEventAdd extends UISliceSprite { this.step = step; this.y = (step * 40) - (bHeight / 2); text.text = TU.translate("charter.addEvent"); - framesOffset = 0; - x = -(bWidth = 37 + Math.ceil(text.width)); + framesOffset = 0; bWidth = 37 + Math.ceil(text.width); + x = global ? Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].x + (40*Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].keyCount) : -(bWidth); } public function updateEdit(event:CharterEvent) { curCharterEvent = event; this.y = event.y; text.text = TU.translate("charter.editEvent"); - framesOffset = 9; - x = -(bWidth = 27 + Math.ceil(text.width) + event.bWidth); + framesOffset = 9; bWidth = 27 + Math.ceil(text.width) + event.bWidth; + x = global ? Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].x + (40*Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].keyCount) : -(bWidth); } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEventGroup.hx b/source/funkin/editors/charter/CharterEventGroup.hx index b81ee554..d7be0864 100644 --- a/source/funkin/editors/charter/CharterEventGroup.hx +++ b/source/funkin/editors/charter/CharterEventGroup.hx @@ -5,6 +5,7 @@ import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; class CharterEventGroup extends FlxTypedGroup<CharterEvent> { public var eventsBackdrop:EventBackdrop; + public var eventsRowText:UIText; public var autoSort:Bool = true; var __lastSort:Int = 0; @@ -14,6 +15,7 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> { if (autoSort && members.length != __lastSort) sortEvents(); + eventsRowText.y = FlxMath.lerp(eventsRowText.y, -40 + (members[0] != null ? Math.min(members[0].y, 0) : 0), 1/20); super.update(elapsed); } @@ -26,21 +28,25 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> { for (event in members) { event.eventsBackdrop = eventsBackdrop; event.snappedToGrid = true; + event.cameras = cameras; } super.draw(); } public inline function sortEvents() { __lastSort = members.length; - this.sort(function(i, e1, e2) { - return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step); - }); + this.sort(sortEventsFilter); updateEventsIDs(); + updateEventsBackdrop(); } public inline function updateEventsIDs() for (i => n in members) n.ID = i; + public inline function updateEventsBackdrop() + for (event in members) + event.eventsBackdrop = eventsBackdrop; + public inline function filterEvents() { for (event in members) if (event.events.length == 0) { @@ -48,4 +54,7 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> { event.kill(); } } + + public dynamic function sortEventsFilter(i:Int, e1:CharterEvent, e2:CharterEvent) + return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step); } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index ab9b0fb8..a6cb28b8 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -26,15 +26,17 @@ class CharterEventScreen extends UISubstateWindow { public var saveButton:UIButton; public var closeButton:UIButton; - public function new(step:Float, ?chartEvent:Null<CharterEvent>) { + public var global:Bool = false; + + public function new(step:Float, global:Bool, ?chartEvent:Null<CharterEvent>) { if (chartEvent != null) this.chartEvent = chartEvent; - this.step = step; + this.step = step; this.global = global; super(); } public override function create() { var creatingEvent:Bool = chartEvent == null; - if (creatingEvent) chartEvent = new CharterEvent(step, []); + if (creatingEvent) chartEvent = new CharterEvent(step, [], global); winTitle = TU.translate("charterEventScreen.title" + (creatingEvent ? "-creating" : "-editing")); winWidth = 960; @@ -47,7 +49,7 @@ class CharterEventScreen extends UISubstateWindow { events = chartEvent.events.copy(); - eventsList = new UIButtonList<EventButton>(0,0,75, 570, "", FlxPoint.get(73, 40), null, 0); + eventsList = new UIButtonList<EventButton>(0,0,75, 570, null, FlxPoint.get(73, 40), null, 0); eventsList.drawTop = false; eventsList.addButton.callback = () -> openSubState(new CharterEventTypeSelection(function(eventName) { events.push({ @@ -57,7 +59,7 @@ class CharterEventScreen extends UISubstateWindow { }); eventsList.add(new EventButton(events[events.length-1], CharterEvent.generateEventIcon(events[events.length-1]), events.length-1, this, eventsList)); changeTab(events.length-1); - })); + }, chartEvent.step)); for (k=>i in events) eventsList.add(new EventButton(i, CharterEvent.generateEventIcon(i), k, this, eventsList)); add(eventsList); @@ -69,21 +71,20 @@ class CharterEventScreen extends UISubstateWindow { saveCurTab(); chartEvent.refreshEventIcons(); + Charter.instance.updateBPMEvents(); + if (events.length <= 0 && !creatingEvent) Charter.instance.deleteSelection([chartEvent]); else if (events.length > 0) { var oldEvents:Array<ChartEvent> = chartEvent.events.copy(); - chartEvent.events = [ - for (i in eventsList.buttons.members) i.event - ]; + chartEvent.events = [for (i in eventsList.buttons.members) i.event]; + chartEvent.global = global; + + chartEvent.refreshEventIcons(); if (creatingEvent && events.length > 0) Charter.instance.createSelection([chartEvent]); else { - chartEvent.events = [for (i in eventsList.buttons.members) i.event]; - chartEvent.refreshEventIcons(); - Charter.instance.updateBPMEvents(); - Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); } } @@ -214,11 +215,11 @@ class CharterEventScreen extends UISubstateWindow { public function saveCurTab() { if (curEvent < 0) return; - events[curEvent].params = [ - for(p in paramsFields) { + var dataParams = EventsData.getEventParams(events[curEvent].name); + var params:Array<Dynamic> = [ + for (i => p in paramsFields) { if (p is UIDropDown) { - var dataParams = EventsData.getEventParams(events[curEvent].name); - if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index; + if (dataParams[i].type == TStrumLine) cast(p, UIDropDown).index; else cast(p, UIDropDown).label.text; } else if (p is UINumericStepper) { @@ -239,6 +240,14 @@ class CharterEventScreen extends UISubstateWindow { null; } ]; + + while(dataParams.length > 0 && { + var index = params.length - 1; + var dataParam = dataParams[index]; + dataParam.saveIfDefault == false && params[index] == dataParam.defValue; + }) params.pop(); + + events[curEvent].params = params; } } @@ -251,17 +260,17 @@ class EventButton extends UIButton { public function new(event:ChartEvent, icon:FlxSprite, id:Int, substate:CharterEventScreen, parent:UIButtonList<EventButton>) { this.icon = icon; this.event = event; - super(0,0,"" ,function() { + super(0, 0, null, function() { substate.changeTab(id); for(i in parent.buttons.members) i.alpha = i == this ? 1 : 0.25; - },73,40); + }, 73, 40); autoAlpha = false; members.push(icon); icon.setPosition(18 - icon.width / 2, 20 - icon.height / 2); - deleteButton = new UIButton(bWidth - 30, y + (bHeight - 26) / 2, "", function () { + deleteButton = new UIButton(bWidth - 30, y + (bHeight - 26) / 2, null, function () { substate.events.splice(id, 1); substate.changeTab(id, false); parent.remove(this); diff --git a/source/funkin/editors/charter/CharterEventScreenNew.hx b/source/funkin/editors/charter/CharterEventScreenNew.hx new file mode 100644 index 00000000..2ea20ce6 --- /dev/null +++ b/source/funkin/editors/charter/CharterEventScreenNew.hx @@ -0,0 +1,381 @@ +package funkin.editors.charter; + +import flixel.group.FlxGroup; +import flixel.util.FlxColor; +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.chart.EventsData; +import funkin.backend.system.Conductor; +import funkin.game.Character; +import funkin.game.Stage; + +using StringTools; + +class CharterEventScreenNew extends MusicBeatSubstate { + public var cam:FlxCamera; + public var eventCam:FlxCamera; + public var chartEvent:CharterEvent; + + public var events:Array<ChartEvent> = []; + public var eventsList:UIButtonList<EventButtonNew>; + + public var eventName:UIText; + + public var paramsPanel:FlxGroup; + public var paramsFields:Array<FlxBasic> = []; + + public var downIndicator:UIText; + + public var bWidth:Float; + + var bg:UISliceSprite; + + public function new(?chartEvent:Null<CharterEvent>) { + if (chartEvent != null) this.chartEvent = chartEvent; + super(); + } + + public override function create() { + super.create(); + + FlxG.sound.music.pause(); // prevent the song from continuing + Charter.instance.vocals.pause(); + for (strumLine in Charter.instance.strumLines.members) strumLine.vocals.pause(); + + events = chartEvent.events.copy(); + + FlxG.state.persistentUpdate = true; + + camera = cam = new FlxCamera(); + cam.bgColor = 0; + + eventCam = new FlxCamera(); + eventCam.bgColor = 0; + + FlxG.cameras.add(cam, false); + FlxG.cameras.add(eventCam, false); + + bg = new UISliceSprite(0, 0, 0, 0, 'editors/ui/inputbox'); + bg.alpha = 0.75; + bg.cameras = [cam]; + bg.setPosition(0, 0); + add(bg); + + paramsPanel = new FlxGroup(); + paramsPanel.cameras = [eventCam]; + add(paramsPanel); + + eventName = new UIText(0, 0, 0, "", 24); + eventName.cameras = [eventCam]; + add(eventName); + + downIndicator = new UIText(0, 0, 0, "↓", 16); + downIndicator.cameras = [eventCam]; + downIndicator.scrollFactor.set(); + downIndicator.borderSize = 2; + downIndicator.alpha = 0; + add(downIndicator); + + eventsList = new UIButtonList<EventButtonNew>(10, 10, 75, 570, null, FlxPoint.get(75, 40), null, 0); + eventsList.alpha = 0; + eventsList.cameras = [cam]; + eventsList.addButton.callback = () -> { + __ignoreLastClick = true; // Stop closing >:D + openSubState(new CharterEventTypeSelection(function(eventName) { + events.push({ + time: Conductor.getTimeForStep(chartEvent.step), + params: [], + name: eventName + }); + eventsList.add(new EventButtonNew(events[events.length-1], CharterEvent.generateEventIcon(events[events.length-1]), events.length-1, this, eventsList)); + changeTab(events.length-1); + }, chartEvent.step)); + }; + for (k=>i in events) + eventsList.add(new EventButtonNew(i, CharterEvent.generateEventIcon(i), k, this, eventsList)); + add(eventsList); + + // this took forever to find out omg >:D -lunar + eventsList.cameraSpacing = 0; eventsList.topHeight = 0; + + changeTab(0); + boundWindow(); + } + + public var curEvent:Int = -1; + public var winHeight:Float = 0; + + public function changeTab(id:Int, save:Bool = true) { + if (save) + saveCurTab(); + + // destroy old elements + paramsFields = []; + for(e in paramsPanel) { + e.destroy(); + paramsPanel.remove(e); + } + + winHeight = eventName.y + eventName.height + 6; + eventCam.scroll.y = 0; + + if (id >= 0 && id < events.length) { + curEvent = id; + var curEvent = events[curEvent]; + eventName.text = curEvent.name; + bWidth = eventName.x + eventName.width+16; + + // add new elements + for(k=>param in EventsData.getEventParams(curEvent.name)) { + function addLabel() { + var label:UIText = new UIText(eventName.x+6, winHeight, 0, param.name); + winHeight += label.height + 4; + paramsPanel.add(label); + + bWidth = Math.max(bWidth, label.x+label.width+10); + }; + + var value:Dynamic = CoolUtil.getDefault(curEvent.params[k], param.defValue); + var lastAdded = switch(param.type) { + case TString: + addLabel(); + var textBox:UITextBox = new UITextBox(eventName.x+6, winHeight, cast value); + paramsPanel.add(textBox); paramsFields.push(textBox); + textBox; + case TBool: + winHeight += 2; + var checkbox = new UICheckbox(eventName.x+8, winHeight, param.name, cast value); + paramsPanel.add(checkbox); paramsFields.push(checkbox); + checkbox; + case TInt(min, max, step): + addLabel(); + var numericStepper = new UINumericStepper(eventName.x+6, winHeight, cast value, step.getDefault(1), 0, min, max); + paramsPanel.add(numericStepper); paramsFields.push(numericStepper); + numericStepper; + case TFloat(min, max, step, precision): + addLabel(); + var numericStepper = new UINumericStepper(eventName.x+6, winHeight, cast value, step.getDefault(1), precision, min, max); + paramsPanel.add(numericStepper); paramsFields.push(numericStepper); + numericStepper; + case TStrumLine: + addLabel(); + var dropdown = new UIDropDown(eventName.x+6, winHeight, 320, 32, [for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})'], cast value); + paramsPanel.add(dropdown); paramsFields.push(dropdown); + dropdown; + case TColorWheel: + addLabel(); + var colorWheel = new UIColorwheel(eventName.x+6, winHeight, value is String ? FlxColor.fromString(value) : Std.int(value)); + paramsPanel.add(colorWheel); paramsFields.push(colorWheel); + colorWheel; + case TDropDown(options): + addLabel(); + var optionIndex = options.indexOf(cast value); + if(optionIndex < 0) { + optionIndex = 0; + } + var dropdown = new UIDropDown(eventName.x+6, winHeight, 320, 32, options, optionIndex); + paramsPanel.add(dropdown); paramsFields.push(dropdown); + dropdown; + case TCharacter: + addLabel(); + var charFileList = Character.getList(false); + var textBox:UIAutoCompleteTextBox = new UIAutoCompleteTextBox(eventName.x+6, winHeight, cast value); + textBox.suggestItems = charFileList; + paramsPanel.add(textBox); paramsFields.push(textBox); + textBox; + case TStage: + addLabel(); + var stageFileList = Stage.getList(false); + var textBox:UIAutoCompleteTextBox = new UIAutoCompleteTextBox(eventName.x+6, winHeight, cast value); + textBox.suggestItems = stageFileList; + paramsPanel.add(textBox); paramsFields.push(textBox); + textBox; + default: + paramsFields.push(null); + null; + } + if (lastAdded is UISliceSprite) { + winHeight += cast(lastAdded, UISliceSprite).bHeight + 4; + bWidth = Math.max(bWidth, eventName.x + 6 + cast(lastAdded, UISliceSprite).bWidth + 6 + 10); + } + else if (lastAdded is FlxSprite) { + winHeight += cast(lastAdded, FlxSprite).height + 6; + bWidth = Math.max(bWidth, eventName.x + 6 + cast(lastAdded, FlxSprite).width + 6 + 10); + } + } + } else { + eventName.text = "No event"; + curEvent = -1; + + bWidth = eventName.x + eventName.width+16; + } + + winHeight = Math.max(winHeight, (eventsList.buttonSize.y * (eventsList.buttons.length + 1))-4) + 10; + bWidth += 10+75+10+4; // add events list width to account for event name being on a diff cam +4 because margins >:D + update(0); + } + + public function boundWindow() { + var screenPos:FlxPoint = CoolUtil.pointToScreenPosition(FlxPoint.get(chartEvent.x, chartEvent.y + chartEvent.bHeight)); + screenPos.x -= chartEvent.global ? -8 : 68+12; screenPos.y += 8; + + bg.bWidth = cam.width = cast bWidth-6; + bg.bHeight = cam.height = cast winHeight + 10; + eventsList.bHeight = cast winHeight - 10; + + // Bound stuff from being off screen + var screenSpaceY:Float = (screenPos.y + winHeight + 10) - FlxG.height; + screenPos.x = FlxMath.bound(screenPos.x, 4, (FlxG.width - 20) - bWidth - 4); // FlxG.width - 20 for the scroll bar on the right + if (screenSpaceY > -8) { // border of 8 of screen >:D + bg.bHeight -= cast screenSpaceY + 8; cam.height -= cast screenSpaceY + 8; + + screenSpaceY -= 20; // events list is slight diff with its camera + if (screenSpaceY > -8) eventsList.bHeight -= cast screenSpaceY + 8; + } + + cam.x = screenPos.x; cam.y = screenPos.y; + + eventCam.x = cam.x+10+75+10; eventCam.y = cam.y+10; + eventCam.width = bg.bWidth-(10+75+10); eventCam.height = bg.bHeight - 20; + + screenPos.put(); + } + + @:noCompletion var __clickedWhileHovering = false; + @:noCompletion var __ignoreLastClick:Bool = false; + var sinner:Float = 0; + override public function update(elapsed:Float) { + var mousepoint = FlxG.mouse.getPositionInCameraView(cam); + boundWindow(); + + if (FlxG.mouse.justPressed && (FlxMath.inBounds(mousepoint.x, 0, bg.bWidth) && FlxMath.inBounds(mousepoint.y, 0, bg.bHeight))) __clickedWhileHovering = true; + + var boundingParamY:Float = 0; + var lastParamSprite:FlxObject = paramsFields != null ? cast paramsFields[paramsFields.length-1] : null; + if (lastParamSprite != null) boundingParamY = lastParamSprite.y; + + if (FlxMath.inBounds(mousepoint.x, 0, bg.bWidth) && FlxMath.inBounds(mousepoint.y, 0, bg.bHeight)) { + Charter.instance.shouldScroll = false; + if (FlxMath.inBounds(mousepoint.x, 10+75+10, eventCam.width) && FlxMath.inBounds(mousepoint.y, 10, eventCam.height)) { + var boundingParamYHeight:Float = boundingParamY; + if (lastParamSprite != null) { + if (lastParamSprite is UISliceSprite) boundingParamYHeight += cast(lastParamSprite, UISliceSprite).bHeight; + else if (lastParamSprite is FlxSprite) boundingParamYHeight += cast(lastParamSprite, FlxSprite).height; + } + + if (boundingParamYHeight > eventCam.height) + eventCam.scroll.y = CoolUtil.bound(eventCam.scroll.y - (FlxG.mouse.wheel * 12), 0, boundingParamYHeight - eventCam.height); + } + } else if (Charter.instance.curContextMenu == null && ((FlxG.mouse.justReleased && !__clickedWhileHovering) || FlxG.mouse.wheel != 0)) { + if (__ignoreLastClick) + __ignoreLastClick = false; + else { + Charter.instance.shouldScroll = true; + quit(); + } + } + super.update(elapsed); + + if (FlxG.mouse.justReleased) __clickedWhileHovering = false; + + sinner += elapsed; + + downIndicator.setPosition(((eventCam.width/2)) - (downIndicator.fieldWidth/2), (eventCam.height-downIndicator.height) - 2 - (FlxMath.fastSin(sinner*2) * 2)); + downIndicator.alpha = CoolUtil.fpsLerp(downIndicator.alpha, (eventCam.scroll.y+eventCam.height > boundingParamY) ? 0 : 1, 1/3); + } + + public function quit() { + saveCurTab(); + chartEvent.refreshEventIcons(); + Charter.instance.updateBPMEvents(); + + if (events.length <= 0) + Charter.instance.deleteSelection([chartEvent]); + else if (events.length > 0) { + var oldEvents:Array<ChartEvent> = chartEvent.events.copy(); + chartEvent.events = [for (i in eventsList.buttons.members) i.event]; + + Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); + } + close(); + } + + public function saveCurTab() { + if (curEvent < 0) return; + + events[curEvent].params = [ + for(p in paramsFields) { + if (p is UIDropDown) { + var dataParams = EventsData.getEventParams(events[curEvent].name); + if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index; + else cast(p, UIDropDown).label.text; + } + else if (p is UINumericStepper) { + var stepper = cast(p, UINumericStepper); + @:privateAccess stepper.__onChange(stepper.label.text); + if (stepper.precision == 0) // int + Std.int(stepper.value); + else + stepper.value; + } + else if (p is UITextBox) + cast(p, UITextBox).label.text; + else if (p is UICheckbox) + cast(p, UICheckbox).checked; + else if (p is UIColorwheel) + cast(p, UIColorwheel).curColor; + else + null; + } + ]; + } + public override function destroy() { + super.destroy(); + FlxG.cameras.remove(cam); + FlxG.cameras.remove(eventCam); + } +} + +class EventButtonNew extends UIButton { + public var icon:FlxSprite = null; + public var event:ChartEvent = null; + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + + public function new(event:ChartEvent, icon:FlxSprite, id:Int, substate:CharterEventScreenNew, parent:UIButtonList<EventButtonNew>) { + this.icon = icon; + this.event = event; + super(0, 0, null, function() { + substate.changeTab(id); + for(i in parent.buttons.members) + i.alpha = i == this ? 1 : 0.25; + }, 73, 40); + autoAlpha = false; + + members.push(icon); + icon.setPosition(18 - icon.width / 2, 20 - icon.height / 2); + + deleteButton = new UIButton(bWidth - 30, y + (bHeight - 26) / 2, null, function () { + substate.events.splice(id, 1); + substate.changeTab(id, false); + parent.remove(this); + }, 26, 26); + deleteButton.color = FlxColor.RED; + deleteButton.autoAlpha = false; + members.push(deleteButton); + + deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 4).loadGraphic(Paths.image('editors/delete-button')); + deleteIcon.antialiasing = false; + members.push(deleteIcon); + } + + override function update(elapsed) { + super.update(elapsed); + + deleteButton.selectable = selectable; + deleteButton.shouldPress = shouldPress; + + icon.setPosition(x + (18 - icon.width / 2),y + (20 - icon.height / 2)); + deleteButton.setPosition(x + (bWidth - 30), y + (bHeight - 26) / 2); + deleteIcon.setPosition(deleteButton.x + (10/2), deleteButton.y + 4); + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEventTypeSelection.hx b/source/funkin/editors/charter/CharterEventTypeSelection.hx index 4888442a..24a299f1 100644 --- a/source/funkin/editors/charter/CharterEventTypeSelection.hx +++ b/source/funkin/editors/charter/CharterEventTypeSelection.hx @@ -1,9 +1,11 @@ package funkin.editors.charter; +import funkin.backend.system.Conductor; import funkin.backend.chart.EventsData; class CharterEventTypeSelection extends UISubstateWindow { var callback:String->Void; + var eventStepTime:Float; var buttons:Array<UIButton> = []; @@ -13,9 +15,10 @@ class CharterEventTypeSelection extends UISubstateWindow { var upIndicator:UIText; var downIndicator:UIText; - public function new(callback:String->Void) { + public function new(callback:String->Void, eventStepTime:Float) { super(); this.callback = callback; + this.eventStepTime = eventStepTime; } public override function create() { @@ -28,10 +31,19 @@ class CharterEventTypeSelection extends UISubstateWindow { FlxG.cameras.add(buttonCameras, false); buttonCameras.bgColor = 0; - buttonsBG = new UIWindow(10, 41, buttonCameras.width, buttonCameras.height, ""); + buttonsBG = new UIWindow(10, 41, buttonCameras.width, buttonCameras.height, null); buttonsBG.frames = Paths.getFrames('editors/ui/inputbox'); add(buttonsBG); + var disableConductorEvents:Bool = false; + var disableOnlyContinuousChanges:Bool = false; + for (change in Conductor.bpmChangeMap) { + if (change.continuous && MathUtil.greaterThanEqual(eventStepTime, change.stepTime) && MathUtil.lessThan(eventStepTime, change.endStepTime)) { + disableOnlyContinuousChanges = MathUtil.equal(eventStepTime, change.stepTime); //allow time sig and instant bpm changes on the same event + disableConductorEvents = !disableOnlyContinuousChanges; + } + } + for(k=>eventName in EventsData.eventsList) { var visualName = eventName; var tuId = "charter.events." + TU.raw2Id(eventName); @@ -56,6 +68,11 @@ class CharterEventTypeSelection extends UISubstateWindow { icon.x = button.x + 8; icon.y = button.y + Math.abs(button.bHeight - icon.height) / 2; add(icon); + + if (disableConductorEvents && (eventName == "Time Signature Change" || eventName == "Continuous BPM Change" || eventName == "BPM Change") || (disableOnlyContinuousChanges && eventName == "Continuous BPM Change")) { + button.selectable = button.shouldPress = false; + button.autoAlpha = true; + } } windowSpr.bHeight = 61 + (32 * (17)); @@ -83,7 +100,7 @@ class CharterEventTypeSelection extends UISubstateWindow { sinner += elapsed; for (button in buttons) - button.selectable = buttonsBG.hovered; + if (button.shouldPress) button.selectable = buttonsBG.hovered; buttonCameras.zoom = subCam.zoom; @@ -91,7 +108,7 @@ class CharterEventTypeSelection extends UISubstateWindow { buttonCameras.y = -subCam.scroll.y + Std.int(windowSpr.y+41); if (buttons.length > 16) - buttonCameras.scroll.y = FlxMath.bound(buttonCameras.scroll.y - (buttonsBG.hovered ? FlxG.mouse.wheel : 0) * 12, 0, + buttonCameras.scroll.y = CoolUtil.bound(buttonCameras.scroll.y - (buttonsBG.hovered ? FlxG.mouse.wheel : 0) * 12, 0, (buttons[buttons.length-1].y + buttons[buttons.length-1].bHeight) - buttonCameras.height); upIndicator.setPosition((buttonsBG.bWidth/2) - (upIndicator.fieldWidth/2), 22 + (FlxMath.fastSin(sinner*2) * 4)); diff --git a/source/funkin/editors/charter/CharterMetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx index ac9b5045..54e7be05 100644 --- a/source/funkin/editors/charter/CharterMetaDataScreen.hx +++ b/source/funkin/editors/charter/CharterMetaDataScreen.hx @@ -3,6 +3,7 @@ package funkin.editors.charter; import flixel.math.FlxPoint; import funkin.backend.chart.ChartData.ChartMetaData; import funkin.editors.extra.PropertyButton; +import funkin.game.HealthIcon; using StringTools; @@ -14,12 +15,12 @@ class CharterMetaDataScreen extends UISubstateWindow { public var songNameTextBox:UITextBox; public var bpmStepper:UINumericStepper; public var beatsPerMeasureStepper:UINumericStepper; - public var stepsPerBeatStepper :UINumericStepper; + public var denominatorStepper:UINumericStepper; public var customPropertiesButtonList:UIButtonList<PropertyButton>; public var displayNameTextBox:UITextBox; public var iconTextBox:UITextBox; - public var iconSprite:FlxSprite; + public var iconSprite:HealthIcon; public var opponentModeCheckbox:UICheckbox; public var coopAllowedCheckbox:UICheckbox; public var colorWheel:UIColorwheel; @@ -64,8 +65,8 @@ class CharterMetaDataScreen extends UISubstateWindow { add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22)); - stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, metadata.stepsPerBeat, 1, 0, 1, null, 54); - add(stepsPerBeatStepper); + denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, Math.floor(16 / metadata.stepsPerBeat), 1, 0, 1, null, 54); + add(denominatorStepper); add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, translate("menusData"), 28)); @@ -87,7 +88,7 @@ class CharterMetaDataScreen extends UISubstateWindow { coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, translate("coopAllowed"), metadata.coopAllowed); add(coopAllowedCheckbox); - colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.parsedColor); + colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.color); add(colorWheel); addLabelOn(colorWheel, translate("color")); @@ -95,7 +96,7 @@ class CharterMetaDataScreen extends UISubstateWindow { add(difficultiesTextBox); addLabelOn(difficultiesTextBox, translate("difficulties")); - customPropertiesButtonList = new UIButtonList<PropertyButton>(stepsPerBeatStepper.x + 80 + 26 + 105, songNameTextBox.y, 290, 316, '', FlxPoint.get(280, 35), null, 5); + customPropertiesButtonList = new UIButtonList<PropertyButton>(denominatorStepper.x + 80 + 26 + 105, songNameTextBox.y, 290, 316, '', FlxPoint.get(280, 35), null, 5); customPropertiesButtonList.frames = Paths.getFrames('editors/ui/inputbox'); customPropertiesButtonList.cameraSpacing = 0; customPropertiesButtonList.addButton.callback = function() { @@ -127,27 +128,18 @@ class CharterMetaDataScreen extends UISubstateWindow { } function updateIcon(icon:String) { - if (iconSprite == null) add(iconSprite = new FlxSprite()); + if (iconSprite == null) add(iconSprite = new HealthIcon()); - if (iconSprite.animation.exists(icon)) return; - @:privateAccess iconSprite.animation.clearAnimations(); - - var path:String = Paths.image('icons/$icon'); - if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON); - - iconSprite.loadGraphic(path, true, 150, 150); - iconSprite.animation.add(icon, [0], 0, false); - iconSprite.antialiasing = true; - iconSprite.animation.play(icon); - - iconSprite.scale.set(0.5, 0.5); + iconSprite.setIcon(icon); + var size = Std.int(150 * 0.5); + iconSprite.setUnstretchedGraphicSize(size, size, true); iconSprite.updateHitbox(); - iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); + iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2)); + iconSprite.scrollFactor.set(1, 1); } public function saveMeta() { - for (stepper in [bpmStepper, beatsPerMeasureStepper, stepsPerBeatStepper]) - @:privateAccess stepper.__onChange(stepper.label.text); + UIUtil.confirmUISelections(this); var customVals = {}; for (vals in customPropertiesButtonList.buttons.members) { @@ -158,11 +150,10 @@ class CharterMetaDataScreen extends UISubstateWindow { name: songNameTextBox.label.text, bpm: bpmStepper.value, beatsPerMeasure: Std.int(beatsPerMeasureStepper.value), - stepsPerBeat: Std.int(stepsPerBeatStepper.value), + stepsPerBeat: Std.int(16 / denominatorStepper.value), displayName: displayNameTextBox.label.text, icon: iconTextBox.label.text, - color: colorWheel.curColorString, - parsedColor: colorWheel.curColor, + color: colorWheel.curColor, opponentModeAllowed: opponentModeCheckbox.checked, coopAllowed: coopAllowedCheckbox.checked, difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()], diff --git a/source/funkin/editors/charter/CharterNote.hx b/source/funkin/editors/charter/CharterNote.hx index 1c5e1aaf..715cf3f7 100644 --- a/source/funkin/editors/charter/CharterNote.hx +++ b/source/funkin/editors/charter/CharterNote.hx @@ -1,5 +1,6 @@ package funkin.editors.charter; +import flixel.math.FlxAngle; import flixel.math.FlxPoint; import flixel.tweens.FlxTween; import flixel.util.FlxColor; @@ -28,6 +29,8 @@ class CharterNote extends UISprite implements ICharterSelectable { public var selected:Bool = false; public var draggable:Bool = true; + static var noteTypeTexts:Array<UIText> = []; + public function new() { super(); @@ -38,39 +41,51 @@ class CharterNote extends UISprite implements ICharterSelectable { this.setUnstretchedGraphicSize(40, 40, false); sustainSpr = new UISprite(10, 20); - sustainSpr.makeSolid(1, 1, -1); + sustainSpr.makeSolid(1, 1, -1, false, "note-sustain"); sustainSpr.scale.set(10, 0); members.push(sustainSpr); - typeText = new UIText(x, y, 0, Std.string(type)); + this.type = 0; - cursor = sustainSpr.cursor = BUTTON; + cursor = sustainSpr.cursor = CLICK; moves = false; } public override function updateButtonHandler() { - __rect.x = x; - __rect.y = y; - __rect.width = 40; - __rect.height = 40; + __rect.set(x, y, 40, 40); UIState.state.updateRectButtonHandler(this, __rect, onHovered); } public var step:Float; public var id:Int; public var susLength:Float; - public var type:Int; + public var type(default, set):Null<Int> = null; + public var typeVisible:Bool = true; + public var typeAlpha:Float = 1; + + function set_type(v:Null<Int>) { + if(v != this.type) { + if(noteTypeTexts[v] == null || noteTypeTexts[v].graphic.isDestroyed) { + noteTypeTexts[v] = new UIText(x, y, 0, Std.string(v)); + noteTypeTexts[v].exists = v != 0; + //noteTypeTexts[v].layer = (Charter.instance != null) ? Charter.instance.textLayer : null; + } + + typeText = noteTypeTexts[v]; + } + return this.type = v; + } public var strumLine:CharterStrumline; public var strumLineID(get, default):Int = -1; public function get_strumLineID():Int return strumLineID = (strumLine == null ? strumLineID : Charter.instance.strumLines.members.indexOf(strumLine)); - public var snappedToStrumline:Bool = true; + public var snappedToGrid:Bool = true; public var fullID(get, never):Int; // instead of %4 get fullID (for mousepos stuff) public function get_fullID():Int - return (strumLineID * 4) + id; + return strumLine.startingID + id; public function updatePos(step:Float, id:Int, susLength:Float = 0, ?type:Int = 0, ?strumLine:CharterStrumline = null) { this.step = step; @@ -79,16 +94,14 @@ class CharterNote extends UISprite implements ICharterSelectable { this.type = type; if (strumLine != null) this.strumLine = strumLine; - typeText.exists = type != 0; - typeText.text = Std.string(this.type); - typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); + sustainSpr.exists = susLength != 0; y = step * 40; if (angleTween != null) angleTween.cancel(); - var destAngle = switch(animation.curAnim.curFrame = (id % 4)) { - case 0: -90; + var destAngle:Float = switch(animation.curAnim.curFrame = (id % 4)) { + case 0: 270; case 1: 180; case 2: 0; case 3: 90; @@ -107,7 +120,9 @@ class CharterNote extends UISprite implements ICharterSelectable { if(angleTween != null) angleTween.cancel(); - angleTween = FlxTween.tween(this, {angle: destAngle}, (2/3)/__animSpeed, {ease: function(t) { + destAngle = CoolUtil.getClosestAngle(angle, destAngle); + + angleTween = FlxTween.angle(this, angle, destAngle, (2/3)/__animSpeed, {ease: function(t) { return ((Math.sin(t * Math.PI) * 0.35) * 3 * t * Math.sqrt(1 - t)) + t; }}); } @@ -117,7 +132,7 @@ class CharterNote extends UISprite implements ICharterSelectable { angleTween.cancel(); angleTween = null; angle = switch(animation.curAnim.curFrame = (id % 4)) { - case 0: -90; + case 0: 270; case 1: 180; case 2: 0; case 3: 90; @@ -132,28 +147,28 @@ class CharterNote extends UISprite implements ICharterSelectable { public override function update(elapsed:Float) { super.update(elapsed); - var sprLength:Float = (40 * (susLength+tempSusLength)) + ((susLength+tempSusLength) != 0 ? (height/2) : 0); - sustainSpr.scale.set(10, __susInstaLerp ? sprLength : CoolUtil.fpsLerp(sustainSpr.scale.y, sprLength, 1/2)); - sustainSpr.updateHitbox(); - sustainSpr.follow(this, 15, 20); + if(susLength != 0) { + var sprLength:Float = (40 * (susLength+tempSusLength)) + ((susLength+tempSusLength) != 0 ? (height/2) : 0); + sustainSpr.scale.set(10, __susInstaLerp ? sprLength : CoolUtil.fpsLerp(sustainSpr.scale.y, sprLength, 1/2)); + sustainSpr.updateHitbox(); + sustainSpr.follow(this, 15, 20); + } sustainDraggable = false; if (!hovered && susLength != 0) { - UIState.state.updateSpriteRect(sustainSpr); + sustainSpr.updateSpriteRect(); sustainDraggable = UIState.state.isOverlapping(sustainSpr, @:privateAccess sustainSpr.__rect); } - if (typeText.exists) - typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); - - if (__passed != (__passed = step < Conductor.curStepFloat)) { + if (__passed != (__passed = step < Conductor.curStepFloat + (Conductor.songOffset / Conductor.stepCrochet))) { if (__passed && FlxG.sound.music.playing && Charter.instance.hitsoundsEnabled(strumLineID)) Charter.instance.hitsound.replay(); } if (strumLine != null) { - sustainSpr.alpha = alpha = !strumLine.strumLine.visible ? (__passed ? 0.2 : 0.4) : (__passed ? 0.6 : 1); - typeText.alpha = !strumLine.strumLine.visible ? (__passed ? 0.4 : 0.6) : (__passed ? 0.8 : 1); + var isVisible = strumLine.strumLine.visible; + sustainSpr.alpha = alpha = !isVisible ? (__passed ? 0.2 : 0.4) : (__passed ? 0.6 : 1); + typeAlpha = !isVisible ? (__passed ? 0.4 : 0.6) : (__passed ? 0.8 : 1); } colorTransform.redMultiplier = colorTransform.greenMultiplier = colorTransform.blueMultiplier = selected ? 0.75 : 1; @@ -173,18 +188,27 @@ class CharterNote extends UISprite implements ICharterSelectable { } public function handleDrag(change:FlxPoint) { - var newStep = FlxMath.bound(step + change.x, 0, Charter.instance.__endStep-1); - var newID:Int = Std.int(FlxMath.bound(fullID + Std.int(change.y), 0, (Charter.instance.strumLines.members.length*4)-1)); + var newStep = CoolUtil.bound(step + change.x, 0, Charter.instance.__endStep-1); + var newID:Int = Std.int(FlxMath.bound(fullID + Std.int(change.y), 0, Charter.instance.strumLines.totalKeyCount-1)); + var newStrumLine = Charter.instance.strumLines.getStrumlineFromID(newID); - updatePos(newStep, newID % 4, susLength, type, Charter.instance.strumLines.members[Std.int(newID/4)]); + updatePos(newStep, (newID - newStrumLine.startingID) % newStrumLine.keyCount, susLength, type, newStrumLine); } public override function draw() { - if (snappedToStrumline) - x = (strumLine != null ? strumLine.x : 0) + (id % 4) * 40; + if (snappedToGrid) + x = (strumLine != null ? strumLine.x : 0) + (id % (strumLine != null ? strumLine.keyCount : 4)) * 40; drawMembers(); drawSuper(); - if(typeText.exists && typeText.visible) typeText.draw(); + drawNoteTypeText(); + } + + public inline function drawNoteTypeText() { + if(typeText.exists && typeText.visible && typeVisible) { + typeText.alpha = typeAlpha; + typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); + typeText.draw(); + } } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterNoteGroup.hx b/source/funkin/editors/charter/CharterNoteGroup.hx index c2c2e388..31c7eae3 100644 --- a/source/funkin/editors/charter/CharterNoteGroup.hx +++ b/source/funkin/editors/charter/CharterNoteGroup.hx @@ -60,14 +60,14 @@ class CharterNoteGroup extends FlxTypedGroup<CharterNote> { public override function update(elapsed:Float) @:privateAccess { var oldDefaultCameras = FlxCamera._defaultCameras; - if (cameras != null) FlxCamera._defaultCameras = cameras; + if (_cameras != null) FlxCamera._defaultCameras = _cameras; if (autoSort && members.length != __lastSort) sortNotes(); forEach((n) -> { if(n.exists && n.active) { - n.cameras = n.__lastDrawCameras = cameras; + n._cameras = n.__lastDrawCameras = cameras; n.update(elapsed); } }); diff --git a/source/funkin/editors/charter/CharterNoteHoverer.hx b/source/funkin/editors/charter/CharterNoteHoverer.hx index 3ef2e543..3f7146ba 100644 --- a/source/funkin/editors/charter/CharterNoteHoverer.hx +++ b/source/funkin/editors/charter/CharterNoteHoverer.hx @@ -6,8 +6,8 @@ class CharterNoteHoverer extends CharterNote { public function new() { super(); - snappedToStrumline = selectable = autoAlpha = false; visible = sustainSpr.visible = false; - @:privateAccess __animSpeed = 1.25; typeText.visible = false; alpha = 0.4; + snappedToGrid = selectable = autoAlpha = false; visible = sustainSpr.visible = false; + @:privateAccess __animSpeed = 1.25; typeVisible = false; alpha = 0.4; } @:noCompletion var __mousePos:FlxPoint = FlxPoint.get(); @@ -17,10 +17,10 @@ class CharterNoteHoverer extends CharterNote { switch (Charter.instance.gridActionType) { case NONE: var inBoundsY:Bool = (__mousePos.y > 0 && __mousePos.y < (Charter.instance.__endStep)*40); - if ((__mousePos.x > 0 && __mousePos.x < Charter.instance.gridBackdrops.strumlinesAmount * 160 && inBoundsY) && showHoverer) { - step = FlxMath.bound(FlxG.keys.pressed.SHIFT ? ((__mousePos.y-20) / 40) : Charter.instance.quantStep(__mousePos.y/40), 0, Charter.instance.__endStep-1); - id = Math.floor(__mousePos.x / 40); y = step * 40; x = id * 40; visible = true; sustainSpr.visible = typeText.visible = false; - angle = switch(animation.curAnim.curFrame = (id % 4)) { + if ((__mousePos.x > 0 && __mousePos.x < Charter.instance.strumLines.totalKeyCount * 40 && inBoundsY) && showHoverer) { + step = CoolUtil.bound(FlxG.keys.pressed.SHIFT ? ((__mousePos.y-20) / 40) : Charter.instance.quantStep(__mousePos.y/40), 0, Charter.instance.__endStep-1); + id = Math.floor(__mousePos.x / 40); y = step * 40; x = id * 40; visible = true; sustainSpr.visible = typeVisible = false; + angle = switch(animation.curAnim.curFrame = ((id - Charter.instance.strumLines.getStrumlineFromID(id).startingID) % 4)) { case 0: -90; case 1: 180; case 2: 0; @@ -30,9 +30,9 @@ class CharterNoteHoverer extends CharterNote { } else visible = false; case NOTE_DRAG: - visible = sustainSpr.visible = typeText.visible = true; __doAnim = false; + visible = sustainSpr.visible = typeVisible = true; __doAnim = false; default: - visible = sustainSpr.visible = typeText.visible = false; __doAnim = false; + visible = sustainSpr.visible = typeVisible = false; __doAnim = false; } } @@ -53,8 +53,8 @@ class CharterNoteHoverer extends CharterNote { y -= ((draggingNote.step + verticalChange) - Charter.instance.quantStepRounded(draggingNote.step+verticalChange, verticalChange > 0 ? 0.35 : 0.65)); y *= 40; - var newID:Int = Std.int(FlxMath.bound(draggingNote.fullID + horizontalChange, 0, (Charter.instance.strumLines.members.length*4)-1)); - x = (id=newID) * 40; y = FlxMath.bound(y, 0, (Charter.instance.__endStep*40) - height); + var newID:Int = CoolUtil.boundInt(draggingNote.fullID + horizontalChange, 0, Charter.instance.strumLines.totalKeyCount-1); + x = (id=newID) * 40; y = CoolUtil.bound(y, 0, (Charter.instance.__endStep*40) - height); angle = switch(animation.curAnim.curFrame = (draggingNote.id % 4)) { case 0: -90; @@ -69,8 +69,9 @@ class CharterNoteHoverer extends CharterNote { sustainSpr.updateHitbox(); sustainSpr.alpha = alpha; sustainSpr.follow(this, 15, 20); sustainSpr.exists = draggingNote.susLength != 0; - typeText.text = Std.string(draggingNote.type); - typeText.exists = draggingNote.type != 0; + type = draggingNote.type; + //typeText.text = Std.string(draggingNote.type); + //typeText.exists = draggingNote.type != 0; typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); super.draw(); diff --git a/source/funkin/editors/charter/CharterNoteTypesList.hx b/source/funkin/editors/charter/CharterNoteTypesList.hx index 6805a0d5..d9a2b66f 100644 --- a/source/funkin/editors/charter/CharterNoteTypesList.hx +++ b/source/funkin/editors/charter/CharterNoteTypesList.hx @@ -26,12 +26,8 @@ class CharterNoteTypesList extends UISubstateWindow { var noteTypesFileList = getNoteTypesList(true); if (noteTypesFileList.length == 0) noteTypesFileList = getNoteTypesList(false); - noteTypesList = new UIButtonList<NoteTypeButton>(20, title.y + title.height + 10, winWidth - 40, 342 - 85 - 16, "", FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0); - noteTypesList.addButton.callback = () -> { - noteTypesList.add(new NoteTypeButton(0, 0, - TU.translate("charterNoteTypesList.editType", [noteTypesList.buttons.members.length]), - noteTypesList.buttons.members.length, noteTypesFileList, noteTypesList)); - } + noteTypesList = new UIButtonList<NoteTypeButton>(20, title.y + title.height + 10, winWidth - 40, 342 - 85 - 16, null, FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0); + noteTypesList.addButton.callback = () -> noteTypesList.add(new NoteTypeButton(0, 0, TU.translate("charterNoteTypesList.editType", [noteTypesList.buttons.members.length]), noteTypesList.buttons.members.length, noteTypesFileList, noteTypesList)); noteTypesList.cameraSpacing = 0; noteTypesList.dragCallback = (object:NoteTypeButton, oldIndex:Int, newIndex:Int) -> {object.IDText.text = '${newIndex}.';}; for (i=>noteType in Charter.instance.noteTypes) // TODO: make this translatable @@ -96,7 +92,7 @@ class NoteTypeButton extends UIButton { }; textBox.antialiasing = true; - deleteButton = new UIButton(textBox.x + textBox.label.width + 10, textBox.y, "", function () { + deleteButton = new UIButton(textBox.x + textBox.label.width + 10, textBox.y, null, function () { parent.remove(this); }, 32); deleteButton.color = 0xFFFF0000; diff --git a/source/funkin/editors/charter/CharterPreviewStrumLine.hx b/source/funkin/editors/charter/CharterPreviewStrumLine.hx new file mode 100644 index 00000000..91aa5cca --- /dev/null +++ b/source/funkin/editors/charter/CharterPreviewStrumLine.hx @@ -0,0 +1,72 @@ +package funkin.editors.charter; + +import funkin.game.Note; + +class CharterPreviewStrumLine extends FlxTypedGroup<FlxSprite> +{ + public function new(x:Float, y:Float, scale:Float, spacing:Float, keyCount:Int, scrollSpeed:Float){ + super(); + updatePos(x, y, scale, spacing, keyCount, scrollSpeed); + } + + private var note:FlxSprite; + + public function generateStrums(x:Float, y:Float, scale:Float, spacing:Float, keyCount:Int, scrollSpeed:Float){ + for (member in members){ + member.destroy(); + } + clear(); + + var strumAnimPrefix = ["left", "down", "up", "right"]; + for (i in 0...keyCount){ + var strum = new FlxSprite(); + strum.frames = Paths.getFrames("game/notes/default"); + strum.setGraphicSize(Std.int((strum.width * 0.7) * scale)); + strum.updateHitbox(); + + var animPrefix = strumAnimPrefix[i % 4]; + strum.animation.addByPrefix('static', 'arrow${animPrefix.toUpperCase()}'); + strum.animation.play('static'); + strum.alpha = 0.4; + add(strum); + } + + note = new FlxSprite(); + note.frames = Paths.getFrames("game/notes/default"); + note.setGraphicSize(Std.int((note.width * 0.7) * scale)); + note.updateHitbox(); + note.animation.addByPrefix('purple', 'purple0'); + note.animation.play('purple'); + note.alpha = 0.4; + add(note); + } + + var noteTime:Float = FlxG.height; + var scroll:Float = 1.0; + + public function updatePos(x:Float, y:Float, scale:Float, spacing:Float, keyCount:Int, scrollSpeed:Float){ + if (members.length-1 != keyCount) //strumline + note + generateStrums(x, y, scale, spacing, keyCount, scrollSpeed); + + for (i in 0...keyCount){ + var strum = members[i]; + + strum.x = CoolUtil.fpsLerp(strum.x, x + (Note.swagWidth * scale * spacing * i), 0.2); + strum.y = CoolUtil.fpsLerp(strum.y, y + (Note.swagWidth*0.5) - (Note.swagWidth * scale * 0.5), 0.2); + strum.scale.x = strum.scale.y = CoolUtil.fpsLerp(strum.scale.x, 0.7 * scale, 0.2); + strum.updateHitbox(); + } + + + scroll = CoolUtil.fpsLerp(scroll, scrollSpeed, 0.2); + noteTime -= FlxG.elapsed * scroll * 1000 * 0.45; + if (noteTime <= 0.0) + noteTime = FlxG.height; + + note.x = members[0].x; + note.y = members[0].y + noteTime; + note.scale.x = note.scale.y = members[0].scale.x; + note.updateHitbox(); + note.alpha = CoolUtil.fpsLerp(note.alpha, scrollSpeed == 0.0 ? 0.0 : 0.4, 0.2); + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx index 346f02c0..467817d7 100644 --- a/source/funkin/editors/charter/CharterSelection.hx +++ b/source/funkin/editors/charter/CharterSelection.hx @@ -1,5 +1,6 @@ package funkin.editors.charter; +import funkin.backend.chart.Chart; import funkin.backend.chart.ChartData.ChartMetaData; import funkin.backend.chart.ChartData; import funkin.backend.system.framerate.Framerate; @@ -34,18 +35,22 @@ class CharterSelection extends EditorTreeMenu { FlxG.switchState(new Charter(s.name, d)); }) ]; + #if sys var newDiff = TU.translate("charterSelection.newDifficulty"); list.push(new NewOption(newDiff, newDiff, function() { FlxG.state.openSubState(new ChartCreationScreen(saveChart)); })); + #end optionsTree.add(new OptionsScreen(s.name, TU.translate("charterSelection.selectDifficulty"), list)); - }, s.parsedColor.getDefault(0xFFFFFFFF)) + }, s.color.getDefault(0xFFFFFFFF)) ]; + #if sys var newSong = TU.translate("charterSelection.newSong"); list.insert(0, new NewOption(newSong, newSong, function() { FlxG.state.openSubState(new SongCreationScreen(saveSong)); })); + #end main = new OptionsScreen(TU.translate("editor.chart.name"), TU.translate("charterSelection.desc"), list); @@ -90,7 +95,8 @@ class CharterSelection extends EditorTreeMenu { } } - public function saveSong(creation:SongCreationData) { + #if sys + public function saveSong(creation:SongCreationData, ?callback:String -> SongCreationData -> Void) { var songAlreadyExists:Bool = [for (s in freeplayList.songs) s.name.toLowerCase()].contains(creation.meta.name.toLowerCase()); if (songAlreadyExists) { @@ -111,12 +117,17 @@ class CharterSelection extends EditorTreeMenu { sys.FileSystem.createDirectory('$songFolder/charts'); // Save Files - CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(creation.meta, Flags.JSON_PRETTY_PRINT)); + CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(creation.meta)); if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst.${Flags.SOUND_EXT}', creation.instBytes); if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Flags.SOUND_EXT}', creation.voicesBytes); + + if (creation.playerVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Player.${Flags.SOUND_EXT}', creation.playerVocals); + if (creation.oppVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Opponent.${Flags.SOUND_EXT}', creation.oppVocals); #end - // duplicated code, todo: fix this + if (callback != null) + callback(songFolder, creation); + var option = new EditorIconOption(creation.meta.name, TU.translate("charterSelection.acceptSong"), creation.meta.icon, function() { curSong = creation.meta; var list:Array<OptionType> = [ @@ -130,7 +141,7 @@ class CharterSelection extends EditorTreeMenu { FlxG.state.openSubState(new ChartCreationScreen(saveChart)); })); optionsTree.insert(1, new OptionsScreen(creation.meta.name, TU.translate("charterSelection.selectDifficulty"), list)); - }, creation.meta.parsedColor.getDefault(0xFFFFFFFF)); + }, creation.meta.color.getDefault(0xFFFFFFFF)); // Add to List freeplayList.songs.insert(0, creation.meta); @@ -165,7 +176,8 @@ class CharterSelection extends EditorTreeMenu { var meta = Json.parse(sys.io.File.getContent('$songFolder/meta.json')); if (meta.difficulties != null && !meta.difficulties.contains(name)) { meta.difficulties.push(name); - CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(meta)); + CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(meta)); } } + #end } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterStrumLineGroup.hx b/source/funkin/editors/charter/CharterStrumLineGroup.hx index 296cffaa..5b1633a6 100644 --- a/source/funkin/editors/charter/CharterStrumLineGroup.hx +++ b/source/funkin/editors/charter/CharterStrumLineGroup.hx @@ -8,13 +8,25 @@ class CharterStrumLineGroup extends FlxTypedGroup<CharterStrumline> { var draggingObj:CharterStrumline = null; var draggingOffset:Float = 0; + public var totalKeyCount(get, never):Int; + private var __totalKeyCount:Int = -1; + public function get_totalKeyCount():Int { + if (__totalKeyCount != -1) return __totalKeyCount; + + var v:Int = 0; + for (strumLine in members) v += strumLine.keyCount; + return __totalKeyCount = v; + } + public var draggable:Bool = false; public var isDragging(get, never):Bool; public function get_isDragging():Bool return draggingObj != null; + var __mousePos:FlxPoint = new FlxPoint(); + public override function update(elapsed:Float) { - var mousePos = FlxG.mouse.getWorldPosition(cameras[0], FlxPoint.get()); + FlxG.mouse.getWorldPosition(cameras[0], __mousePos); for (strumLine in members) { if (strumLine == null) continue; strumLine.draggable = draggable; @@ -22,37 +34,51 @@ class CharterStrumLineGroup extends FlxTypedGroup<CharterStrumline> { draggingObj = strumLine; strumLine.dragging = true; - draggingOffset = mousePos.x - strumLine.button.x; + draggingOffset = __mousePos.x - strumLine.button.x; __pastStrumlines = members.copy(); break; } } if (isDragging) { - draggingObj.x = mousePos.x - draggingOffset; + draggingObj.x = __mousePos.x - draggingOffset; this.sort(function(o, a, b) return FlxSort.byValues(o, a.x, b.x), -1); + refreshStrumlineIDs(); } for (i=>strum in members) - if (strum != null && !strum.dragging) strum.x = CoolUtil.fpsLerp(strum.x, 160 * i, 0.225); + if (strum != null && !strum.dragging) strum.x = CoolUtil.fpsLerp(strum.x, 40*strum.startingID, 0.225); - if (Charter.instance.eventsBackdrop != null && members[0] != null) - Charter.instance.eventsBackdrop.x = members[0].button.x - Charter.instance.eventsBackdrop.width; + if (Charter.instance.leftEventsBackdrop != null && members[0] != null) { + Charter.instance.leftEventsBackdrop.x = members[0].button.x - Charter.instance.leftEventsBackdrop.width; + Charter.instance.leftEventsBackdrop.alpha = members[0].strumLine.visible ? 0.9 : 0.4; + + if (Charter.instance.leftEventRowText != null) + Charter.instance.leftEventRowText.x = members[0].button.x - Charter.instance.leftEventRowText.width - 42; + } + + if (Charter.instance.rightEventsBackdrop != null && members[CoolUtil.maxInt(0, members.length-1)] != null) { + Charter.instance.rightEventsBackdrop.x = members[members.length-1].x + (40*members[members.length-1].keyCount); + Charter.instance.rightEventsBackdrop.alpha = members[CoolUtil.maxInt(0, members.length-1)].strumLine.visible ? 0.9 : 0.4; + + if (Charter.instance.rightEventRowText != null) + Charter.instance.rightEventRowText.x = Charter.instance.rightEventsBackdrop.x + 42; + } + if (Charter.instance.strumlineLockButton != null && members[0] != null) - Charter.instance.strumlineLockButton.x = members[0].x - (40*4); - if (Charter.instance.strumlineAddButton != null && members[Std.int(Math.max(0, members.length-1))] != null) - Charter.instance.strumlineAddButton.x = members[members.length-1].x + (40*4); + Charter.instance.strumlineLockButton.x = members[0].x - (160); + if (Charter.instance.strumlineAddButton != null && members[CoolUtil.maxInt(0, members.length-1)] != null) + Charter.instance.strumlineAddButton.x = members[members.length-1].x + (40*members[members.length-1].keyCount); if ((FlxG.mouse.justReleased || !draggable) && isDragging) finishDrag(); - mousePos.put(); super.update(elapsed); } public function snapStrums() { for (i=>strum in members) - if (strum != null && !strum.dragging) strum.x = 160 * i; + if (strum != null && !strum.dragging) strum.x = 40*strum.startingID; } public function orderStrumline(strumLine:CharterStrumline, newID:Int) { @@ -61,6 +87,7 @@ class CharterStrumLineGroup extends FlxTypedGroup<CharterStrumline> { members.remove(strumLine); members.insert(newID, strumLine); + refreshStrumlineIDs(); finishDrag(false); } @@ -77,21 +104,39 @@ class CharterStrumLineGroup extends FlxTypedGroup<CharterStrumline> { draggingObj = null; fixEvents(); + refreshStrumlineIDs(); } public inline function fixEvents() { - for (i in Charter.instance.eventsGroup.members) { - for (j in i.events) { - var paramTypes:Array<EventParamInfo> = EventsData.getEventParams(j.name); - for (i => param in paramTypes) { - if (param.type != TStrumLine) continue; - j.params[i] = members.indexOf(__pastStrumlines[j.params[i]]); + for (eventsGroup in [Charter.instance.leftEventsGroup, Charter.instance.rightEventsGroup]) { + for (i in eventsGroup) { + for (j in i.events) { + var paramTypes:Array<EventParamInfo> = EventsData.getEventParams(j.name); + for (i => param in paramTypes) { + if (param.type != TStrumLine) continue; + j.params[i] = members.indexOf(__pastStrumlines[j.params[i]]); + } } } } __pastStrumlines = null; } + public inline function refreshStrumlineIDs() { + __totalKeyCount = -1; + @:privateAccess + for (strumLine in members) strumLine.__startingID = -1; + } + + public function getStrumlineFromID(id:Int) { + var v:Int = 0; + for (strumLine in members) { + v += strumLine.keyCount; + if (id < v) return strumLine; + } + return members.last(); + } + override function draw() @:privateAccess { var i:Int = 0; var basic:FlxBasic = null; diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index bce0ca2e..8c233863 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -23,6 +23,20 @@ class CharterStrumline extends UISprite { public var vocals:FlxSound; + public var keyCount:Int = 4; + public var startingID(get, null):Int; + private var __startingID:Int = -1; + public function get_startingID():Int { + if (__startingID != -1) return __startingID; + + var index = Charter.instance.strumLines.members.indexOf(this); + if (index < 1) return __startingID = 0; //-1 or 0 + + var v:Int = 0; + for (i in 0...index) v += Charter.instance.strumLines.members[i].keyCount; + return __startingID = v; + } + public var selectedWaveform(default, set):Int = -1; public function set_selectedWaveform(value:Int):Int { if (value == -1) waveformShader = null; @@ -45,14 +59,18 @@ class CharterStrumline extends UISprite { var icons = strumLine.characters != null ? strumLine.characters : []; + keyCount = strumLine.keyCount != null ? strumLine.keyCount : 4; + healthIcons = new FlxSpriteGroup(x, y); + var maxCol = icons.length < 4 ? icons.length : 4; + var maxRow = Math.floor((icons.length-1) / 4) + 1; for (i=>icon in icons) { var healthIcon = new HealthIcon(Character.getIconFromCharName(icon)); - healthIcon.scale.x = healthIcon.scale.y = 0.6 - (icons.length / 20); + healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35); healthIcon.updateHitbox(); - healthIcon.x = FlxMath.lerp(0, icons.length * 20, (icons.length-1 != 0 ? i / (icons.length-1) : 0)); - healthIcon.y = draggable ? 29 : 7; + healthIcon.x = FlxMath.lerp(0, Math.min(icons.length * 20, 120), (maxCol-1 != 0 ? (i % 4) / (maxCol-1) : 0)); + healthIcon.y = (draggable ? 29 : 7) + FlxMath.lerp(0, Math.min(maxRow * 15, 60), (maxRow-1 != 0 ? Math.floor(i / 4) / (maxRow-1) : 0)); healthIcon.alpha = strumLine.visible ? 1 : 0.4; healthIcons.add(healthIcon); } @@ -64,14 +82,13 @@ class CharterStrumline extends UISprite { draggingSprite.alpha = 0.4; draggingSprite.y = 9; draggingSprite.antialiasing = true; - draggingSprite.cursor = BUTTON; + draggingSprite.cursor = CLICK; members.push(draggingSprite); button = new CharterStrumlineOptions(this); members.push(button); - vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound(); - vocals.group = FlxG.sound.defaultMusicGroup; + updateInfo(); selectedWaveform = -1; } @@ -82,16 +99,16 @@ class CharterStrumline extends UISprite { public override function update(elapsed:Float) { if (FlxG.keys.justPressed.K) draggable = !draggable; - healthIcons.follow(this, ((40 * 4) - healthIcons.width) / 2, 7 + (__healthYOffset = FlxMath.lerp(__healthYOffset, draggable ? 8 : 0, 1/20))); + healthIcons.follow(this, ((40 * keyCount) - healthIcons.width) / 2, 7 + (__healthYOffset = FlxMath.lerp(__healthYOffset, draggable ? 8 : 0, 1/20))); draggingSprite.selectable = draggable; - UIState.state.updateSpriteRect(draggingSprite); + draggingSprite.updateSpriteRect(); var dragScale:Float = FlxMath.lerp(draggingSprite.scale.x, draggable ? 1 : 0.8, 1/16); draggingSprite.scale.set(dragScale, dragScale); draggingSprite.updateHitbox(); - draggingSprite.follow(this, (160/2) - (draggingSprite.width/2), 6 + (__draggingYOffset = FlxMath.lerp(__draggingYOffset, draggable ? 3 : 0, 1/12))); + draggingSprite.follow(this, ((keyCount*40)/2) - (draggingSprite.width/2), 6 + (__draggingYOffset = FlxMath.lerp(__draggingYOffset, draggable ? 3 : 0, 1/12))); var fullAlpha:Float = UIState.state.isOverlapping(draggingSprite, @:privateAccess draggingSprite.__rect) || dragging ? 0.9 : 0.35; draggingSprite.alpha = FlxMath.lerp(draggingSprite.alpha, draggable ? fullAlpha : 0, 1/12); button.follow(this, 0, 95); @@ -102,14 +119,18 @@ class CharterStrumline extends UISprite { public function updateInfo() { var icons = strumLine.characters != null ? strumLine.characters : []; + keyCount = strumLine.keyCount != null ? strumLine.keyCount : 4; + healthIcons.clear(); + var maxCol = icons.length < 4 ? icons.length : 4; + var maxRow = Math.floor((icons.length-1) / 4) + 1; for (i=>icon in icons) { var healthIcon = new HealthIcon(Character.getIconFromCharName(icon)); - healthIcon.scale.x = healthIcon.scale.y = 0.6 - (icons.length / 20); + healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35); healthIcon.updateHitbox(); - healthIcon.x = FlxMath.lerp(0, icons.length * 20, (icons.length-1 != 0 ? i / (icons.length-1) : 0)); - healthIcon.y = draggable ? 14 : 7; + healthIcon.x = FlxMath.lerp(0, Math.min(icons.length * 20, 120), (maxCol-1 != 0 ? (i % 4) / (maxCol-1) : 0)); + healthIcon.y = (draggable ? 14 : 7) + FlxMath.lerp(0, Math.min(maxRow * 15, 60), (maxRow-1 != 0 ? Math.floor(i / 4) / (maxRow-1) : 0)); healthIcon.alpha = strumLine.visible ? 1 : 0.4; healthIcons.add(healthIcon); } @@ -126,13 +147,13 @@ class CharterStrumlineOptions extends UITopMenuButton { // TODO: better id for this super(0, 95, null, TU.translate("charter.strumLine.button-name"), []); strLine = parent; - bWidth = 40 * 4; - this.label.fieldWidth = bWidth; } public override function update(elapsed:Float) { super.update(elapsed); alpha = FlxMath.lerp(1/20, 1, alpha); // so that instead of 0% it is 33% visible + bWidth = 40 * strLine.keyCount; + this.label.fieldWidth = bWidth; } public override function openContextMenu() { @@ -185,6 +206,9 @@ class CharterStrumlineOptions extends UITopMenuButton { contextMenu.insert(1+Charter.waveformHandler.waveformList.length, null); - super.openContextMenu(); + var cam = Charter.instance.charterCamera; + var point = CoolUtil.worldToScreenPosition(this, cam); + curMenu = UIState.state.openContextMenu(contextMenu, null, point.x, point.y + (bHeight*cam.zoom), Std.int(bWidth * cam.zoom)); + point.put(); } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterStrumlineButton.hx b/source/funkin/editors/charter/CharterStrumlineButton.hx index a0a8c7df..a1955248 100644 --- a/source/funkin/editors/charter/CharterStrumlineButton.hx +++ b/source/funkin/editors/charter/CharterStrumlineButton.hx @@ -24,7 +24,7 @@ class CharterStrumlineButton extends UISprite { button.scale.set(0.85,0.85); button.updateHitbox(); button.antialiasing = true; - button.cursor = BUTTON; + button.cursor = CLICK; members.push(button); this.text = new UIText(0,0, 160, text); @@ -58,8 +58,8 @@ class CharterStrumlineButton extends UISprite { super.update(elapsed); - UIState.state.updateSpriteRect(button); - if(UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect)) { + button.updateSpriteRect(); + if (UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect) && button.visible) { buttonScale.set(0.95, 0.95); if (FlxG.mouse.justPressed && onClick != null) { onClick(); diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index 7256975f..5f1cb623 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -1,9 +1,13 @@ package funkin.editors.charter; +import funkin.game.StrumLine; import flixel.math.FlxPoint; import funkin.backend.chart.ChartData.ChartStrumLine; import funkin.game.Character; +import funkin.game.Note; +import funkin.game.HudCamera; import funkin.game.HealthIcon; +import flixel.tweens.FlxTween; class CharterStrumlineScreen extends UISubstateWindow { public var strumLineID:Int = -1; @@ -19,12 +23,17 @@ class CharterStrumlineScreen extends UISubstateWindow { public var visibleCheckbox:UICheckbox; public var scrollSpeedStepper:UINumericStepper; public var usesChartScrollSpeed:UICheckbox; + public var keyCountStepper:UINumericStepper; + public var hudSpacingStepper:UINumericStepper; public var characterIcons:Array<HealthIcon> = []; public var saveButton:UIButton; public var closeButton:UIButton; + public var strumLineCam:HudCamera; + public var previewStrumLine:CharterPreviewStrumLine; + private var onSave:ChartStrumLine -> Void = null; public function new(strumLineID:Int, strumLine:ChartStrumLine, ?onSave:ChartStrumLine->Void) { @@ -43,11 +52,12 @@ class CharterStrumlineScreen extends UISubstateWindow { type: OPPONENT, notes: [], position: "dad", - visible: true + visible: true, + keyCount: 4 }; winTitle = TU.translate("charterStrumlineScreen.win-title" + (creatingStrumLine ? "-creating" : "-editing"), ["#" + strumLineID]); - winWidth = 690; winHeight = 400; + winWidth = 720; winHeight = 400; FlxG.sound.music.pause(); Charter.instance.vocals.pause(); @@ -64,7 +74,7 @@ class CharterStrumlineScreen extends UISubstateWindow { var charFileList = Character.getList(true); if (charFileList.length == 0) charFileList = Character.getList(false); - charactersList = new UIButtonList<CharacterButton>(15, title.y + title.height + 36, 250, 269, "", FlxPoint.get(250, 54), null, 0); + charactersList = new UIButtonList<CharacterButton>(15, title.y + title.height + 36, 250, 269, null, FlxPoint.get(250, 54), null, 0); charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, TU.translate("charterStrumLine.newChar"), charFileList, charactersList)); charactersList.cameraSpacing = 0; for (i in strumLine.characters) @@ -106,17 +116,21 @@ class CharterStrumlineScreen extends UISubstateWindow { add(stagePositionDropdown); addLabelOn(stagePositionDropdown, TU.translate("charterStrumLine.stagePos")); - hudScaleStepper = new UINumericStepper(typeDropdown.x, typeDropdown.y + 64, strumLine.strumScale == null ? 1 : strumLine.strumScale, 0.001, 2, null, null, 74); + hudScaleStepper = new UINumericStepper(typeDropdown.x, typeDropdown.y + 64, strumLine.strumScale == null ? 1 : strumLine.strumScale, 0.001, 2, null, null, 84); add(hudScaleStepper); addLabelOn(hudScaleStepper, TU.translate("charterStrumLine.scale")); + hudSpacingStepper = new UINumericStepper(hudScaleStepper.x + 80 - 32 + 26, hudScaleStepper.y, strumLine.strumSpacing != null ? strumLine.strumSpacing : 1, 0.001, 10, null, null, 84); + add(hudSpacingStepper); + addLabelOn(hudSpacingStepper, "Spacing"); + var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos; var startingPos:FlxPoint = strumLine.strumPos == null ? FlxPoint.get(strOffset, 50) : FlxPoint.get(strOffset, strumLine.strumPos[1]); - hudXStepper = new UINumericStepper(hudScaleStepper.x + 80 - 32 + 26, hudScaleStepper.y, startingPos.x, 0.01, 2, 0, 2, 84); + hudXStepper = new UINumericStepper(hudSpacingStepper.x + 80, hudScaleStepper.y, startingPos.x, 0.01, 2, 0, 2, 84); add(hudXStepper); addLabelOn(hudXStepper, TU.translate("charterStrumLine.hudPos")); @@ -157,11 +171,26 @@ class CharterStrumlineScreen extends UISubstateWindow { vocalsSuffixDropDown = new UIDropDown(typeDropdown.x, hudScaleStepper.y + 128, 200, 32, suffixList, strumLine.vocalsSuffix != null && strumLine.vocalsSuffix != "" ? suffixList.indexOf(strumLine.vocalsSuffix) : 0); add(vocalsSuffixDropDown); addLabelOn(vocalsSuffixDropDown, TU.translate("charterStrumLine.vocalSuffix")); + + keyCountStepper = new UINumericStepper(stagePositionDropdown.x, vocalsSuffixDropDown.y, strumLine.keyCount != null ? strumLine.keyCount : 4, 1, 0, 1, 1000, 84); + // if (Flags.CHARTER_ADVANCED_SETTINGS) { + add(keyCountStepper); + addLabelOn(keyCountStepper, "Key Count"); + // } + + strumLineCam = new HudCamera(); + strumLineCam.downscroll = Options.downscroll; + strumLineCam.bgColor = 0; + strumLineCam.alpha = 0; + FlxG.cameras.add(strumLineCam, false); + previewStrumLine = new CharterPreviewStrumLine(0, 0, 0, 1, 4, 0); + previewStrumLine.camera = strumLineCam; + add(previewStrumLine); + FlxTween.tween(strumLineCam, {alpha: 1}, 0.25, {ease: FlxEase.cubeOut}); } function saveStrumline() { - for (stepper in [hudXStepper, hudYStepper, hudScaleStepper]) - @:privateAccess stepper.__onChange(stepper.label.text); + UIUtil.confirmUISelections(this); var newStrumLine:ChartStrumLine = { characters: [ @@ -175,11 +204,32 @@ class CharterStrumlineScreen extends UISubstateWindow { strumPos: [0, hudYStepper.value], strumLinePos: hudXStepper.value, strumScale: hudScaleStepper.value, - vocalsSuffix: vocalsSuffixDropDown.options[vocalsSuffixDropDown.index] != "NONE" ? vocalsSuffixDropDown.options[vocalsSuffixDropDown.index] : "" + strumSpacing: hudSpacingStepper.value, + vocalsSuffix: vocalsSuffixDropDown.options[vocalsSuffixDropDown.index] != "NONE" ? vocalsSuffixDropDown.options[vocalsSuffixDropDown.index] : "", + keyCount: Std.int(keyCountStepper.value) }; if(!usesChartScrollSpeed.checked) newStrumLine.scrollSpeed = scrollSpeedStepper.value; if (onSave != null) onSave(newStrumLine); } + + override public function update(elapsed:Float) { + var scrollSpeed:Float = 0.0; + if (usesChartScrollSpeed.hovered || scrollSpeedStepper.hovered || scrollSpeedStepper.focused) + scrollSpeed = scrollSpeedStepper.value; + + previewStrumLine.visible = visibleCheckbox.checked; + + var xOffset:Float = StrumLine.calculateStartingXPos(hudXStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value)); + previewStrumLine.updatePos(xOffset, hudYStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value), scrollSpeed); + + super.update(elapsed); + } + + override public function destroy() { + super.destroy(); + FlxTween.cancelTweensOf(strumLineCam); + FlxG.cameras.remove(strumLineCam); + } } class CharacterButton extends UIButton { @@ -189,10 +239,11 @@ class CharacterButton extends UIButton { public var deleteIcon:FlxSprite; public function new(x:Float, y:Float, char:String, charsList:Array<String>, parent:UIButtonList<CharacterButton>) { - super(x, y, "", null, 250, 54); + super(x, y, null, null, 250, 54); charIcon = new HealthIcon(Character.getIconFromCharName(char)); - charIcon.scale.set(0.3, 0.3); + var size = Std.int(150 * 0.3); + charIcon.setUnstretchedGraphicSize(size, size, true); charIcon.updateHitbox(); charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2); charIcon.scrollFactor.set(1,1); @@ -204,14 +255,13 @@ class CharacterButton extends UIButton { textBox.antialiasing = true; textBox.onChange = function(char:String) { char = Character.getIconFromCharName(char); - var image = Paths.image("icons/" + char); - if(!Assets.exists(image)) - image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON); - charIcon.loadGraphic(image, true, 150, 150); + charIcon.setIcon(char); + charIcon.setUnstretchedGraphicSize(size, size, true); charIcon.updateHitbox(); + charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2); } - deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), "", function () { + deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), null, function () { parent.remove(this); }, 32); deleteButton.color = 0xFFFF0000; diff --git a/source/funkin/editors/charter/CharterWaveformHandler.hx b/source/funkin/editors/charter/CharterWaveformHandler.hx index 940c0321..2ba9756e 100644 --- a/source/funkin/editors/charter/CharterWaveformHandler.hx +++ b/source/funkin/editors/charter/CharterWaveformHandler.hx @@ -5,6 +5,7 @@ import flixel.util.FlxColor; import funkin.backend.shaders.CustomShader; import funkin.backend.system.Conductor; import openfl.display.BitmapData; +import openfl.display.ShaderInput; class CharterWaveformHandler extends FlxBasic { public var ampsNeeded:Float = 0; @@ -67,13 +68,18 @@ class CharterWaveformHandler extends FlxBasic { var waveData:BitmapData = waveDatas.get(name); - var waveShader:CustomShader = new CustomShader("engine/editorWaveforms"); + var waveShader:CustomShader = new CustomShader(Options.charterRainbowWaveforms ? "engine/editorWaveformsRainbow" : "engine/editorWaveforms"); waveShader.data.waveformSize.value = [waveData.width, waveData.height]; - waveShader.data.waveformTexture.input = waveData; + var waveformInput:ShaderInput<BitmapData> = cast waveShader.data.waveformTexture; + waveformInput.input = waveData; + waveformInput.filter = NEAREST; // force it to be nearest, incase someone changed it waveShader.data.textureRes.value = [0, 0]; waveShader.data.pixelOffset.value = [0]; waveShader.data.lowDetail.value = [Options.charterLowDetailWaveforms]; + if (Options.charterRainbowWaveforms) + waveShader.data.time.value = [0]; + waveShaders.set(name, waveShader); return waveShader; } diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index 7fa1c8c8..fbbc1366 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -3,33 +3,53 @@ package funkin.editors.charter; import flixel.group.FlxGroup; import flixel.text.FlxText.FlxTextFormat; import flixel.text.FlxText.FlxTextFormatMarkerPair; -import funkin.backend.chart.ChartData.ChartMetaData; +import funkin.backend.chart.ChartData; +import funkin.backend.chart.FNFLegacyParser.SwagSong; +import funkin.backend.chart.PsychParser; +import funkin.backend.chart.VSliceParser; +import funkin.backend.utils.ZipUtil.ZipReader; +import funkin.game.HealthIcon; +import haxe.Json; import haxe.io.Bytes; +import haxe.io.BytesInput; typedef SongCreationData = { var meta:ChartMetaData; var instBytes:Bytes; var voicesBytes:Bytes; + @:optional var playerVocals:Bytes; + @:optional var oppVocals:Bytes; } class SongCreationScreen extends UISubstateWindow { - private var onSave:Null<SongCreationData> -> Void = null; + private var onSave:Null<SongCreationData> -> Null<String -> SongCreationData -> Void> -> Void = null; public var songNameTextBox:UITextBox; public var bpmStepper:UINumericStepper; public var beatsPerMeasureStepper:UINumericStepper; - public var stepsPerBeatStepper :UINumericStepper; + public var denominatorStepper:UINumericStepper; public var instExplorer:UIFileExplorer; public var voicesExplorer:UIFileExplorer; + public var importFrom:UIButton; public var displayNameTextBox:UITextBox; public var iconTextBox:UITextBox; - public var iconSprite:FlxSprite; + public var iconSprite:HealthIcon; public var opponentModeCheckbox:UICheckbox; public var coopAllowedCheckbox:UICheckbox; public var colorWheel:UIColorwheel; public var difficultiesTextBox:UITextBox; + public var engineDropdown:UIDropDown; + public var createSong:UIButton; + + public var importInstExplorer:UIFileExplorer; + public var importVoicesExplorer:UIFileExplorer; + + public var importIdTextBox:UITextBox; + public var importChartFile:UIFileExplorer; + public var importMetaFile:UIFileExplorer; + public var backButton:UIButton; public var saveButton:UIButton; public var closeButton:UIButton; @@ -37,11 +57,21 @@ class SongCreationScreen extends UISubstateWindow { public var songDataGroup:FlxGroup = new FlxGroup(); public var menuDataGroup:FlxGroup = new FlxGroup(); + public var selectFormatGroup:FlxGroup = new FlxGroup(); + public var importAudioGroup:FlxGroup = new FlxGroup(); + public var importDataGroup:FlxGroup = new FlxGroup(); + public var pages:Array<FlxGroup> = []; public var pageSizes:Array<FlxPoint> = []; + + public var importPages:Array<FlxGroup> = []; + public var importPageSizes:Array<FlxPoint> = []; + + public var isImporting = false; + public var curPage:Int = 0; - public function new(?onSave:SongCreationData->Void) { + public function new(?onSave:SongCreationData -> Null<String -> SongCreationData -> Void> -> Void) { super(); if (onSave != null) this.onSave = onSave; } @@ -61,9 +91,9 @@ class SongCreationScreen extends UISubstateWindow { super.create(); function addLabelOn(ui:UISprite, text:String):UIText { - var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); - ui.members.push(text); - return text; + var label:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(label); + return label; } var songTitle:UIText; @@ -83,12 +113,11 @@ class SongCreationScreen extends UISubstateWindow { songDataGroup.add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22)); - stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54); - songDataGroup.add(stepsPerBeatStepper); + denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54); + songDataGroup.add(denominatorStepper); - var voicesUIText:UIText = null; - - instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (res) { + instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (path, res) { + if (path == null || res == null) return; var audioPlayer:UIAudioPlayer = new UIAudioPlayer(instExplorer.x + 8, instExplorer.y + 8, res); instExplorer.members.push(audioPlayer); instExplorer.uiElement = audioPlayer; @@ -98,7 +127,8 @@ class SongCreationScreen extends UISubstateWindow { translate("instAudio"), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); - voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (res) { + voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (path, res) { + if (path == null || res == null) return; var audioPlayer:UIAudioPlayer = new UIAudioPlayer(voicesExplorer.x + 8, voicesExplorer.y + 8, res); voicesExplorer.members.push(audioPlayer); voicesExplorer.uiElement = audioPlayer; @@ -112,6 +142,13 @@ class SongCreationScreen extends UISubstateWindow { addLabelOn(voicesExplorer, "Vocal Audio File"); + importFrom = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "Import From...", function() { + winTitle = "Importing Song"; + isImporting = true; + updatePagesTexts(); + refreshPages(); + }, 150); + songDataGroup.add(importFrom); var menuTitle:UIText; menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translateMeta("menusData"), 28)); @@ -145,7 +182,66 @@ class SongCreationScreen extends UISubstateWindow { for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox]) {checkbox.y += 6; checkbox.x += 4;} + var menuTitle:UIText; + selectFormatGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Import From:", 28)); + + engineDropdown = new UIDropDown(menuTitle.x, menuTitle.y + menuTitle.height + 36, 480, 32, ["Psych/Legacy FNF", "V-Slice", "V-Slice Project (.fnfc)"]); + selectFormatGroup.add(engineDropdown); + addLabelOn(engineDropdown, "Chart Format"); + + createSong = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "< Back", function() { + winTitle = "Creating New Song"; + isImporting = false; + updatePagesTexts(); + refreshPages(); + }, 120); + selectFormatGroup.add(createSong); + + var menuTitle:UIText; + importAudioGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Add Audios", 28)); + + importInstExplorer = new UIFileExplorer(menuTitle.x, menuTitle.y + menuTitle.height + 36, null, null, Flags.SOUND_EXT, function (path, res) { + if (path == null || res == null) return; + var audioPlayer:UIAudioPlayer = new UIAudioPlayer(importInstExplorer.x + 8, importInstExplorer.y + 8, res); + importInstExplorer.members.push(audioPlayer); + importInstExplorer.uiElement = audioPlayer; + }); + importAudioGroup.add(importInstExplorer); + addLabelOn(importInstExplorer, "Inst Audio File").applyMarkup( + "Inst Audio File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + importVoicesExplorer = new UIFileExplorer(importInstExplorer.x + 320 + 26, importInstExplorer.y, null, null, Flags.SOUND_EXT, function (path, res) { + if (path == null || res == null) return; + var audioPlayer:UIAudioPlayer = new UIAudioPlayer(importVoicesExplorer.x + 8, importVoicesExplorer.y + 8, res); + importVoicesExplorer.members.push(audioPlayer); + importVoicesExplorer.uiElement = audioPlayer; + }); + importAudioGroup.add(importVoicesExplorer); + addLabelOn(importVoicesExplorer, "Vocal Audio File"); + + var menuTitle:UIText; + importDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Add Data", 28)); + + importDataGroup.add(importIdTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36)); + addLabelOn(importIdTextBox, "Song file name").applyMarkup( + "Song file name $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + importChartFile = new UIFileExplorer(importIdTextBox.x, importIdTextBox.y + importIdTextBox.height + 56, null, null, "fnfc", function (_, _) importIdTextBox.label.text = new haxe.io.Path(importChartFile.filePath).file); + importDataGroup.add(importChartFile); + addLabelOn(importChartFile, "Data/Chart File").applyMarkup( + "Data/Chart File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + importMetaFile = new UIFileExplorer(importChartFile.x + 320 + 26, importChartFile.y, null, null, "json"); + importDataGroup.add(importMetaFile); + addLabelOn(importMetaFile, "Meta File").applyMarkup( + "Meta File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { + var pages = isImporting ? importPages : pages; if (curPage == pages.length-1) { saveSongInfo(); close(); @@ -178,17 +274,41 @@ class SongCreationScreen extends UISubstateWindow { pages.push(cast add(menuDataGroup)); pageSizes.push(FlxPoint.get(748 - 32 + 40, 400)); + importPages.push(cast add(selectFormatGroup)); + importPageSizes.push(FlxPoint.get(500, 250)); + + importPages.push(cast add(importAudioGroup)); + importPageSizes.push(FlxPoint.get(705, 250)); + + importPages.push(cast add(importDataGroup)); + importPageSizes.push(FlxPoint.get(705, 300)); + refreshPages(); updatePagesTexts(); } public override function update(elapsed:Float) { - if (curPage == 0) { - if (instExplorer.file != null) + if (isImporting) + { + var name = engineDropdown.options[engineDropdown.index]; + var project = name == "V-Slice Project (.fnfc)"; + + if (curPage == 1) { + importInstExplorer.selectable = importVoicesExplorer.selectable = !project; + saveButton.selectable = project ? true : (importInstExplorer.file != null); + } else if (curPage == 2) { + importIdTextBox.selectable = !project; + importChartFile.fileType = project ? "fnfc" : "json"; + importMetaFile.selectable = name == "V-Slice"; + saveButton.selectable = importChartFile.file != null && (!importMetaFile.selectable || importMetaFile.file != null) && (!importIdTextBox.selectable || importIdTextBox.label.text.trim().length > 0); + } else saveButton.selectable = true; - else saveButton.selectable = false; - } else - saveButton.selectable = true; + } else { + if (curPage == 0) + saveButton.selectable = (instExplorer.file != null); + else + saveButton.selectable = true; + } saveButton.alpha = saveButton.field.alpha = saveButton.selectable ? 1 : 0.4; super.update(elapsed); @@ -196,18 +316,23 @@ class SongCreationScreen extends UISubstateWindow { function refreshPages() { for (i=>page in pages) - page.visible = page.exists = i == curPage; + page.visible = page.exists = i == curPage && !isImporting; + + for (i=>page in importPages) + page.visible = page.exists = i == curPage && isImporting; } function updatePagesTexts() { + var pageSizes = isImporting ? importPageSizes : pageSizes; + var pages = isImporting ? importPages : pages; windowSpr.bWidth = Std.int(pageSizes[curPage].x); windowSpr.bHeight = Std.int(pageSizes[curPage].y); titleSpr.x = windowSpr.x + 25; titleSpr.y = windowSpr.y + ((30 - titleSpr.height) / 2); - saveButton.field.text = curPage == pages.length-1 ? TU.translate("editor.saveClose") : translate("next") + " >"; - titleSpr.text = translate("win-title") + ' (${curPage+1}/${pages.length})'; + saveButton.field.text = curPage == pages.length-1 ? "Save & Close" : 'Next >'; + titleSpr.text = '$winTitle (${curPage+1}/${pages.length})'; backButton.field.text = '< ' + translate("back"); backButton.visible = backButton.exists = curPage > 0; @@ -215,52 +340,121 @@ class SongCreationScreen extends UISubstateWindow { backButton.x = (saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - 125) - 20 - saveButton.bWidth; closeButton.x = (curPage > 0 ? backButton : saveButton).x - 20 - saveButton.bWidth; - for (button in [saveButton, backButton, closeButton]) + for (button in [saveButton, backButton, closeButton, importFrom, createSong]) button.y = windowSpr.y + windowSpr.bHeight - 16 - 32; } function updateIcon(icon:String) { - if (iconSprite == null) menuDataGroup.add(iconSprite = new FlxSprite()); + if (iconSprite == null) menuDataGroup.add(iconSprite = new HealthIcon()); - if (iconSprite.animation.exists(icon)) return; - @:privateAccess iconSprite.animation.clearAnimations(); - - var path:String = Paths.image('icons/$icon'); - if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON); - - iconSprite.loadGraphic(path, true, 150, 150); - iconSprite.animation.add(icon, [0], 0, false); - iconSprite.antialiasing = true; - iconSprite.animation.play(icon); - - iconSprite.scale.set(0.5, 0.5); + iconSprite.setIcon(icon); + var size = Std.int(150 * 0.5); + iconSprite.setUnstretchedGraphicSize(size, size, true); iconSprite.updateHitbox(); - iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); + iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2)); + iconSprite.scrollFactor.set(1, 1); } function saveSongInfo() { - for (stepper in [bpmStepper, beatsPerMeasureStepper, stepsPerBeatStepper]) - @:privateAccess stepper.__onChange(stepper.label.text); + if (isImporting) + { + try switch(engineDropdown.options[engineDropdown.index]) + { + case "V-Slice Project (.fnfc)": + var files:Map<String, Any> = []; + for (field in new ZipReader(new BytesInput(importChartFile.file)).read()) { + var fileName = field.fileName; + var fileContent = field.data; + files.set(fileName, fileContent); + } + saveFromVSlice(files); + case "V-Slice": + var songId = importIdTextBox.label.text; + var files:Map<String, Any> = []; + files.set('${songId}-metadata.json', importMetaFile.file); + files.set('${songId}-chart.json', importChartFile.file); + files.set('Inst.${Flags.SOUND_EXT}', importInstExplorer.file); + files.set('Voices.${Flags.SOUND_EXT}', importVoicesExplorer.file); + saveFromVSlice(files, songId); + default /*"Psych/Legacy FNF"*/: + var songId = importIdTextBox.label.text; + var oldChart:SwagSong = Json.parse(cast importChartFile.file); + var base:ChartData = { + strumLines: [], + noteTypes: [], + events: [], + meta: {name: null}, + scrollSpeed: Flags.DEFAULT_SCROLL_SPEED, + stage: Flags.DEFAULT_STAGE, + codenameChart: true + }; + PsychParser.parse(oldChart, base); + if (onSave != null) onSave({ + meta: { + name: songId, + difficulties: [songId], + bpm: oldChart.bpm, + beatsPerMeasure: oldChart.beatsPerMeasure, + stepsPerBeat: oldChart.stepsPerBeat, + displayName: oldChart.song + }, + instBytes: importInstExplorer.file, + voicesBytes: importVoicesExplorer.file, + }, (songFolder:String, creation:SongCreationData) -> { + #if sys + CoolUtil.safeSaveFile('$songFolder/charts/${songId}.json', Json.stringify(base, Flags.JSON_PRETTY_PRINT)); + #end + }); + } catch (e) { + openSubState(new UIWarningSubstate("Importing Song/Charts: Error!", Std.string(e), [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } + } else { + for (stepper in [bpmStepper, beatsPerMeasureStepper, denominatorStepper]) + @:privateAccess stepper.__onChange(stepper.label.text); - var meta:ChartMetaData = { - name: songNameTextBox.label.text, - bpm: bpmStepper.value, - beatsPerMeasure: Std.int(beatsPerMeasureStepper.value), - stepsPerBeat: Std.int(stepsPerBeatStepper.value), - displayName: displayNameTextBox.label.text, - icon: iconTextBox.label.text, - color: colorWheel.curColorString, - parsedColor: colorWheel.curColor, - opponentModeAllowed: opponentModeCheckbox.checked, - coopAllowed: coopAllowedCheckbox.checked, - difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()], - }; + var meta:ChartMetaData = { + name: songNameTextBox.label.text, + bpm: bpmStepper.value, + beatsPerMeasure: Std.int(beatsPerMeasureStepper.value), + stepsPerBeat: Std.int(16 / denominatorStepper.value), + displayName: displayNameTextBox.label.text, + icon: iconTextBox.label.text, + color: colorWheel.curColor, + opponentModeAllowed: opponentModeCheckbox.checked, + coopAllowed: coopAllowedCheckbox.checked, + difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()], + }; + + if (onSave != null) onSave({ + meta: meta, + instBytes: instExplorer.file, + voicesBytes: voicesExplorer.file + }, null); + } + } + + function saveFromVSlice(files:Map<String, Any>, ?name:String) { + var songId = name == null && files.exists("manifest.json") ? Json.parse(files.get("manifest.json")).songId : name; + var vslicemeta:SwagMetadata = Json.parse(files.get('${songId}-metadata.json')); + var vslicechart:NewSwagSong = Json.parse(files.get('${songId}-chart.json')); + var playData = vslicemeta.playData; + + var meta:ChartMetaData = {name: songId}; + var diffCharts:Array<ChartDataWithInfo> = []; + VSliceParser.parse(vslicemeta, vslicechart, meta, diffCharts, songId); if (onSave != null) onSave({ meta: meta, - instBytes: instExplorer.file, - voicesBytes: voicesExplorer.file + instBytes: files.get('Inst.${Flags.SOUND_EXT}'), + voicesBytes: files.get('Voices.${Flags.SOUND_EXT}'), //it may exist + playerVocals: files.get('Voices-${playData.characters.playerVocals != null ? playData.characters.playerVocals[0] : playData.characters.player}.${Flags.SOUND_EXT}'), + oppVocals: files.get('Voices-${playData.characters.opponentVocals != null ? playData.characters.opponentVocals[0] : playData.characters.opponent}.${Flags.SOUND_EXT}'), + }, (songFolder:String, creation:SongCreationData) -> { + #if sys + for (diff in diffCharts) CoolUtil.safeSaveFile('$songFolder/charts/${diff.diffName}.json', Json.stringify(diff.chart, Flags.JSON_PRETTY_PRINT)); + #end }); } - } \ No newline at end of file diff --git a/source/funkin/editors/extra/AxisGizmo.hx b/source/funkin/editors/extra/AxisGizmo.hx new file mode 100644 index 00000000..2d4c7b8d --- /dev/null +++ b/source/funkin/editors/extra/AxisGizmo.hx @@ -0,0 +1,31 @@ +package funkin.editors.extra; + +class AxisGizmo extends FlxObject { + var _worldPos:FlxPoint = FlxPoint.get(); + + public override function draw() { + super.draw(); + + for (camera in cameras) { + if (DrawUtil.line == null) DrawUtil.createDrawers(); + DrawUtil.line.camera = camera; DrawUtil.line.alpha = 0.85; + + CoolUtil.pointToScreenPosition(FlxPoint.weak(), FlxG.camera, _worldPos); + // Stole these colors directly from godot >:D -luanr + DrawUtil.drawLine( + FlxPoint.weak(_worldPos.x, 0), + FlxPoint.weak(_worldPos.x, camera.height), + 1, 0xFF76BC02); + + DrawUtil.drawLine( + FlxPoint.weak(0, _worldPos.y), + FlxPoint.weak(camera.width, _worldPos.y), + 1, 0xFFD72C47); + } + } + + public override function destroy() { + super.destroy(); + _worldPos.put(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/extra/CameraHoverDummy.hx b/source/funkin/editors/extra/CameraHoverDummy.hx new file mode 100644 index 00000000..5ad69391 --- /dev/null +++ b/source/funkin/editors/extra/CameraHoverDummy.hx @@ -0,0 +1,21 @@ +package funkin.editors.extra; + +class CameraHoverDummy extends UISprite { + var parent:FlxBasic; + public function new(?parent:FlxBasic, scroll:FlxPoint) { + super(); + this.parent = parent; + cameras = parent == null ? [FlxG.camera] : parent.cameras; + scrollFactor.copyFrom(scroll); + } + + public override function updateButton() { + camera.getViewRect(__rect); + UIState.state.updateRectButtonHandler(this, __rect, onHovered); + } + + public override function draw() { + @:privateAccess + __lastDrawCameras = cameras.copy(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/extra/PropertyButton.hx b/source/funkin/editors/extra/PropertyButton.hx index 0ffad946..1f7bbce2 100644 --- a/source/funkin/editors/extra/PropertyButton.hx +++ b/source/funkin/editors/extra/PropertyButton.hx @@ -6,28 +6,36 @@ class PropertyButton extends UIButton { public var deleteButton:UIButton; public var deleteIcon:FlxSprite; - public function new(property, value, parent) { - super(0, 0, '', function () {}, 280, 35); - members.push(propertyText = new UITextBox(5, 5, property, 100, 25)); - members.push(valueText = new UITextBox(propertyText.x + propertyText.bWidth + 10, 5, value, 135, 25)); + public function new(property, value, parent, width:Int = 280, height:Int = 35, nameWidth:Int = 100, valueWidth:Int = 135, inputHeight:Int = 25) { + super(0, 0, '', function () {}, width, height); + members.push(propertyText = new UITextBox(5, 5, property, nameWidth, inputHeight)); + members.push(valueText = new UITextBox(propertyText.x + propertyText.bWidth + 10, 5, value, valueWidth, inputHeight)); - deleteButton = new UIButton(valueText.x + 135, bHeight/2 - (25/2), "", function () { + var deleteSize = height - 5 * 2; + deleteButton = new UIButton(valueText.x + 135, bHeight/2 - (deleteSize/2), null, function () { parent.remove(this); - }, 25, 25); + }, deleteSize, deleteSize); deleteButton.color = 0xFFFF0000; deleteButton.autoAlpha = false; members.push(deleteButton); - deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button')); + deleteIcon = new FlxSprite(deleteButton.x, deleteButton.y).loadGraphic(Paths.image('editors/delete-button')); deleteIcon.antialiasing = false; members.push(deleteIcon); + + updatePos(); } public override function update(elapsed) { super.update(elapsed); - propertyText.follow(this, 5, 5); - valueText.follow(this, propertyText.x + propertyText.bWidth, 5); - deleteButton.follow(this, valueText.x + 135, bHeight/2 - (25/2)); - deleteIcon.follow(deleteButton, 5, 5); + updatePos(); + } + + public function updatePos() { + propertyText.follow(this, 5, bHeight/2 - (propertyText.bHeight/2)); + valueText.follow(this, propertyText.x + propertyText.bWidth, bHeight/2 - (valueText.bHeight/2)); + //deleteButton.follow(this, valueText.x + 135, bHeight/2 - (25/2)); + deleteButton.follow(this, bWidth - deleteButton.bWidth - 5, bHeight/2 - deleteButton.bHeight/2); + deleteIcon.follow(deleteButton, deleteButton.bWidth / 2 - deleteIcon.width / 2, deleteButton.bHeight / 2 - deleteIcon.height / 2); } } \ No newline at end of file diff --git a/source/funkin/editors/import.hx b/source/funkin/editors/import.hx index 3d64ccc2..de888fa0 100644 --- a/source/funkin/editors/import.hx +++ b/source/funkin/editors/import.hx @@ -3,6 +3,7 @@ package funkin.editors; import flixel.math.FlxPoint; import funkin.editors.ui.*; +import funkin.editors.ui.notifications.*; using funkin.editors.ui.UIUtil; #end \ No newline at end of file diff --git a/source/funkin/editors/stage/StageCreationScreen.hx b/source/funkin/editors/stage/StageCreationScreen.hx new file mode 100644 index 00000000..957ff896 --- /dev/null +++ b/source/funkin/editors/stage/StageCreationScreen.hx @@ -0,0 +1,62 @@ +package funkin.editors.stage; + +typedef StageCreationData = { + var name:String; + var path:String; +} + +class StageCreationScreen extends UISubstateWindow { + private var onSave:Null<StageCreationData> -> Void = null; + + public var stageNameTextBox:UITextBox; + public var stagePathTextBox:UITextBox; + public var voicesExplorer:UIFileExplorer; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public function new(?onSave:StageCreationData->Void) { + super(); + if (onSave != null) this.onSave = onSave; + } + + public override function create() { + winTitle = "Creating New Stage"; + + winWidth = 748 - 32 + 40; + winHeight = 220; + + super.create(); + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + var stageInfo:UIText; + add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Stage Info", 28)); + + add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, "Stage Name")); + addLabelOn(stageNameTextBox, "Stage Name"); + + add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, "Stage Path")); + addLabelOn(stagePathTextBox, "Stage Path"); + + add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveStageInfo(); + close(); + }, 125)); + + add(closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", close, 125)); + closeButton.color = 0xFFFF0000; + } + + function saveStageInfo() { + if (onSave != null) onSave({ + name: stageNameTextBox.label.text, + path: stagePathTextBox.label.text + }); + } + +} \ No newline at end of file diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx new file mode 100644 index 00000000..4e84894e --- /dev/null +++ b/source/funkin/editors/stage/StageEditor.hx @@ -0,0 +1,1368 @@ +package funkin.editors.stage; + +import flixel.math.FlxAngle; +import flixel.math.FlxPoint; +import flixel.math.FlxRect; +import flixel.util.FlxColor; +import flixel.util.FlxSort; +import funkin.backend.system.framerate.Framerate; +import funkin.backend.utils.XMLUtil.AnimData; +import funkin.editors.stage.elements.*; +import funkin.editors.stage.elements.StageSpriteButton.StageSpriteEditScreen; +import funkin.editors.ui.UIContextMenu.UIContextMenuOption; +import funkin.editors.extra.AxisGizmo; +import funkin.game.Character; +import funkin.game.Stage; +import haxe.xml.Access; +import haxe.xml.Printer; +import lime.ui.MouseCursor; +import openfl.ui.Mouse; + +using funkin.backend.utils.MatrixUtil; + +@:access(flixel.FlxSprite) +class StageEditor extends UIState { + static var __stage:String; + public var stage:Stage; + + private var _point:FlxPoint = new FlxPoint(); + + public static var instance(get, null):StageEditor; + + private static inline function get_instance() + return FlxG.state is StageEditor ? cast FlxG.state : null; + + public var topMenu:Array<UIContextMenuOption>; + public var topMenuSpr:UITopMenu; + + public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>(); + public var stageSpritesWindow:StageSpritesWindow; + + public var xmlMap:Map<FlxObject, Access> = new Map<FlxObject, Access>(); + + public var chars:Array<Character> = []; + public var charMap:Map<String, Character> = []; + + public var stageCamera:FlxCamera; + //public var guideCamera:FlxCamera; + public var uiCamera:FlxCamera; + + public var gizmosCamera:FlxCamera; + public var axisGizmo:AxisGizmo; + + public var selection:Selection = new Selection(); + public var mouseMode:StageEditorMouseMode = NONE; + public var mousePoint:FlxPoint = new FlxPoint(); + public var clickPoint:FlxPoint = new FlxPoint(); + public var storedPos:FlxPoint = new FlxPoint(); + public var storedScale:FlxPoint = new FlxPoint(); + public var storedSkew:FlxPoint = new FlxPoint(); + public var storedAngle:Float = 0; + public var angleOffset:Float = 0; + + public var showCharacters:Bool = true; + + public static inline var SPRITE_WINDOW_WIDTH:Int = 400; + public static inline var SPRITE_WINDOW_BUTTON_HEIGHT:Int = 64; + + public var undos:UndoList<StageChange> = new UndoList<StageChange>(); + + public inline static function exID(id:String) { + return "stageEditor." + id; + } + + public function new(stage:String) { + super(); + if (stage != null) __stage = stage; + } + + public override function create() { + super.create(); + + WindowUtils.suffix = " (Stage Editor)"; + SaveWarning.selectionClass = StageSelection; + SaveWarning.saveFunc = () -> {_file_save(null);}; + + topMenu = [ + { + label: "File", + childs: [ + { + label: "Save", + keybind: [CONTROL, S], + onSelect: _file_save, + }, + { + label: "Save As...", + keybind: [CONTROL, SHIFT, S], + onSelect: _file_saveas, + }, + null, + { + label: "Exit", + onSelect: _file_exit + } + ] + }, + { + label: "Edit", + childs: [ + { + label: "Undo", + keybind: [CONTROL, Z], + onSelect: _edit_undo, + }, + { + label: "Redo", + keybind: [CONTROL, SHIFT, Z], + onSelect: _edit_redo, + }, + null, + { + label: "Edit Stage Info", + onSelect: (_) -> { + openSubState(new UISoftcodedWindow( + "layouts/stage/stageInfoScreen", + [ + "winTitle" => "Editing Stage Info", + "hasSaveButton" => true, + "hasCloseButton" => true, + "stage" => stage, + "Stage" => Stage, + "exID" => exID, + "CEditInfo" => StageChange.CEditInfo + ] + )); + }, + } + ] + }, + { + label: "Select", + childs: [ + { + label: "All", + keybind: [CONTROL, A], + onSelect: (_) -> _select_all(_), + }, + { + label: "Deselect", + keybind: [CONTROL, D], + onSelect: (_) -> _select_deselect(_), + }, + { + label: "Inverse", + keybind: [CONTROL, SHIFT, I], + onSelect: (_) -> _select_inverse(_), + }, + ] + }, + { + label: "View", + childs: [ + { + label: "Zoom in", + keybind: [CONTROL, NUMPADPLUS], + onSelect: _view_zoomin + }, + { + label: "Zoom out", + keybind: [CONTROL, NUMPADMINUS], + onSelect: _view_zoomout + }, + { + label: "Reset zoom", + keybind: [CONTROL, NUMPADZERO], + onSelect: _view_zoomreset + }, + null, + { + label: "Focus Dad", + //keybind: [CONTROL, NUMPADZERO], + onSelect: _view_focusdad + }, + { + label: "Focus Gf", + //keybind: [CONTROL, NUMPADZERO], + onSelect: _view_focusgf + }, + { + label: "Focus BF", + //keybind: [CONTROL, NUMPADZERO], + onSelect: _view_focusbf + }, + // TODO: add support for custom character focus + ] + }, + { + label: "Editor", + childs: [ + { + label: "Show Characters", + //keybind: [CONTROL, NUMPADMINUS], + onSelect: _editor_showCharacters, + icon: showCharacters ? 1 : 0 + } + ] + }, + ]; + + stageCamera = FlxG.camera; + + gizmosCamera = new FlxCamera(); + gizmosCamera.bgColor = 0; + FlxG.cameras.add(gizmosCamera, false); + + axisGizmo = new AxisGizmo(); + axisGizmo.cameras = [gizmosCamera]; + add(axisGizmo); + + uiCamera = new FlxCamera(); + uiCamera.bgColor = 0; + + uiCameras = [uiCamera]; + + //FlxG.cameras.add(guideCamera, false); + FlxG.cameras.add(uiCamera, false); + + // Load from xml + var order:Array<Dynamic> = []; + var orderNodes:Array<Access> = []; + + loadStage(order, orderNodes); + + // for (sprite => node in xmlMap) + // sprite.cameras = [stageCamera]; + + topMenuSpr = new UITopMenu(topMenu); + topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; + + stageSpritesWindow = new StageSpritesWindow(Std.int(FlxG.width - 400), 25); + for (i=>sprite in order) { + var xml = (sprite != null) ? xmlMap.get(sprite) : orderNodes[i]; + if (xml != null) { + if (sprite is Character) { + var char:Character = cast sprite; + var button = new StageCharacterButton(0,0, char, xml); + char.extra.set(exID("button"), button); + stageSpritesWindow.add(button); + } else if (sprite is FunkinSprite) { + var sprite:FunkinSprite = cast sprite; + var type = sprite.extra.get(exID("type")); + var button:StageElementButton = (type == "box" || type == "solid") ? new StageSolidButton(0,0, sprite, xml) : new StageSpriteButton(0,0, sprite, xml); + sprite.extra.set(exID("button"), button); + stageSpritesWindow.add(button); + } else if(sprite == null) { + var basic = new FlxBasic(); // prevent awkward layering + insert(i, basic); + var button = new StageUnknownButton(0,0, basic, xml); + stageSpritesWindow.add(button); + } + } + } + uiGroup.add(stageSpritesWindow); + + add(topMenuSpr); + add(uiGroup); + + if(Framerate.isLoaded) { + Framerate.fpsCounter.alpha = 0.4; + Framerate.memoryCounter.alpha = 0.4; + Framerate.codenameBuildField.alpha = 0.4; + } + + // DiscordUtil.call("onEditorLoaded", ["Stage Editor", __stage]); + } + + function loadStage(order:Array<Dynamic>, orderNodes:Array<Access>) { + stage = new Stage(__stage, this, false); + stage.onXMLLoaded = function(xml:Access, elems:Array<Access>) { + return elems; + } + stage.onNodeFinished = function(node:Access, sprite:Dynamic) { + if(sprite is FlxSprite) { + sprite.moves = false; + } + if (sprite is FunkinSprite) { + sprite.animEnabled = false; + //sprite.zoomFactorEnabled = false; + } + } + stage.onNodeLoaded = function(node:Access, sprite:Dynamic):Dynamic { + var parent = new Access(node.x.parent); + //var name = ""; + //trace(node, sprite); + if(sprite is FlxSprite) { + //sprite.forceIsOnScreen = true; // hack + } + if(sprite is FunkinSprite) { + var sprite:FunkinSprite = cast sprite; + //name = sprite.name; + sprite.extra.set(exID("node"), node); + sprite.extra.set(exID("type"), node.name); + sprite.extra.set(exID("imageFile"), '${node.getAtt("sprite").getDefault(sprite.name)}'); + sprite.extra.set(exID("parentNode"), parent); + sprite.extra.set(exID("highMemory"), parent.name == "highMemory"); + sprite.extra.set(exID("lowMemory"), parent.name == "lowMemory"); + //sprite.active = false; + } + if(sprite is StageCharPos) + sprite = prepareCharacter(cast sprite, node); + order.push(sprite); + orderNodes.push(node); + xmlMap.set(sprite, node); + + return sprite; + } + stage.loadXml(stage.stageXML, true); + + if (!charMap.exists("NO_DELETE_girlfriend")) { + var xml = Xml.createElement("girlfriend"); + stage.stageXML.x.addChild(xml); + var node = new Access(xml); + var sprite = prepareCharacter(stage.characterPoses["girlfriend"], node); + order.push(sprite); + orderNodes.push(node); + xmlMap.set(sprite, node); + } + if (!charMap.exists("NO_DELETE_dad")) { + var xml = Xml.createElement("dad"); + stage.stageXML.x.addChild(xml); + var node = new Access(xml); + var sprite = prepareCharacter(stage.characterPoses["dad"], node); + order.push(sprite); + orderNodes.push(node); + xmlMap.set(sprite, node); + } + if (!charMap.exists("NO_DELETE_boyfriend")) { + var xml = Xml.createElement("boyfriend"); + stage.stageXML.x.addChild(xml); + var node = new Access(xml); + var sprite = prepareCharacter(stage.characterPoses["boyfriend"], node); + order.push(sprite); + orderNodes.push(node); + xmlMap.set(sprite, node); + } + + add(stage); + + setZoom(stage.defaultZoom); + } + + function prepareCharacter(charPos:StageCharPos, node:Access):Character { + var parent = new Access(node.x.parent); + + var charName = switch(node.name) { + case "dad" | "opponent": "dad"; + case "gf" | "girlfriend": "gf"; + case "bf" | "boyfriend" | "player": "bf"; + default: (charPos.flipX) ? "bf" : "dad"; + } + + var char = new Character(0,0, charName, charPos.flipX, true); + charName = switch(node.name) { + case "dad" | "opponent": "NO_DELETE_dad"; + case "gf" | "girlfriend": "NO_DELETE_girlfriend"; + case "bf" | "boyfriend" | "player": "NO_DELETE_boyfriend"; + default: node.att.name; + } + char.name = charName; + char.debugMode = true; + // Play first anim, and make it the last frame + var animToPlay = char.getAnimOrder()[0]; + char.playAnim(animToPlay, true, NONE); + var lastIndx = (char.animateAtlas != null) ? + char.animateAtlas.anim.length - 1 : + char.animation.curAnim.numFrames - 1; + char.playAnim(animToPlay, true, NONE, false, lastIndx); + char.stopAnimation(); + + // Add it to the stage + char.visible = true; + char.alpha = 0.75; + char.extra.set(exID("node"), node); + char.extra.set(exID("spacingX"), charPos.charSpacingX); + char.extra.set(exID("spacingY"), charPos.charSpacingY); + char.extra.set(exID("camX"), charPos.camxoffset); + char.extra.set(exID("camY"), charPos.camyoffset); + + char.extra.set(exID("parentNode"), parent); + char.extra.set(exID("highMemory"), parent.name == "highMemory"); + char.extra.set(exID("lowMemory"), parent.name == "lowMemory"); + + chars.push(char); + stage.applyCharStuff(char, charPos.name, 0); + charMap[charName] = char; + + remove(charPos, true); + charPos.destroy(); + return char; + } + + override function destroy() { + super.destroy(); + nextScroll = FlxDestroyUtil.destroy(nextScroll); + if(Framerate.isLoaded) { + Framerate.fpsCounter.alpha = 1; + Framerate.memoryCounter.alpha = 1; + Framerate.codenameBuildField.alpha = 1; + } + } + + //private var movingCam:Bool = false; + //private var camDragSpeed:Float = 1.2; + + private var movedTillRel:FlxPoint = FlxPoint.get(0,0); + private var nextScroll:FlxPoint = FlxPoint.get(0,0); + + public override function update(elapsed:Float) { + super.update(elapsed); + + if (true) { + if(FlxG.keys.justPressed.ANY) + UIUtil.processShortcuts(topMenu); + } + + //if (character != null) + // characterPropertiresWindow.characterInfo.text = '${character.getNameList().length} Animations\nFlipped: ${character.flipX}\nSprite: ${character.sprite}\nAnim: ${character.getAnimName()}\nOffset: (${character.frameOffset.x}, ${character.frameOffset.y})'; + + currentCursor = ARROW; + + FlxG.mouse.getWorldPosition(stageCamera, mousePoint); + if ((!stageSpritesWindow.hovered && !stageSpritesWindow.dragging) && !topMenuSpr.hovered) { + if (FlxG.mouse.wheel != 0) { + zoom += 0.25 * FlxG.mouse.wheel; + updateZoom(); + } + + var prevMode = mouseMode; + + // TODO: make this work with multiple selections + if(selection.length == 1) { + for(sprite in selection) { + if(sprite is FunkinSprite) { + handleSelection(cast sprite); + } + } + } + + //if (FlxG.mouse.justReleasedRight) { + // closeCurrentContextMenu(); + // openContextMenu(topMenu[2].childs); + //} + + if (mouseMode == NONE && prevMode == NONE) { + if (FlxG.mouse.pressed) { + var x = FlxG.mouse.deltaScreenX; + var y = FlxG.mouse.deltaScreenY; + movedTillRel.x += x; movedTillRel.y += y; + nextScroll.set(nextScroll.x - x, nextScroll.y - y); + currentCursor = HAND; + } + + if (FlxG.mouse.justReleased) { + if (Math.abs(movedTillRel.x) < 16 && Math.abs(movedTillRel.y) < 16) { + var point = FlxG.mouse.getWorldPosition(stageCamera, _point); + var sprites = getRealSprites(); + for (i in 0...sprites.length) { + var sprite = sprites[sprites.length - i - 1]; + //if(sprite.animateAtlas != null) continue; + + calcSpriteBounds(sprite); + //trace("Sprite: " + sprite); + //trace("Sprite bounds: " + sprite.extra.get(exID("bounds"))); + if (cast(sprite.extra.get(exID("bounds")), FlxRect).containsPoint(point)) { + selectSprite(sprite); break; + } + } + } + movedTillRel.set(); + } + } + }/* else if (!FlxG.mouse.pressed) + currentCursor = ARROW;*/ + + stageCamera.scroll.set( + lerp(stageCamera.scroll.x, nextScroll.x, 0.35), + lerp(stageCamera.scroll.y, nextScroll.y, 0.35) + ); + + stageCamera.zoom = lerp(stageCamera.zoom, __camZoom, 0.125); + + WindowUtils.prefix = undos.unsaved ? "* " : ""; + SaveWarning.showWarning = undos.unsaved; + } + + // TOP MENU OPTIONS + #if REGION + function _file_exit(_) { + if (undos.unsaved) SaveWarning.triggerWarning(); + else FlxG.switchState(new StageSelection()); + } + + function _file_save(_) { + #if sys + CoolUtil.safeSaveFile( + '${Paths.getAssetsRoot()}/data/stages/${__stage}.xml', + buildStage() + ); + undos.save(); + #else + _file_saveas(_); + #end + } + + function _file_saveas(_) { + openSubState(new SaveSubstate(buildStage(), { + defaultSaveFile: '${__stage}.xml' + })); + undos.save(); + } + + function _sprite_new(_) { + var node:Access = new Access(Xml.createElement("sprite")); + stage.stageXML.x.addChild(node.x); + node.att.name = "sprite_" + stageSpritesWindow.buttons.members.length; + + var sprite:FunkinSprite = new FunkinSprite(); + insert(members.indexOf(stage), sprite); + sprite.extra.set(exID("node"), node); + sprite.extra.set(exID("type"), node.name); + sprite.extra.set(exID("imageFile"), ''); + sprite.extra.set(exID("parentNode"), stage.stageXML.x); + sprite.extra.set(exID("highMemory"), false); + sprite.extra.set(exID("lowMemory"), false); + xmlMap.set(sprite, node); + + var button:StageSpriteButton = new StageSpriteButton(0, 0, sprite, node); + sprite.extra.set(exID("button"), button); + stageSpritesWindow.add(button); + + var substate = new StageSpriteEditScreen(button); + substate.newSprite = true; + openSubState(substate); + } + + function _character_new(_) { + var node:Access = new Access(Xml.createElement("char")); + stage.stageXML.x.addChild(node.x); + node.att.name = "character_" + stageSpritesWindow.buttons.members.length; + + var char = new Character(0,0, "bf", false, true); + char.name = node.att.name; + char.debugMode = true; + // Play first anim, and make it the last frame + var animToPlay = char.getAnimOrder()[0]; + char.playAnim(animToPlay, true, NONE); + var lastIndx = (char.animateAtlas != null) ? + char.animateAtlas.anim.length - 1 : + char.animation.curAnim.numFrames - 1; + char.playAnim(animToPlay, true, NONE, false, lastIndx); + char.stopAnimation(); + + // Add it to the stage + char.visible = true; + char.alpha = 0.75; + + char.extra.set(exID("node"), node); + char.extra.set(exID("spacingX"), 20); + char.extra.set(exID("spacingY"), 0); + char.extra.set(exID("camX"), 0); + char.extra.set(exID("camY"), 0); + + char.extra.set(exID("parentNode"), stage.stageXML); + char.extra.set(exID("highMemory"), false); + char.extra.set(exID("lowMemory"), false); + xmlMap.set(char, node); + chars.push(char); + + insert(members.indexOf(stage), char); + charMap[node.att.name] = char; + + var button = new StageCharacterButton(0,0, char, node); + char.extra.set(exID("button"), button); + stageSpritesWindow.add(button); + } + + function saveToXml(xml:Xml, name:String, value:Dynamic, ?defaultValue:Dynamic) { + if(value == null || value == defaultValue) return xml; + xml.set(name, Std.string(value)); + return xml; + } + function savePointToXml(xml:Xml, name:String, point:FlxPoint, ?defaultValueX:Float, ?defaultValueY:Float) { + if (point == null) return xml; + if(defaultValueY == null) defaultValueY = defaultValueX; + if(point.x == point.y) { + saveToXml(xml, name, point.x, defaultValueX); + } else { + saveToXml(xml, name + "x", point.x, defaultValueX); + saveToXml(xml, name + "y", point.y, defaultValueY); + } + return xml; + } + + function getBoolOfNode(node:Access, name:String) { + var xml:Xml = cast node; + + return xml.exists(name) && xml.get(name) == "true"; + } + + function buildStage():String { + var xml = Xml.createElement("stage"); + saveToXml(xml, "zoom", stage.defaultZoom, 1.05); + saveToXml(xml, "name", stage.stageName); + saveToXml(xml, "folder", stage.spritesParentFolder); + saveToXml(xml, "startCamPosX", stage.startCam.x, 0); + saveToXml(xml, "startCamPosY", stage.startCam.y, 0); + + for(prop in stage.extra.keys()) + if(!Stage.DEFAULT_ATTRIBUTES.contains(prop) && !prop.startsWith("stageEditor.")) + saveToXml(xml, prop, stage.extra.get(prop)); + + var group:Xml = null; + var curGroup:String = null; + + for(sprite in getSprites()) { + var button:StageElementButton = sprite.extra.get(exID("button")); + var newNode:Xml = null; + var sprite:FunkinSprite = button.getSprite(); + if(button is StageSolidButton) { + var button:StageSolidButton = cast button; + var node:Access = cast sprite.extra.get(exID("node")); + Logs.trace("SOLID / BOX isnt implemented yet!"); + } else if(button is StageSpriteButton) { + var button:StageSpriteButton = cast button; + var node:Access = cast sprite.extra.get(exID("node")); + var spriteXML = Xml.createElement("sprite"); + saveToXml(spriteXML, "name", sprite.name); + saveToXml(spriteXML, "x", sprite.x, 0); + saveToXml(spriteXML, "y", sprite.y, 0); + saveToXml(spriteXML, "sprite", sprite.extra.get(exID("imageFile"))); + savePointToXml(spriteXML, "scale", sprite.scale, 1); + savePointToXml(spriteXML, "scroll", sprite.scrollFactor, 1); + saveToXml(spriteXML, "skewx", sprite.skew.x, 0); + saveToXml(spriteXML, "skewy", sprite.skew.y, 0); + saveToXml(spriteXML, "alpha", sprite.alpha, 1); + saveToXml(spriteXML, "angle", sprite.angle, 0); + //saveToXml(spriteXML, "graphicSize", sprite.width, sprite.width); + //saveToXml(spriteXML, "graphicSizex", sprite.height, sprite.height); + //saveToXml(spriteXML, "graphicSizey", sprite.height, sprite.height); + saveToXml(spriteXML, "zoomfactor", sprite.zoomFactor, 1); + saveToXml(spriteXML, "updateHitbox", getBoolOfNode(node, "updateHitbox"), false); + saveToXml(spriteXML, "antialiasing", sprite.antialiasing, true); + //saveToXml(spriteXML, "width", sprite.width); + //saveToXml(spriteXML, "height", sprite.height); + saveToXml(spriteXML, "playOnCountdown", getBoolOfNode(node, "playOnCountdown"), false); + saveToXml(spriteXML, "interval", node.getAtt("beatInterval"), 2); + saveToXml(spriteXML, "interval", node.getAtt("interval"), 2); + saveToXml(spriteXML, "beatOffset", node.getAtt("beatOffset"), 0); + if(sprite.spriteAnimType != LOOP) + spriteXML.set("type", sprite.spriteAnimType.toString()); + saveToXml(spriteXML, "color", sprite.color.toWebString(), "#FFFFFF"); + // TODO: save custom parameters + //saveToXml(spriteXML, "flipX", sprite.flipX, false); + newNode = spriteXML; + } else if(button is StageCharacterButton) { + var button:StageCharacterButton = cast button; + var char:Character = button.char; + var node:Access = cast char.extra.get(exID("node")); + var defaultPos = Stage.getDefaultPos(char.name.replace("NO_DELETE_", "")); + var charXML:Xml = Xml.createElement(node.name); + if(!char.name.startsWith("NO_DELETE_")) + saveToXml(charXML, "name", char.name); + saveToXml(charXML, "x", char.x, defaultPos.x); + saveToXml(charXML, "y", char.y, defaultPos.y); + saveToXml(charXML, "camxoffset", char.extra.get(exID("camX")), 0); + saveToXml(charXML, "camyoffset", char.extra.get(exID("camY")), 0); + saveToXml(charXML, "skewx", char.skew.x, 0); + saveToXml(charXML, "skewy", char.skew.y, 0); + saveToXml(charXML, "spacingx", char.extra.get(exID("spacingX")), 20); + saveToXml(charXML, "spacingy", char.extra.get(exID("spacingY")), 0); + saveToXml(charXML, "alpha", char.alpha / 0.75, 1); + saveToXml(charXML, "angle", char.angle, 0); + saveToXml(charXML, "zoomfactor", char.zoomFactor, 1); + saveToXml(charXML, "flipX", char.isPlayer, defaultPos.flip); + savePointToXml(charXML, "scroll", char.scrollFactor, defaultPos.scroll); + savePointToXml(charXML, "scale", char.scale.scaleNew(button.charScale), 1); + // TODO: save custom parameters + newNode = charXML; + } else if(button is StageUnknownButton) { + var button:StageUnknownButton = cast button; + newNode = button.xml.x; + } + else { + Logs.trace("Unknown Stage Type : " + Type.getClassName(Type.getClass(button))); + Logs.trace("> Sprite : " + Type.getClassName(Type.getClass(sprite))); + } + + if(newNode != null && sprite != null) { + var isLowMemory = sprite.extra.get(exID("lowMemory")) == true; + var isHighMemory = sprite.extra.get(exID("highMemory")) == true; + /* // Only if this compiled :sob: + var groupName:String = null; + if ((groupName = isLowMemory ? "low-memory" : isHighMemory ? "high-memory" : null) != null) { + var a = group != null && groupName != curGroup && ((group = cast xml.addChild(group)) != null); + (group = (group == null ? Xml.createElement(curGroup = groupName) : group)).addChild(newNode); + }else xml.addChild(newNode); + */ + + var groupName = isLowMemory ? "low-memory" : isHighMemory ? "high-memory" : null; + if(group != null && groupName != curGroup) { + xml.addChild(group); + group = null; + } + if(groupName != null) + (group = (group == null ? Xml.createElement(curGroup = groupName) : group)).addChild(newNode); + else + xml.addChild(newNode); + } + } + + var xmlThingYea:String = "<!DOCTYPE codename-engine-stage>\n" + Printer.print(xml, Options.editorPrettyPrint); + return Options.editorPrettyPrint ? xmlThingYea : xmlThingYea.replace("\n", ""); + } + + function _edit_undo(_) { + var undo = undos.undo(); + switch(undo) { + case null: + // do nothing + case CEditInfo(oldInfo, newInfo): + stage.startCam.x = oldInfo.startCamX; + stage.startCam.y = oldInfo.startCamY; + stage.defaultZoom = oldInfo.zoom; + stage.stageName = oldInfo.name; + stage.spritesParentFolder = oldInfo.folder; + + for (attrib in newInfo.attrib.keys()) + stage.extra.remove(attrib); + for (attrib => val in oldInfo.attrib) + stage.extra.set(attrib, val); + case CTransform(sprite, oldInfo, newInfo): + sprite.setPosition(oldInfo.x, oldInfo.y); + sprite.scale.set(oldInfo.scaleX, oldInfo.scaleY); + sprite.skew.set(oldInfo.skewX, oldInfo.skewY); + sprite.angle = oldInfo.angle; + cast(sprite.extra.get(exID("button")), StageElementButton).updateInfo(); + } + } + + function _edit_redo(_) { + var redo = undos.redo(); + switch(redo) { + case null: + // do nothing + case CEditInfo(oldInfo, newInfo): + stage.startCam.x = newInfo.startCamX; + stage.startCam.y = newInfo.startCamY; + stage.defaultZoom = newInfo.zoom; + stage.stageName = newInfo.name; + stage.spritesParentFolder = newInfo.folder; + + for (attrib in oldInfo.attrib.keys()) + stage.extra.remove(attrib); + for (attrib => val in newInfo.attrib) + stage.extra.set(attrib, val); + case CTransform(sprite, oldInfo, newInfo): + sprite.setPosition(newInfo.x, newInfo.y); + sprite.scale.set(newInfo.scaleX, newInfo.scaleY); + sprite.skew.set(newInfo.skewX, newInfo.skewY); + sprite.angle = newInfo.angle; + cast(sprite.extra.get(exID("button")), StageElementButton).updateInfo(); + } + } + + public function selectSprite(_sprite:FunkinSprite) { + if(!UIUtil.getKeyState(CONTROL, PRESSED)) + _select_deselect(null, false); + + if(_sprite is FunkinSprite) { + if(selection.contains(_sprite)) + selection.remove(_sprite); + else + selection.push(_sprite); + } + updateSelection(); + } + + function getSprites() { + return stageSpritesWindow.buttons.members.map((o) -> o.getSprite()).filter((o) -> o != null); + } + function getRealSprites() { + return stageSpritesWindow.buttons.members.filter(o -> o.canRender()).map((o) -> o.getSprite()).filter((o) -> o != null); + } + + function updateSelection() { + var sprites = getRealSprites(); + // Unselect all + for(sprite in sprites) { + if(sprite is FunkinSprite) { + var sprite:FunkinSprite = cast sprite; + sprite.extra.set(exID("selected"), false); + sprite.extra.get(exID("button")).selected = false; + } + } + // Mark selected as selected + for(sprite in selection) { + if(sprite is FunkinSprite) { + var sprite:FunkinSprite = cast sprite; + sprite.extra.set(exID("selected"), true); + sprite.extra.get(exID("button")).selected = true; + Logs.trace("Selected " + sprite.name); + } + } + } + + function _select_all(_, checkSelection:Bool = true) { + _select_deselect(null, false); + var sprites = getRealSprites(); + selection = new Selection(sprites); + if(checkSelection) updateSelection(); + } + + function _select_deselect(_, checkSelection:Bool = true) { + selection = new Selection(); + if(checkSelection) updateSelection(); + } + + function _select_inverse(_, checkSelection:Bool = true) { + var oldSelection = selection; + _select_all(null, false); + for(sprite in oldSelection) { + selection.remove(sprite); + } + if(checkSelection) updateSelection(); + } + + var zoom(default, set):Float = 0; + var __camZoom(default, set):Float = 1; + function set_zoom(val:Float) { + return zoom = CoolUtil.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling + } + function set___camZoom(val:Float) { + return __camZoom = CoolUtil.bound(val, 0.1, 3); + } + + function _view_zoomin(_) { + zoom += 0.25; + updateZoom(); + } + function _view_zoomout(_) { + zoom -= 0.25; + updateZoom(); + } + function _view_zoomreset(_) { + setZoom(stage.defaultZoom); + } + + inline function updateZoom() { + __camZoom = Math.pow(2, zoom); + } + + inline function calculateZoom(zoom:Float) { + return Math.log(zoom) / Math.log(2); + } + + inline function setZoom(_zoom:Float) { + zoom = calculateZoom(__camZoom = _zoom); + } + + function _view_focusdad(_) { + focusCharacter(charMap["NO_DELETE_dad"]); + } + function _view_focusgf(_) { + focusCharacter(charMap["NO_DELETE_girlfriend"]); + } + function _view_focusbf(_) { + focusCharacter(charMap["NO_DELETE_boyfriend"]); + } + + function focusCharacter(char:Character) { + var point = char.getCameraPosition(); + nextScroll.set(point.x - stageCamera.width / 2, point.y - stageCamera.height / 2); + + setZoom(stage.defaultZoom); + } + + function _editor_showCharacters(t) { + showCharacters = !showCharacters; + t.icon = showCharacters ? 1 : 0; + for(char in chars) { + var button:StageCharacterButton = char.extra.get(exID("button")); + button.isHidden = !showCharacters; + button.updateInfo(); + } + } + #end + + override function draw() { + super.draw(); + + for(sprite in selection) + if(sprite is FunkinSprite) drawGuides(cast sprite); + } + + function drawGuides(sprite:FunkinSprite) { + if(sprite == null || sprite.offset == null) return; // destroyed + + var corners:Array<FlxPoint> = calcSpriteBounds(sprite); + var transformedCorners:Array<FlxPoint> = [for (corner in corners) corner.clone()]; + for (corner in transformedCorners) { + corner.x -= sprite.cameras[0].viewX; + corner.y -= sprite.cameras[0].viewY; + + corner.x *= sprite.cameras[0].zoom; + corner.y *= sprite.cameras[0].zoom; + } + + if (DrawUtil.line == null) DrawUtil.createDrawers(); + DrawUtil.line.cameras = [gizmosCamera]; DrawUtil.line.alpha = 0.85; + DrawUtil.dot.cameras = [gizmosCamera]; DrawUtil.dot.alpha = 1; + + DrawUtil.drawLine(transformedCorners[0], transformedCorners[1], 1, 0xFF007B8F); // tl - tr + DrawUtil.drawLine(transformedCorners[0], transformedCorners[2], 1, 0xFF007B8F); // tl - bl + DrawUtil.drawLine(transformedCorners[1], transformedCorners[3], 1, 0xFF007B8F); // tr - br + DrawUtil.drawLine(transformedCorners[2], transformedCorners[3], 1, 0xFF007B8F); // bl - br + + DrawUtil.drawLine(transformedCorners[7], transformedCorners[9], 1, 0xFF007B8F); // tc - ac // top center to angle center + + // cross + // DrawUtil.drawLine(transformedCorners[0], transformedCorners[3], 1, 0xFF007B8F); // tl - br + // DrawUtil.drawLine(transformedCorners[1], transformedCorners[2], 1, 0xFF007B8F); // tr - bl + + final ANGLE_INDEX = StageEditorEdge.ROTATE_CIRCLE.toInt(); + final CENTER_INDEX = StageEditorEdge.CENTER_CIRCLE.toInt(); + + if(sprite.extra.exists(exID("buttonBoxes"))) { + var oldButtonBoxes:Array<FlxPoint> = cast sprite.extra.get(exID("buttonBoxes")); + if(oldButtonBoxes != null) + for(point in oldButtonBoxes) point.put(); + } + + var buttonBoxes:Array<FlxPoint> = []; + if(sprite != null) sprite.extra.set(exID("buttonBoxes"), buttonBoxes); + + DrawUtil.dot.color = 0xFFBBE4EA; DrawUtil.dot.animation.play("default"); + for(i in 0...transformedCorners.length) { + var transformedCorner:FlxPoint = transformedCorners[i]; + + if (i != CENTER_INDEX) { + if (i == ANGLE_INDEX) { + DrawUtil.dot.animation.play("hollow"); DrawUtil.dot.color = 0xFFBBE4EA; + DrawUtil.drawDot(transformedCorner.x, transformedCorner.y, 0.5); + } else { + DrawUtil.dot.animation.play("default"); DrawUtil.dot.color = 0xFFF7A7A7; + DrawUtil.drawDot(transformedCorner.x, transformedCorner.y, 0.3); + + DrawUtil.dot.animation.play("hollow"); DrawUtil.dot.color = 0xFFBBE4EA; + DrawUtil.drawDot(transformedCorner.x, transformedCorner.y, 0.5); + } + } else if (sprite.visible) { + DrawUtil.dot.color = 0xFFBBE4EA; + DrawUtil.drawDot(transformedCorner.x, transformedCorner.y, 0.7*0.5); + DrawUtil.dot.animation.play("hollow"); + } + + buttonBoxes.push(corners[i]); + } + } + + public static function calcSpriteBounds(sprite:FunkinSprite) { + var oldWidth = sprite.width; + var oldHeight = sprite.height; + + /* + if (sprite.animateAtlas != null) { + var rect:FlxRect = MatrixUtil.getBounds(sprite); + oldWidth = rect.width; + oldHeight = rect.height; + } + */ + + var oldOffset = sprite.offset.clone(FlxPoint.weak()); + var oldOrigin = sprite.origin.clone(FlxPoint.weak()); + sprite.updateHitbox(); + sprite.offset.copyFrom(oldOffset); + sprite.origin.copyFrom(oldOrigin); + @:privateAccess sprite.updateTrig(); + + var corners = sprite.getMatrixPosition([ + // corners + FlxPoint.get(0, 0), + FlxPoint.get(1, 0), + FlxPoint.get(0, 1), + FlxPoint.get(1, 1), + // edges + FlxPoint.get(0, 0.5), + FlxPoint.get(1, 0.5), + FlxPoint.get(0.5, 1), + FlxPoint.get(0.5, 0), + // center + FlxPoint.get(0.5, 0.5), + //FlxPoint.get(0.5, -100/sprite.frameHeight) // angle + FlxPoint.get(0.5, -0.5), // angle + FlxPoint.get(1, 0) // rotate corner + ], sprite.camera, sprite.frameWidth, sprite.frameHeight); + //if(sprite.animateAtlas != null) { + // trace("Corners: " + corners); + //} + @:privateAccess corners[corners.length - 1].add( + dotCheckSize * 0.35 * sprite._cosAngle + dotCheckSize * 0.35 * sprite._sinAngle, + -dotCheckSize * 0.35 * sprite._cosAngle + dotCheckSize * 0.35 * sprite._sinAngle + ); + + //Logs.trace("Guide at " + corners[0].x + ", " + corners[0].y + " sprite at " + sprite.x + ", " + sprite.y); + + if(sprite is FunkinSprite) { + var sprite:FunkinSprite = cast sprite; + var corner0 = corners[0]; + var corner1 = corners[1]; + var corner2 = corners[2]; + var corner3 = corners[3]; + var maxX = MathUtil.maxSmart(corner0.x, corner1.x, corner2.x, corner3.x); + var maxY = MathUtil.maxSmart(corner0.y, corner1.y, corner2.y, corner3.y); + var minX = MathUtil.minSmart(corner0.x, corner1.x, corner2.x, corner3.x); + var minY = MathUtil.minSmart(corner0.y, corner1.y, corner2.y, corner3.y); + + if(!sprite.extra.exists(exID("bounds"))) { + sprite.extra.set(exID("bounds"), new FlxRect()); + } + cast(sprite.extra.get(exID("bounds")), FlxRect).set(minX, minY, maxX - minX, maxY - minY); + } + + // reset hitbox to old values + sprite.width = oldWidth; + sprite.height = oldHeight; + + return corners; + } + + var edges:Array<StageEditorEdge> = [ + //// corners + TOP_LEFT, //FlxPoint.get(0, 0), + TOP_RIGHT, //FlxPoint.get(1, 0), + BOTTOM_LEFT, //FlxPoint.get(0, 1), + BOTTOM_RIGHT, //FlxPoint.get(1, 1), + //// edges + MIDDLE_LEFT, //FlxPoint.get(0, 0.5), + MIDDLE_RIGHT, //FlxPoint.get(1, 0.5), + BOTTOM_MIDDLE, //FlxPoint.get(0.5, 1), + TOP_MIDDLE, //FlxPoint.get(0.5, 0), + + CENTER_CIRCLE, //FlxPoint.get(0.5, -0.5) + + ROTATE_CIRCLE, //FlxPoint.get(0.5, -0.5) + ROTATE_CORNER + ]; + + function tryUpdateHitbox(sprite:FunkinSprite) { + call("tryUpdateHitbox", [sprite]); + } + + function handleSelection(sprite:FunkinSprite) { + if(!sprite.extra.exists(exID("buttonBoxes"))) return; + var buttonBoxes:Array<FlxPoint> = cast sprite.extra.get(exID("buttonBoxes")); + + dotCheckSize = DrawUtil.dot.frameWidth / 0.7/stageCamera.zoom; // basically adjust it to the zoom. + + var prevMode = mouseMode; + if(FlxG.mouse.justPressed) { + for (i in StageEditorMouseMode.SKEW_TOP...(StageEditorMouseMode.SKEW_BOTTOM + 1)) { + var cappedI1 = CoolUtil.maxInt(i - StageEditorMouseMode.SKEW_TOP - 1, 0); + var cappedI2 = CoolUtil.minInt(i - StageEditorMouseMode.SKEW_TOP + 1, 3); + var point1 = buttonBoxes[cappedI1]; + var point2 = buttonBoxes[cappedI2]; + if (checkLine(point1, point2, point2.x - point1.x, point2.y - point1.y)) { + mousePoint.copyTo(clickPoint); + storedPos.set(sprite.x, sprite.y); + storedSkew.copyFrom(sprite.skew); + storedScale.copyFrom(sprite.scale); + storedAngle = sprite.angle; + mouseMode = i; + } + } + + for(i=>edge in edges) { + if(checkDot(buttonBoxes[i])) { + mouseMode = switch(edge) { + case TOP_LEFT: SCALE_TOP_LEFT; + case TOP_MIDDLE: SCALE_TOP; + case TOP_RIGHT: SCALE_TOP_RIGHT; + case MIDDLE_LEFT: SCALE_LEFT; + case MIDDLE_RIGHT: SCALE_RIGHT; + case BOTTOM_LEFT: SCALE_BOTTOM_LEFT; + case BOTTOM_MIDDLE: SCALE_BOTTOM; + case BOTTOM_RIGHT: SCALE_BOTTOM_RIGHT; + case CENTER_CIRCLE: MOVE_CENTER; + case ROTATE_CIRCLE: + angleOffset = 90; + ROTATE; + case ROTATE_CORNER: + angleOffset = Math.atan(sprite.height / sprite.width) * FlxAngle.TO_DEG; + ROTATE; + default: NONE; + } + Logs.trace("Clicked Dot: " + mouseMode.toString()); + mousePoint.copyTo(clickPoint); + storedPos.set(sprite.x, sprite.y); + storedSkew.set(sprite.skew.x, sprite.skew.y); + storedScale.copyFrom(sprite.scale); + storedAngle = sprite.angle; + } + + if(mouseMode == MOVE_CENTER){ + trace(mouseMode); + storedPos.set(sprite.x, sprite.y); + } + } + } + for(i=>edge in edges) { + if(checkDot(buttonBoxes[i])) { + // TODO: make it show both sided arrows when resizing, unless its at minimum size then show only one + // TODO: make this rotate with the sprite + currentCursor = switch(edge) { + // RESIZE_NESW; //RESIZE_NS; //RESIZE_NWSE; //RESIZE_WE; + case TOP_LEFT: RESIZE_TL; + case TOP_MIDDLE: RESIZE_T; + case TOP_RIGHT: RESIZE_TR; + case MIDDLE_LEFT: RESIZE_L; + case CENTER_CIRCLE: + #if (mac) FlxG.mouse.pressed ? DRAG : DRAG_OPEN + #elseif (linux) FlxG.mouse.pressed ? DRAG : CLICK + #else MOVE #end; + case MIDDLE_RIGHT: RESIZE_R; + case BOTTOM_LEFT: RESIZE_BL; + case BOTTOM_MIDDLE: RESIZE_B; + case BOTTOM_RIGHT: RESIZE_BR; + //case TOP_LEFT | BOTTOM_RIGHT: MouseCursor.RESIZE_NWSE; + //case TOP_MIDDLE | BOTTOM_MIDDLE: MouseCursor.RESIZE_NS; + //case TOP_RIGHT | BOTTOM_LEFT: MouseCursor.RESIZE_NESW; + //case MIDDLE_LEFT | MIDDLE_RIGHT: MouseCursor.RESIZE_WE; + + case ROTATE_CIRCLE | ROTATE_CORNER: FlxG.mouse.pressed ? DRAG : #if mac DRAG_OPEN #else CLICK #end; + default: ARROW; + } + break; + } + } + + mouseMode = (FlxG.mouse.justReleased) ? NONE : mouseMode; + + if (prevMode == NONE && mouseMode == NONE) return; + + if (prevMode != NONE && mouseMode == NONE) { + undos.addToUndo(CTransform(sprite, { + x: storedPos.x, + y: storedPos.y, + scaleX: storedScale.x, + scaleY: storedScale.y, + skewX: storedSkew.x, + skewY: storedSkew.y, + angle: storedAngle + }, { + x: sprite.x, + y: sprite.y, + scaleX: sprite.scale.x, + scaleY: sprite.scale.y, + skewX: sprite.skew.x, + skewY: sprite.skew.y, + angle: sprite.angle + })); + } + + if(mouseMode == NONE) return; + + var relative = clickPoint.subtractNew(mousePoint); + call(mouseMode.toString(), [sprite, relative]); + cast(sprite.extra.get(exID("button")), StageElementButton).updateInfo(); + relative.put(); + } + + public static var dotCheckSize:Float = 53; + + function checkDot(point:FlxPoint):Bool { + if(point!=null){ + var rect = new FlxRect(point.x - dotCheckSize/2, point.y - dotCheckSize/2, dotCheckSize, dotCheckSize); + return rect.containsPoint(mousePoint); + } + return false; + } + + function checkLine(point1:FlxPoint, point2:FlxPoint, dx:Float, dy:Float) { + var leftRegion = Math.min(point1.x, point2.x) - dotCheckSize * 0.2; + var rightRegion = Math.max(point1.x, point2.x) + dotCheckSize * 0.2; + var topRegion = Math.min(point1.y, point2.y) - dotCheckSize * 0.2; + var bottomRegion = Math.max(point1.y, point2.y) + dotCheckSize * 0.2; + + if (dx == 0.0 || dy == 0.0) + return (mousePoint.x >= leftRegion && mousePoint.x <= rightRegion) + && (mousePoint.y >= topRegion && mousePoint.y <= bottomRegion); + + var inc = dx * ((mousePoint.y - point1.y) / dy); + leftRegion += inc; + rightRegion += inc; + + inc = dy * ((mousePoint.x - point1.x) / dx); + topRegion += inc; + bottomRegion += inc; + + return (mousePoint.x >= leftRegion && mousePoint.x <= rightRegion) + && (mousePoint.y >= topRegion && mousePoint.y <= bottomRegion); + } +} + +typedef StageInfo = { + name:String, + folder:String, + startCamX:Float, + startCamY:Float, + zoom:Float, + attrib:Map<String, String> +}; + +typedef StageSprInfo = { + x:Float, + y:Float, + scaleX:Float, + scaleY:Float, + skewX:Float, + skewY:Float, // i dont wanna be cloning a bunch of FlxPoints. Theres a pool for a reason. + angle:Float +} + +enum StageChange { + CEditInfo(oldInfo:StageInfo, newInfo:StageInfo); + CTransform(sprite:FunkinSprite, oldInfo:StageSprInfo, newInfo:StageSprInfo); +} + +@:forward abstract Selection(Array<FunkinSprite>) from Array<FunkinSprite> to Array<FunkinSprite> { + public inline function new(?array:Array<FunkinSprite>) + this = array == null ? [] : array; + + // too lazy to put this in every for loop so i made it a abstract + //public inline function loop(onNote:CharterNote->Void, ?onEvent:CharterEvent->Void, ?draggableOnly:Bool = true) { + // for (s in this) { + // if (s is CharterNote && onNote != null && (draggableOnly ? s.draggable: true)) + // onNote(cast(s, CharterNote)); + // else if (s is CharterEvent && onEvent != null && (draggableOnly ? s.draggable: true)) + // onEvent(cast(s, CharterEvent)); + // } + //} +} + +enum abstract StageEditorMouseMode(Int) from Int to Int { + var NONE; + + var SCALE_LEFT; + var SCALE_BOTTOM; + var SCALE_TOP; + var SCALE_RIGHT; + var SCALE_TOP_LEFT; + var SCALE_TOP_RIGHT; + var SCALE_BOTTOM_LEFT; + var SCALE_BOTTOM_RIGHT; + + var MOVE_CENTER; + + var SKEW_TOP; + var SKEW_LEFT; + var SKEW_RIGHT; + var SKEW_BOTTOM; + + var ROTATE; + + public function toString():String { + return switch(cast this) { + case NONE: "NONE"; + case SCALE_LEFT: "SCALE_LEFT"; + case SCALE_BOTTOM: "SCALE_BOTTOM"; + case SCALE_TOP: "SCALE_TOP"; + case SCALE_RIGHT: "SCALE_RIGHT"; + case SCALE_TOP_LEFT: "SCALE_TOP_LEFT"; + case SCALE_TOP_RIGHT: "SCALE_TOP_RIGHT"; + case SCALE_BOTTOM_LEFT: "SCALE_BOTTOM_LEFT"; + case SCALE_BOTTOM_RIGHT: "SCALE_BOTTOM_RIGHT"; + case MOVE_CENTER: "MOVE_CENTER"; + case SKEW_LEFT: "SKEW_LEFT"; + case SKEW_BOTTOM: "SKEW_BOTTOM"; + case SKEW_TOP: "SKEW_TOP"; + case SKEW_RIGHT: "SKEW_RIGHT"; + case ROTATE: "ROTATE"; + } + } +} + +enum abstract StageEditorEdge(Int) { + var NONE = -1; + + var TOP_LEFT = 0; + var TOP_MIDDLE; + var TOP_RIGHT; + var MIDDLE_LEFT; + //var MIDDLE_MIDDLE; + var MIDDLE_RIGHT; + var BOTTOM_LEFT; + var BOTTOM_MIDDLE; + var BOTTOM_RIGHT; + var CENTER_CIRCLE; + + var ROTATE_CIRCLE; + var ROTATE_CORNER; + + public function toString():String { + return switch(cast this) { + case NONE: "NONE"; + case TOP_LEFT: "TOP_LEFT"; + case TOP_MIDDLE: "TOP_MIDDLE"; + case TOP_RIGHT: "TOP_RIGHT"; + case MIDDLE_LEFT: "MIDDLE_LEFT"; + //case MIDDLE_MIDDLE: "MIDDLE_MIDDLE"; + case MIDDLE_RIGHT: "MIDDLE_RIGHT"; + case BOTTOM_LEFT: "BOTTOM_LEFT"; + case BOTTOM_MIDDLE: "BOTTOM_MIDDLE"; + case BOTTOM_RIGHT: "BOTTOM_RIGHT"; + case CENTER_CIRCLE: "CENTER_CIRCLE"; + case ROTATE_CIRCLE: "ROTATE_CIRCLE"; + case ROTATE_CORNER: "ROTATE_CORNER"; + } + } + + public function toInt():Int { + return switch(cast this) { + case NONE: -1; + case TOP_LEFT: 0; + case TOP_MIDDLE: 1; + case TOP_RIGHT: 2; + case MIDDLE_LEFT: 3; + + case MIDDLE_RIGHT: 4; + case BOTTOM_LEFT: 5; + case BOTTOM_MIDDLE: 6; + case BOTTOM_RIGHT: 7; + case CENTER_CIRCLE: 8; + case ROTATE_CIRCLE: 9; + case ROTATE_CORNER: 10; + } + } +} + +class StageXMLEditScreen extends UISoftcodedWindow { + public var xml:Access; + public var saveCallback:Void->Void; + + public function new(xml:Access, saveCallback:Void->Void, type:String = "Unknown") { + this.xml = xml; + this.saveCallback = saveCallback; + super("layouts/stage/xmlEditScreen", [ + "stage" => StageEditor.instance.stage, + "xml" => xml, + "exID" => StageEditor.exID, + "type" => type + ]); + } + + override function saveData() { + super.saveData(); + if(saveCallback != null) saveCallback(); + } +} diff --git a/source/funkin/editors/stage/StageSelection.hx b/source/funkin/editors/stage/StageSelection.hx new file mode 100644 index 00000000..942f5c30 --- /dev/null +++ b/source/funkin/editors/stage/StageSelection.hx @@ -0,0 +1,65 @@ +package funkin.editors.stage; + +import funkin.editors.stage.StageCreationScreen.StageCreationData; +import funkin.game.Stage; +import funkin.options.OptionsScreen; +import funkin.options.type.NewOption; +import funkin.options.type.OptionType; +import funkin.options.type.TextOption; + +class StageSelection extends EditorTreeMenu +{ + public var stages:Array<String> = []; + + public override function create() + { + bgType = "charter"; + super.create(); + + var modsList:Array<String> = Stage.getList(true, true); + + var list:Array<OptionType> = [ + for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) { + stages.push(stage); + new TextOption(stage, "Press ACCEPT to edit this stage.", + function() { + FlxG.switchState(new StageEditor(stage)); + }); + } + ]; + + list.insert(0, new NewOption("New Stage", "Press ACCEPT to create a new stage.", function() { + FlxG.state.openSubState(new StageCreationScreen(saveStage)); + })); + + main = new OptionsScreen("Stage Editor", "Select a stage to edit. HSCRIPT only stages are not supported.", list); + + //DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]); + } + + public function saveStage(creation:StageCreationData) { + if ([for (s in stages) s.toLowerCase()].contains(creation.name.toLowerCase())) { + openSubState(new UIWarningSubstate("Creating Stage: Error!", "The stage you are trying to create alreadly exists, if you would like to override it delete the stage first!", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + return; + } + + #if sys + // Save File + CoolUtil.safeSaveFile('${Paths.getAssetsRoot()}/data/stages/${creation.name}.xml', '<!DOCTYPE codename-engine-stage>\n<stage folder="${creation.path}">\n</stage>'); + #end + + // Add to List + var option = new TextOption(creation.name, "Press ACCEPT to edit this stage.", function() { + FlxG.switchState(new StageEditor(creation.name)); + }); + optionsTree.members[optionsTree.members.length-1].insert(1, option); + } + + override function createPost() { + super.createPost(); + + main.changeSelection(1); + } +} \ No newline at end of file diff --git a/source/funkin/editors/stage/StageSpritesWindow.hx b/source/funkin/editors/stage/StageSpritesWindow.hx new file mode 100644 index 00000000..78988e1e --- /dev/null +++ b/source/funkin/editors/stage/StageSpritesWindow.hx @@ -0,0 +1,50 @@ +package funkin.editors.stage; + +import funkin.editors.stage.elements.StageElementButton; +import funkin.editors.stage.elements.*; + +class StageSpritesWindow extends UIButtonList<StageElementButton> { + public function new(x:Float, y:Float) { + super(x, y, 400, FlxG.height-25, "Stage Sprites", FlxPoint.get(400, 64)); + + collapsable = true; + topAlpha = 0.9; + middleAlpha = 0.5; + bottomAlpha = 0.5; + buttonSpacing = 0; + dragCallback = (button, oldID, newID) -> { + var sprite:FlxBasic = (button is StageUnknownButton) ? cast(button, StageUnknownButton).basic : button.getSprite(); + var idx = StageEditor.instance.members.indexOf(sprite); + StageEditor.instance.members.splice(idx, 1); + StageEditor.instance.members.insert(newID, sprite); + } + addButton.callback = () -> @:privateAccess { + var lastDrawCam = addButton.__lastDrawCameras[0]; + var screenPos = addButton.getScreenPosition(null, lastDrawCam == null ? FlxG.camera : lastDrawCam); + StageEditor.instance.openContextMenu([ + { + label: "Sprite", + onSelect: StageEditor.instance._sprite_new, + color: 0xFF00FF00, + icon: 2 + }, + { + label: "Box", + onSelect: function(_) { + UIState.state.displayNotification(new UIBaseNotification("Creating a box isnt implemented yet!", 2, BOTTOM_LEFT)); + CoolUtil.playMenuSFX(WARNING, 0.45); + }, + color: 0xFF00FF00, + icon: 2 + }, + { + label: "Character", + onSelect: StageEditor.instance._character_new, + color: 0xFF00FF00, + icon: 2 + } + ], null, lastDrawCam.x + screenPos.x, lastDrawCam.y + screenPos.y + addButton.bHeight, addButton.bWidth); + screenPos.put(); + } + } +} \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageCharacterButton.hx b/source/funkin/editors/stage/elements/StageCharacterButton.hx new file mode 100644 index 00000000..68156dcc --- /dev/null +++ b/source/funkin/editors/stage/elements/StageCharacterButton.hx @@ -0,0 +1,131 @@ +package funkin.editors.stage.elements; + +import funkin.editors.stage.StageEditor.StageXMLEditScreen; +import funkin.editors.ui.notifications.UIBaseNotification; +import funkin.editors.ui.UIState; +import funkin.game.Character; +import haxe.xml.Access; + +using StringTools; + +class StageCharacterButton extends StageElementButton { + public var char:Character; + public var charScale(get, null):Float; + + public function new(x:Float,y:Float, char:Character, xml:Access) { + this.char = char; + super(x,y, xml); + + color = 0xff7aa8ff; + + hasAdvancedEdit = true; + + updateInfo(); + } + + public override function update(elapsed:Float) { + super.update(elapsed); + } + + public override function updateInfo() { + char.visible = !isHidden; + char.scale.scale(1 / charScale); + super.updateInfo(); + char.scale.scale(charScale); + } + + public override function getSprite():FunkinSprite { + return char; + } + + public override function onSelect() { + StageEditor.instance.selectSprite(char); + } + + /*public override function onSelect() { + UIState.state.displayNotification(new UIBaseNotification("Selecting a character isnt implemented yet!", 2, BOTTOM_LEFT)); + CoolUtil.playMenuSFX(WARNING, 0.45); + }*/ + + public override function onEdit() { + if(!FlxG.keys.pressed.SHIFT) { + FlxG.state.openSubState(new StageCharacterEditScreen(this)); + } else { + FlxG.state.openSubState(new StageXMLEditScreen(this.xml, updateInfo, "Character")); + } + } + //public override function onEdit() { + // UIState.state.displayNotification(new UIBaseNotification("Editing a character isnt implemented yet!", 2, BOTTOM_LEFT)); + // CoolUtil.playMenuSFX(WARNING, 0.45); + //} + + public override function onDelete() { + if (char.name.startsWith("NO_DELETE_")) { + var text = "This character position is required in stages!"; + if (char.name == "NO_DELTE_girlfriend" && FlxG.random.bool(1)) + text = "You can't delete Girlfriend"; + UIState.state.displayNotification(new UIBaseNotification(text, 2, BOTTOM_LEFT)); + return; + } + + char.destroy(); + xml.x.parent.removeChild(xml.x); + StageEditor.instance.xmlMap.remove(char); + StageEditor.instance.stageSpritesWindow.remove(this); + } + + public override function onVisiblityToggle() { + isHidden = !isHidden; + updateInfo(); + } + + public override function getName():String { + return char.name.replace("NO_DELETE_", ""); + } + + public override function getPos():FlxPoint { + return FlxPoint.get(char.x, char.y); + } + + public override function updatePos() { + super.updatePos(); + } + + function get_charScale():Float { + return (char.xml.has.scale ? Std.parseFloat(char.xml.att.scale).getDefault(1) : 1); + } +} + +class StageCharacterEditScreen extends UISoftcodedWindow { + public var button:StageCharacterButton; + public var char:Character; + + public function new(button:StageCharacterButton) { + this.button = button; + this.char = button.char; + super("layouts/stage/characterEditScreen", [ + "stage" => StageEditor.instance.stage, + "char" => char, + "charScale" => button.charScale, + "button" => button, + "xml" => button.xml, + "exID" => StageEditor.exID, + "StringTools" => StringTools, + "getEx" => function(name:String):Dynamic { + return char.extra.get(StageEditor.exID(name)); + }, + "setEx" => function(name:String, value:Dynamic) { + char.extra.set(StageEditor.exID(name), value); + }, + ]); + } + + public override function create() { + super.create(); + } + + public override function saveData() { + super.saveData(); + button.updateInfo(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageElementButton.hx b/source/funkin/editors/stage/elements/StageElementButton.hx new file mode 100644 index 00000000..937893c5 --- /dev/null +++ b/source/funkin/editors/stage/elements/StageElementButton.hx @@ -0,0 +1,218 @@ +package funkin.editors.stage.elements; + +import flixel.util.FlxColor; +import haxe.xml.Access; + +using flixel.util.FlxColorTransformUtil; + +class StageElementButton extends UIButton { + public var xml:Access; + + public var editButton:UIButton; + public var editIcon:FlxSprite; + + public var visibilityButton:UIButton; + public var visibilityIcon:FlxSprite; + + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + + public var isHidden:Bool = false; + public var hasAdvancedEdit:Bool = false; + + public var selected:Bool = false; + + public var tagColor:UISliceSprite; + + public function new(x:Float,y:Float, xml:Access) { + this.xml = xml; + super(x,y, getInfoText(), function () { + onSelect(); + }, StageEditor.SPRITE_WINDOW_WIDTH, StageEditor.SPRITE_WINDOW_BUTTON_HEIGHT); + autoAlpha = false; + + tagColor = new UISliceSprite(x, y, 10, StageEditor.SPRITE_WINDOW_BUTTON_HEIGHT, 'editors/ui/button'); + tagColor.alpha = 1; // Make entire sprite transparent + tagColor.selectable = false; + tagColor.active = false; + //members.push(tagColor); + + topAlpha = middleAlpha = bottomAlpha = 0.7; + + field.alignment = LEFT; + + visibilityButton = new UIButton(x+282+17, y, null, function () { + onVisiblityToggle(); + }, 32); + visibilityButton.autoAlpha = false; + members.push(visibilityButton); + + visibilityIcon = new FlxSprite(visibilityButton.x + 8, visibilityButton.y + 8).loadGraphic(Paths.image('editors/stage/visible-icon'), true, 16, 16); + visibilityIcon.animation.add("visible", [0]); + visibilityIcon.animation.add("invisible", [1]); + visibilityIcon.animation.play("visible"); + visibilityIcon.antialiasing = false; + visibilityIcon.updateHitbox(); + members.push(visibilityIcon); + + editButton = new UIButton(visibilityButton.x+32+17, y, null, function () { + onEdit(); + }, 32); + editButton.frames = Paths.getFrames("editors/ui/grayscale-button"); + editButton.color = FlxColor.YELLOW; + editButton.autoAlpha = false; + members.push(editButton); + + editIcon = new FlxSprite(editButton.x + 8, editButton.y + 10).loadGraphic(Paths.image('editors/stage/edit-button'), true, 16, 16); + editIcon.animation.add("edit", [0]); + editIcon.animation.add("advanced", [1]); + editIcon.antialiasing = false; + members.push(editIcon); + + deleteButton = new UIButton(editButton.x+32+17, y, null, function () { + onDelete(); + }, 32); + deleteButton.color = FlxColor.RED; + deleteButton.autoAlpha = false; + members.push(deleteButton); + + deleteIcon = new FlxSprite(deleteButton.x + 8, deleteButton.y + 8).loadGraphic(Paths.image('editors/stage/trash-icon')); + deleteIcon.antialiasing = false; + members.push(deleteIcon); + + setEditNormal(); + } + + public function setEditNormal() { + editIcon.animation.play("edit"); + editButton.color = FlxColor.YELLOW; + } + + public function setEditAdvanced() { + editIcon.animation.play("advanced"); + editButton.color = 0xFFFF5B0F; + } + + + var _lastSelected:Bool = false; + + public override function update(elapsed:Float) { + editButton.selectable = visibilityButton.selectable = deleteButton.selectable = selectable; + editButton.shouldPress = visibilityButton.shouldPress = deleteButton.shouldPress = shouldPress; + + hovered = !deleteButton.hovered; + updatePos(); + super.update(elapsed); + field.x += 12; + + if(hasAdvancedEdit) { + if(FlxG.keys.pressed.SHIFT) { + setEditAdvanced(); + } else { + setEditNormal(); + } + } + + tagColor.color = color; + } + + //private var _lastHovered:Bool = false; + + public override function draw() { + //if(_lastHovered != (_lastHovered = hovered && !pressed)) { + if(_lastSelected != (_lastSelected = selected)) { + updateColorTransform(); + } + super.draw(); + } + + /*public override function setFrameOffset() { + var _frameOffset = 0; + if(selected) _frameOffset = 9; + if(hovered && pressed) _frameOffset = 18; + framesOffset = _frameOffset; + }*/ + + public override function updateColorTransform() { + super.updateColorTransform(); + + if(selected) { + useColorTransform = true; + colorTransform.setOffsets(70, 70, 70, 0); + } else { + colorTransform.setOffsets(0, 0, 0, 0); + } + } + + public function updateInfo() { + field.text = getInfoText(); + + visibilityIcon.animation.play(isHidden ? "invisible" : "visible"); + visibilityIcon.alpha = !isHidden ? 1 : 0.5; + } + + public function updatePos() { + // buttons + var spacing = 8; + var buttonY = y + (bHeight - 32) / 2; + deleteButton.x = bWidth - deleteButton.bWidth - spacing; + deleteButton.y = buttonY; + editButton.x = deleteButton.x - editButton.bWidth - spacing; + editButton.y = buttonY; + visibilityButton.x = editButton.x - visibilityButton.bWidth - spacing; + visibilityButton.y = buttonY; + //deleteButton.x = (editButton.x = (visibilityButton.x = (x+282+17))+32+17)+32+17; + //deleteButton.y = editButton.y = visibilityButton.y = y; + // icons + visibilityIcon.x = visibilityButton.x + 8; visibilityIcon.y = visibilityButton.y + 8; + editIcon.x = editButton.x + 8; editIcon.y = editButton.y + 8; + deleteIcon.x = deleteButton.x + 8; deleteIcon.y = deleteButton.y + 8; + + tagColor.x = x;// + bWidth - tagColor.bWidth; + tagColor.y = y; + } + + public function getSprite():FunkinSprite { + return null; + } + + public function canRender() { + return true; + } + + public function getName():String { + return "UNKNOWN"; + } + + public function onSelect() { + // TODO: implement + } + + public function onVisiblityToggle() { + // TODO: implement + } + + public function onEdit() { + // TODO: implement + } + + public function onDelete() { + // TODO: implement + } + + public function getPos():FlxPoint { + return FlxPoint.get(-1, -1); + } + + public function getInfoText():String { + var pos = getPos(); + var text = '${getName()} (${CoolUtil.quantize(pos.x, 100)}, ${CoolUtil.quantize(pos.y, 100)})'; + var sprite = getSprite(); + if(sprite != null) { + text += '\nScale: (${CoolUtil.quantize(sprite.scale.x, 100)}, ${CoolUtil.quantize(sprite.scale.y, 100)})'; + text += '\nScroll: (${CoolUtil.quantize(sprite.scrollFactor.x, 100)}, ${CoolUtil.quantize(sprite.scrollFactor.y, 100)})'; + } + pos.put(); + return text; + } +} \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageSolidButton.hx b/source/funkin/editors/stage/elements/StageSolidButton.hx new file mode 100644 index 00000000..6ded6d26 --- /dev/null +++ b/source/funkin/editors/stage/elements/StageSolidButton.hx @@ -0,0 +1,16 @@ +package funkin.editors.stage.elements; + +import flixel.util.FlxColor; +import haxe.xml.Access; + +class StageSolidButton extends StageSpriteButton { + + public function new(x:Float,y:Float, sprite:FunkinSprite, xml:Access) { + super(x,y, sprite, xml); + color = 0xFFD9FF50; + } + + public override function onEdit() { + // TODO: implement + } +} \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageSpriteButton.hx b/source/funkin/editors/stage/elements/StageSpriteButton.hx new file mode 100644 index 00000000..485eca9f --- /dev/null +++ b/source/funkin/editors/stage/elements/StageSpriteButton.hx @@ -0,0 +1,114 @@ +package funkin.editors.stage.elements; + +import funkin.editors.stage.StageEditor.StageXMLEditScreen; +import funkin.editors.stage.elements.StageElementButton; +import funkin.editors.ui.UISoftcodedWindow; +import haxe.xml.Access; + +class StageSpriteButton extends StageElementButton { + public var sprite:FunkinSprite; + + public function new(x:Float,y:Float, sprite:FunkinSprite, xml:Access) { + this.sprite = sprite; + super(x,y, xml); + + //color = 0xFFD9FF50; + + hasAdvancedEdit = true; + + updateInfo(); + } + + public override function update(elapsed:Float) { + super.update(elapsed); + } + + public override function updateInfo() { + sprite.visible = !isHidden; + super.updateInfo(); + } + + public override function getSprite():FunkinSprite { + return sprite; + } + + public override function onSelect() { + StageEditor.instance.selectSprite(sprite); + } + + public override function onVisiblityToggle() { + isHidden = !isHidden; + updateInfo(); + } + + public override function onEdit() { + if(!FlxG.keys.pressed.SHIFT) { + FlxG.state.openSubState(new StageSpriteEditScreen(this)); + } else { + FlxG.state.openSubState(new StageXMLEditScreen(this.xml, updateInfo, "Sprite")); + } + } + + public override function onDelete() { + sprite.destroy(); + xml.x.parent.removeChild(xml.x); + StageEditor.instance.stage.stageSprites.remove(sprite.name); + StageEditor.instance.xmlMap.remove(sprite); + StageEditor.instance.stageSpritesWindow.remove(this); + } + + public override function getName():String { + return xml.att.name; + } + + public override function getPos():FlxPoint { + return FlxPoint.get(sprite.x, sprite.y); + } + + public override function updatePos() { + super.updatePos(); + } +} + +class StageSpriteEditScreen extends UISoftcodedWindow { + public var newSprite:Bool = false; + public var button:StageSpriteButton; + public var sprite:FunkinSprite; + var isSaving:Bool = false; + + public function new(button:StageSpriteButton) { + this.button = button; + this.sprite = button.getSprite(); + super("layouts/stage/spriteEditScreen", [ + "stage" => StageEditor.instance.stage, + "sprite" => sprite, + "button" => button, + "xml" => button.xml, + "exID" => StageEditor.exID, + "getEx" => function(name:String):Dynamic { + return sprite.extra.get(StageEditor.exID(name)); + }, + "setEx" => function(name:String, value:Dynamic) { + sprite.extra.set(StageEditor.exID(name), value); + }, + ]); + } + + public override function create() { + super.create(); + } + + public override function saveData() { + isSaving = true; + super.saveData(); + button.updateInfo(); + } + + public override function close() { + if (!isSaving && newSprite) { + button.onDelete(); + trace("deleting sprite"); + } + super.close(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageUnknownButton.hx b/source/funkin/editors/stage/elements/StageUnknownButton.hx new file mode 100644 index 00000000..a1beb0bc --- /dev/null +++ b/source/funkin/editors/stage/elements/StageUnknownButton.hx @@ -0,0 +1,107 @@ +package funkin.editors.stage.elements; + +import funkin.game.Character; +import funkin.game.Stage.StageCharPos; +import haxe.xml.Access; + +class StageUnknownButton extends StageElementButton { + public var lowMemory:Bool = false; + public var highMemory:Bool = false; + public var basic:FlxBasic; + + public function new(x:Float,y:Float, basic:FlxBasic, xml:Access) { + this.basic = basic; + super(x,y, xml); + + if(xml.x.parent != null) { + lowMemory = xml.x.parent.nodeName == "low-memory"; + highMemory = xml.x.parent.nodeName == "high-memory"; + } + + color = 0xff000000; + + visibilityButton.exists = false; + members.remove(visibilityButton); + + visibilityIcon.exists = false; + members.remove(visibilityIcon); + + setEditAdvanced(); + + updateInfo(); + } + + public override function update(elapsed:Float) { + super.update(elapsed); + } + + public override function updateInfo() { + super.updateInfo(); + } + + public override function getSprite():FunkinSprite { + var sprite = new FunkinSprite(); // to allow it to be saved + sprite.extra.set(StageEditor.exID("lowMemory"), lowMemory); + sprite.extra.set(StageEditor.exID("highMemory"), highMemory); + sprite.extra.set(StageEditor.exID("button"), this); + return sprite; + } + + public override function canRender() { + return false; + } + + public override function onSelect() { + } + + public override function onEdit() { + FlxG.state.openSubState(new StageUnknownEditScreen(this)); + } + + //public override function onEdit() { + // UIState.state.displayNotification(new UIBaseNotification("Editing a character isnt implemented yet!", 2, BOTTOM_LEFT)); + // CoolUtil.playMenuSFX(WARNING, 0.45); + //} + + public override function onVisiblityToggle() { + } + + public override function getName():String { + return xml.name; + } + + public override function getPos():FlxPoint { + return FlxPoint.get(0, 0); + } + + public override function getInfoText():String { + return 'Unknown\n<${getName()} />'; + } + + public override function updatePos() { + super.updatePos(); + } +} + +class StageUnknownEditScreen extends UISoftcodedWindow { + public var button:StageUnknownButton; + + public function new(button:StageUnknownButton) { + this.button = button; + super("layouts/stage/unknownEditScreen", [ + "stage" => StageEditor.instance.stage, + "button" => button, + "xml" => button.xml, + "exID" => StageEditor.exID + ]); + } + + public override function create() { + super.create(); + } + + public override function saveData() { + super.saveData(); + button.updateInfo(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIAudioPlayer.hx b/source/funkin/editors/ui/UIAudioPlayer.hx index c0f49d90..4b87dd4d 100644 --- a/source/funkin/editors/ui/UIAudioPlayer.hx +++ b/source/funkin/editors/ui/UIAudioPlayer.hx @@ -29,7 +29,7 @@ class UIAudioPlayer extends UIButton { public function new(x:Float, y:Float, bytes:Bytes) { sound = FlxG.sound.load(Sound.fromAudioBuffer(AudioBuffer.fromBytes(bytes))); - super(x, y, "", function () { + super(x, y, null, function () { if (sound.playing) sound.pause(); else sound.play(false, sound.time); }, 58 - 16, 58 - 16); @@ -54,10 +54,11 @@ class UIAudioPlayer extends UIButton { timeBarPlayer = new FlxSprite(timeBar.x, timeBar.y).loadGraphic(Paths.image('editors/ui/audio-time-empty')); timeBarPlayer.colorTransform.color = 0x440364; timeBarPlayer.scale.y = (timeBarPlayer.frameHeight + 2) / timeBarPlayer.frameHeight; + timeBarPlayer.pixelPerfectRender = true; members.push(timeBarPlayer); timeBarSpr = cast new UISprite(timeBar.x, timeBar.y).makeSolid(timeBar.barWidth, timeBar.barHeight, 0x00FFFFFF); - timeBarSpr.cursor = BUTTON; + timeBarSpr.cursor = CLICK; members.push(timeBarSpr); volumeBar = new FlxBar(timeBar.x + timeBar.barWidth - 56, y + 6, LEFT_TO_RIGHT, 56, 10, sound, "volume", 0, 1); @@ -67,7 +68,7 @@ class UIAudioPlayer extends UIButton { members.push(volumeBar); volumeBarSpr = cast new UISprite(volumeBar.x, volumeBar.y).makeSolid(volumeBar.barWidth, volumeBar.barHeight, 0x00FFFFFF); - volumeBarSpr.cursor = BUTTON; + volumeBarSpr.cursor = CLICK; members.push(volumeBarSpr); volumeIcon = new FlxSprite(volumeBar.x - 12 - 8, volumeBar.y-1).loadGraphic(Paths.image('editors/ui/audio-icon')); @@ -87,13 +88,13 @@ class UIAudioPlayer extends UIButton { playingSprite.animation.play(sound.playing ? "playing" : "paused"); timeText.text = '${FlxStringUtil.formatTime(sound.time/1000, true)} / ${FlxStringUtil.formatTime(sound.length/1000)}'; - if(timeBarPlayer.clipRect == null) - timeBarPlayer.clipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight); + if(timeBarPlayer.rawClipRect == null) + timeBarPlayer.rawClipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight); - timeBarPlayer.clipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length); - timeBarPlayer.clipRect.width = 2; + timeBarPlayer.rawClipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length); + timeBarPlayer.rawClipRect.width = 2; - timeBarPlayer.clipRect = timeBarPlayer.clipRect; + timeBarPlayer.rawClipRect = timeBarPlayer.rawClipRect; nextPlayerColor = sound.playing ? 0x732D95 : 0x440364; timeBarPlayer.colorTransform.color = FlxColor.interpolate(timeBarPlayer.colorTransform.color, nextPlayerColor, 1/14); diff --git a/source/funkin/editors/ui/UIAutoCompleteTextBox.hx b/source/funkin/editors/ui/UIAutoCompleteTextBox.hx index 60259c41..497fe383 100644 --- a/source/funkin/editors/ui/UIAutoCompleteTextBox.hx +++ b/source/funkin/editors/ui/UIAutoCompleteTextBox.hx @@ -9,10 +9,10 @@ class UIAutoCompleteTextBox extends UITextBox { public var suggestItems(default, set):Array<String> = []; var suggestIndex = 0; - public function new(x:Float, y:Float, text:String = "", width:Int = 320, height:Int = 32, multiline:Bool = false) { - super(x, y, text, width, height, multiline); + public function new(x:Float, y:Float, text:String = "", width:Int = 320, height:Int = 32, multiline:Bool = false, small:Bool = false) { + super(x, y, text, width, height, multiline, small); - suggestionText = new UIText(0, 0, width, ""); + suggestionText = new UIText(0, 0, width, "", small ? 12 : 15); suggestionText.color = 0xFF888888; suggestionText.visible = false; members.insert(members.indexOf(label), suggestionText); @@ -20,11 +20,7 @@ class UIAutoCompleteTextBox extends UITextBox { public override function update(elapsed:Float) { super.update(elapsed); - - var selected = selectable && focused; - - suggestionText.exists = selected; - + suggestionText.exists = selectable && focused; suggestionText.follow(label, 0, 0); } diff --git a/source/funkin/editors/ui/UIButton.hx b/source/funkin/editors/ui/UIButton.hx index c5522c5c..8aa75936 100644 --- a/source/funkin/editors/ui/UIButton.hx +++ b/source/funkin/editors/ui/UIButton.hx @@ -6,19 +6,24 @@ class UIButton extends UISliceSprite { public var shouldPress = true; public var hasBeenPressed = false; + public var autoFrames:Bool = true; + public var autoFollow:Bool = true; + public override function new(x:Float, y:Float, text:String, callback:Void->Void, w:Int = 120, h:Int = 32) { super(x, y, w, h, 'editors/ui/button'); this.callback = callback; - members.push(field = new UIText(x, y, w, text)); - field.alignment = CENTER; - field.fieldWidth = w; + if(text != null) { + members.push(field = new UIText(x, y, w, text)); + field.alignment = CENTER; + field.fieldWidth = w; + } - cursor = BUTTON; + cursor = CLICK; } public override function resize(w:Int, h:Int) { super.resize(w, h); - if (field != null) field.fieldWidth = w; + if (field != null && autoFollow) field.fieldWidth = w; } public override function onHovered() { @@ -31,14 +36,21 @@ class UIButton extends UISliceSprite { } public override function update(elapsed:Float) { - field.follow(this, 0, (bHeight - field.height) / 2); + if (autoFollow && field != null) field.follow(this, 0, (bHeight - field.height) / 2); if (!hovered && hasBeenPressed && FlxG.mouse.justReleased) hasBeenPressed = false; - if (autoAlpha) alpha = field.alpha = selectable ? 1 : 0.4; + if (autoAlpha) { + alpha = selectable ? 1 : 0.4; + if(field != null) field.alpha = alpha; + } super.update(elapsed); } public override function draw() { - framesOffset = hovered ? (pressed ? 18 : 9) : 0; + setFrameOffset(); super.draw(); } + + public function setFrameOffset() { + framesOffset = hovered ? (pressed ? 18 : 9) : 0; + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIButtonList.hx b/source/funkin/editors/ui/UIButtonList.hx index ec8385e5..896579f9 100644 --- a/source/funkin/editors/ui/UIButtonList.hx +++ b/source/funkin/editors/ui/UIButtonList.hx @@ -14,7 +14,7 @@ class UIButtonList<T:UIButton> extends UIWindow { public var cameraSpacing = 30; public var buttonSpacing:Float = 16; - public var buttonSize:FlxPoint = FlxPoint.get(); + public var buttonSize:FlxPoint = null; public var buttonOffset:FlxPoint = FlxPoint.get(); public var dragging:Bool = false; @@ -33,7 +33,7 @@ class UIButtonList<T:UIButton> extends UIWindow { FlxG.cameras.add(buttonCameras, false); buttonCameras.bgColor = 0; - addButton = new UIButton(25, 16, "", null, Std.int(this.buttonSize.x)); + addButton = new UIButton(25, 16, null, null, Std.int(this.buttonSize.x)); addButton.autoAlpha = false; addButton.color = 0xFF00FF00; addButton.cameras = [buttonCameras]; @@ -41,14 +41,14 @@ class UIButtonList<T:UIButton> extends UIWindow { addIcon = new FlxSprite(addButton.x + addButton.bHeight / 2, addButton.y + (32/2) - 8).loadGraphic(Paths.image('editors/charter/add-button')); addIcon.antialiasing = false; addButton.members.push(addIcon); - members.push(addButton); + content.add(addButton); - members.push(buttons); + content.add(buttons); nextscrollY = buttonCameras.scroll.y = -this.buttonSpacing; } public inline function add(button:T) { - button.ID = buttons.members.length-1; + button.ID = buttons.members.length; buttons.add(button); curMovingInterval = 0; nextscrollY += button.bHeight; @@ -62,40 +62,45 @@ class UIButtonList<T:UIButton> extends UIWindow { public inline function remove(button:T) { nextscrollY -= button.bHeight; - buttons.members.remove(button); + buttons.remove(button, true); button.destroy(); } public inline function updateButtonsPos(elapsed:Float) { + var endButtonY:Float = 0; for (i => button in buttons.members) { if (button == null) continue; - + if (curMoving != button) { button.setPosition( (bWidth/2) - (buttonSize.x/2) + buttonOffset.x, - CoolUtil.fpsLerp(button.y, ((buttonSize.y+buttonSpacing) * i) + buttonOffset.y, 0.25)); + CoolUtil.fpsLerp(button.y, endButtonY + buttonOffset.y, 0.25)); } + endButtonY += button.bHeight+buttonSpacing; if (button.hovered && FlxG.mouse.justPressed) curMoving = button; } if (addButton != null) addButton.setPosition( (bWidth/2) - (buttonSize.x/2) + buttonOffset.x, - CoolUtil.fpsLerp(addButton.y, ((buttonSize.y+buttonSpacing) * buttons.members.length) + buttonOffset.y, 0.25)); + CoolUtil.fpsLerp(addButton.y, endButtonY + buttonOffset.y, 0.25)); if (curMoving != null) { curMovingInterval += FlxG.mouse.deltaY; if (Math.abs(curMovingInterval) > addButton.bHeight / 2) { curMovingInterval = 999; - curMoving.y = CoolUtil.fpsLerp(curMoving.y, FlxG.mouse.getWorldPosition(buttonCameras).y - (curMoving.bHeight / 2), 0.3); + var mousePos = FlxG.mouse.getWorldPosition(buttonCameras); + curMoving.y = CoolUtil.fpsLerp(curMoving.y, mousePos.y - (curMoving.bHeight / 2), 0.3); buttons.sort(function(o, a:T, b:T) return FlxSort.byValues(o, a.y + (a.bHeight / 2), b.y + (a.bHeight / 2)), -1); + mousePos.put(); } if (FlxG.mouse.justReleased) { curMoving = null; curMovingInterval = 0; } } - addIcon.x = addButton.x + addButton.bWidth / 2 - addIcon.width / 2; addIcon.y = addButton.y + addButton.bHeight / 2 - addIcon.height / 2; + addIcon.x = addButton.x + addButton.bWidth / 2 - addIcon.width / 2; + addIcon.y = addButton.y + addButton.bHeight / 2 - addIcon.height / 2; } var nextscrollY:Float = 0; public override function update(elapsed:Float) { @@ -104,14 +109,16 @@ class UIButtonList<T:UIButton> extends UIWindow { super.update(elapsed); - nextscrollY = FlxMath.bound(buttonCameras.scroll.y - (hovered ? FlxG.mouse.wheel : 0) * 12, -buttonSpacing, Math.max((addButton.y + 32 + (buttonSpacing*1.5)) - buttonCameras.height, -buttonSpacing)); + nextscrollY = CoolUtil.bound(nextscrollY - (hovered ? FlxG.mouse.wheel : 0) * 12, -buttonSpacing, Math.max((addButton.y + 32 + (buttonSpacing*1.5)) - buttonCameras.height, -buttonSpacing)); if (curMoving != null && dragging) { - nextscrollY -= Math.min((bHeight - 100) - FlxG.mouse.getWorldPosition(buttonCameras).y, 0) / 8; - nextscrollY += Math.min(FlxG.mouse.getWorldPosition(buttonCameras).y - 100, 0) / 8; + var mousePos = FlxG.mouse.getWorldPosition(buttonCameras); + nextscrollY -= Math.min((bHeight - 100) - mousePos.y, 0) / 8; + nextscrollY += Math.min(mousePos.y - 100, 0) / 8; + mousePos.put(); } - buttonCameras.scroll.y = nextscrollY; + buttonCameras.scroll.y = FlxMath.lerp(buttonCameras.scroll.y, nextscrollY, 1/3); for (i => button in buttons.members) { if (button == null) continue; @@ -125,9 +132,10 @@ class UIButtonList<T:UIButton> extends UIWindow { addButton.selectable = (hovered && !dragging); if (__lastDrawCameras[0] != null) { - buttonCameras.height = bHeight - cameraSpacing - 1; // -1 for the little gap at the bottom of the window + // -1 on each vertical sides for the little gap at the bottom of the window + buttonCameras.height = bHeight - cameraSpacing - 2; buttonCameras.x = __lastDrawCameras[0].x + x - __lastDrawCameras[0].scroll.x; - buttonCameras.y = __lastDrawCameras[0].y + y + cameraSpacing - __lastDrawCameras[0].scroll.y; + buttonCameras.y = __lastDrawCameras[0].y + y + cameraSpacing - __lastDrawCameras[0].scroll.y+1; buttonCameras.zoom = __lastDrawCameras[0].zoom; } } diff --git a/source/funkin/editors/ui/UICheckbox.hx b/source/funkin/editors/ui/UICheckbox.hx index a5bcdef3..edbbdd07 100644 --- a/source/funkin/editors/ui/UICheckbox.hx +++ b/source/funkin/editors/ui/UICheckbox.hx @@ -2,35 +2,38 @@ package funkin.editors.ui; class UICheckbox extends UISprite { public var checked:Bool = false; + public var small:Bool = false; public var onChecked:Bool->Void = null; public var field:UIText; public var check:FlxSprite; - public function new(x:Float, y:Float, text:String, checked:Bool = false, w:Int = 0) { + public function new(x:Float, y:Float, text:String, checked:Bool = false, w:Int = 0, small:Bool = false) { super(x, y); - loadGraphic(Paths.image('editors/ui/checkbox'), true, 20, 20); + loadGraphic(Paths.image('editors/ui/checkbox${small ? "-small" : ""}'), true, small ? 14 : 20, small ? 14 : 20); for(frame=>name in ["normal", "hover", "pressed", "checkmark"]) animation.add(name, [frame], 0, false); this.checked = checked; + this.small = small; - field = new UIText(x, y, w, text); + field = new UIText(x, y, w, text, small ? 12 : 15); check = new FlxSprite().loadGraphicFromSprite(this); check.animation.play("checkmark"); members.push(check); members.push(field); - cursor = BUTTON; + cursor = CLICK; } public override function update(elapsed:Float) { // ANIMATION HANDLING - animation.play(hovered ? (pressed ? "pressed" : "hover") : "normal"); + animation.play(hovered && selectable ? (pressed ? "pressed" : "hover") : "normal"); + this.alpha = field.alpha = selectable ? 1 : 0.4; // CHECKMARK HANDLING - check.alpha = checked ? 1 : 0; + check.alpha = selectable ? (checked ? 1 : 0) : 0; check.scale.x = CoolUtil.fpsLerp(check.scale.x, 1, 0.25); check.scale.y = CoolUtil.fpsLerp(check.scale.y, 1, 0.25); @@ -42,7 +45,7 @@ class UICheckbox extends UISprite { public inline function updatePositions() { check.follow(this); - field.follow(this, 25, 0); + field.follow(this, (small ? 14 : 20)+5, 0); } public override function draw() { @@ -65,10 +68,7 @@ class UICheckbox extends UISprite { } public override function updateButton() { - __rect.x = x; - __rect.y = y; - __rect.width = field.width + 30; - __rect.height = field.height > height ? field.height : height; + __rect.set(x, y, field.width + 30, field.height > height ? field.height : height); UIState.state.updateRectButtonHandler(this, __rect, onHovered); } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIColorwheel.hx b/source/funkin/editors/ui/UIColorwheel.hx index fb7f525b..c1ac432f 100644 --- a/source/funkin/editors/ui/UIColorwheel.hx +++ b/source/funkin/editors/ui/UIColorwheel.hx @@ -69,10 +69,11 @@ class UIColorwheel extends UISliceSprite { numStepper.antialiasing = true; numStepper.ID = i; numStepper.onChange = (text:String) -> { @:privateAccess numStepper.__onChange(text); + var val = Std.int(numStepper.value); switch (numStepper.ID) { - default: curColor.red = Std.int(numStepper.value); - case 1: curColor.green = Std.int(numStepper.value); - case 2: curColor.blue = Std.int(numStepper.value); + default: curColor.red = val; + case 1: curColor.green = val; + case 2: curColor.blue = val; } hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness; updateWheel(); @@ -125,28 +126,33 @@ class UIColorwheel extends UISliceSprite { // Make the colorwheel feel better static inline var hitBoxExtension:Float = 8; + // Skibidi + var selectedSprite = null; public override function update(elapsed:Float) { - if (hovered && FlxG.mouse.pressed) { - var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); - + var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); + if (hovered && FlxG.mouse.justPressed) { for (sprite in [colorPicker, colorSlider]) { var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); - - if (((mousePos.x > (spritePos.x - (hitBoxExtension/2))) && (mousePos.x < spritePos.x - (hitBoxExtension/2) + (sprite.width + hitBoxExtension))) && ((mousePos.y > (spritePos.y - (hitBoxExtension/2))) && (mousePos.y < spritePos.y - (hitBoxExtension/2) + (sprite.height + hitBoxExtension)))) { - mousePos -= FlxPoint.weak(spritePos.x, spritePos.y); - mousePos.set(FlxMath.bound(mousePos.x, 0, sprite.width), FlxMath.bound(mousePos.y, 0, sprite.height)); - - if (sprite == colorSlider) updateColorSliderMouse(mousePos); - if (sprite == colorPicker) updateColorPickerMouse(mousePos); - updateWheel(); - - spritePos.put(); + if (FlxMath.inBounds(mousePos.x, spritePos.x - (hitBoxExtension/2), spritePos.x - (hitBoxExtension/2) + (sprite.width + hitBoxExtension)) && FlxMath.inBounds(mousePos.y, spritePos.y - (hitBoxExtension/2), spritePos.y - (hitBoxExtension/2) + (sprite.height + hitBoxExtension))) { + selectedSprite = sprite; break; } } - mousePos.put(); } + if (selectedSprite != null) { + var spritePos:FlxPoint = selectedSprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); + mousePos -= FlxPoint.weak(spritePos.x, spritePos.y); + mousePos.set(CoolUtil.bound(mousePos.x, 0, selectedSprite.width), CoolUtil.bound(mousePos.y, 0, selectedSprite.height)); + + if (selectedSprite == colorSlider) updateColorSliderMouse(mousePos); + if (selectedSprite == colorPicker) updateColorPickerMouse(mousePos); + updateWheel(); + spritePos.put(); + + if (FlxG.mouse.justReleased) selectedSprite = null; + } + mousePos.put(); super.update(elapsed); } } diff --git a/source/funkin/editors/ui/UICompactColorwheel.hx b/source/funkin/editors/ui/UICompactColorwheel.hx new file mode 100644 index 00000000..7623a1de --- /dev/null +++ b/source/funkin/editors/ui/UICompactColorwheel.hx @@ -0,0 +1,103 @@ +package funkin.editors.ui; + +import flixel.util.FlxColor; +import flixel.math.FlxPoint; +import funkin.editors.ui.UIColorwheel.UIColorWheelSelector; + +using flixel.util.FlxSpriteUtil; + +class UICompactColorwheel extends UISliceSprite { + var colorSlider:FlxSprite; + var colorSliderSelector:UIColorWheelSelector; + + var whiteColorButton:UIButton; + + public var curColor:Null<FlxColor>; + public var curColorString:String = "#FFFFFF"; + + public var hue:Float; + + public function new (x:Float, y:Float, ?color:Int) { + super(x, y, Std.int((12.5 * 3) + 116), Std.int((12.5 * 2) + (16)), 'editors/ui/inputbox'); + + curColor = cast color; + hue = curColor.hue; + + colorSlider = new FlxSprite(x + 12.5, y + 12.5).makeGraphic(100, 16, FlxColor.TRANSPARENT); + colorSlider.drawRoundRect(0, 0, 100, 16, 10, 10, FlxColor.WHITE); + + colorSlider.pixels.lock(); + for (pixelx in 0...Std.int(colorSlider.width)) { + var color:Int = FlxColor.fromHSB(pixelx / (colorSlider.width-1) * 360, 1, 1); + for (pixely in 0...Std.int(colorSlider.height)) + if (colorSlider.pixels.getPixel32(pixelx, pixely) != FlxColor.TRANSPARENT) colorSlider.pixels.setPixel32(pixelx, pixely, color); + } + colorSlider.pixels.unlock(); + + whiteColorButton = new UIButton(colorSlider.x + colorSlider.width + 12.5, colorSlider.y, null, () -> { + setColor(FlxColor.WHITE); + }, 16, 16); + whiteColorButton.colorTransform.color = FlxColor.WHITE; + + colorSliderSelector = new UIColorWheelSelector(colorSlider.x, colorSlider.y); + + colorSlider.antialiasing = true; + + for (item in [colorSlider, colorSliderSelector, whiteColorButton]) + members.push(item); + + updateColor(false); + } + + inline function updateColorSliderPickerSelector() + colorSliderSelector.selector.setPosition(colorSlider.x + (hue / 360 * (colorSlider.width-1)) - 8, colorSlider.y); + + inline function updateColorSliderMouse(mousePos:FlxPoint) + hue = (mousePos.x / colorSlider.width) * 360; + + function setColor(newColor:FlxColor) { + this.color = curColor = colorSliderSelector.curColor = newColor; + } + + public function updateColor(checkChanged:Bool = true) { + if(checkChanged) colorChanged = true; + setColor(FlxColor.fromHSB(hue, 1, 1)); + + updateColorSliderPickerSelector(); + curColorString = curColor.toWebString(); + } + + // For Character Editor + public var colorChanged:Bool = false; + + // Make the colorwheel feel better + static inline var hitBoxExtenstion:Float = 8; + + var selectedSprite = null; + public override function update(elapsed:Float) { + var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); + if (hovered && FlxG.mouse.justPressed) { + for (sprite in [colorSlider]) { + var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); + if (FlxMath.inBounds(mousePos.x, spritePos.x - (hitBoxExtenstion/2), spritePos.x - (hitBoxExtenstion/2) + (sprite.width + hitBoxExtenstion)) && FlxMath.inBounds(mousePos.y, spritePos.y - (hitBoxExtenstion/2), spritePos.y - (hitBoxExtenstion/2) + (sprite.height + hitBoxExtenstion))) { + selectedSprite = sprite; + break; + } + } + } + + if (selectedSprite != null) { + var spritePos:FlxPoint = selectedSprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); + mousePos -= FlxPoint.weak(spritePos.x, spritePos.y); + mousePos.set(CoolUtil.bound(mousePos.x, 0, selectedSprite.width), CoolUtil.bound(mousePos.y, 0, selectedSprite.height)); + + if (selectedSprite == colorSlider) updateColorSliderMouse(mousePos); + updateColor(); + spritePos.put(); + + if (FlxG.mouse.justReleased) selectedSprite = null; + } + mousePos.put(); + super.update(elapsed); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIContextMenu.hx b/source/funkin/editors/ui/UIContextMenu.hx index 8cc5bcb0..bcbf6dbe 100644 --- a/source/funkin/editors/ui/UIContextMenu.hx +++ b/source/funkin/editors/ui/UIContextMenu.hx @@ -7,6 +7,7 @@ class UIContextMenu extends MusicBeatSubstate { public var options:Array<UIContextMenuOption>; var x:Float; var y:Float; + var w:Int = 100; var contextCam:FlxCamera; var bg:UISliceSprite; @@ -15,17 +16,21 @@ class UIContextMenu extends MusicBeatSubstate { public var contextMenuOptions:Array<UIContextMenuOptionSpr> = []; public var separators:Array<FlxSprite> = []; + var scroll:Float = 0.0; + var flipped:Bool = false; + private var __oobDeletion:Bool = true; public inline function preventOutOfBoxClickDeletion() { __oobDeletion = false; } - public function new(options:Array<UIContextMenuOption>, callback:UIContextMenuCallback, x:Float, y:Float) { + public function new(options:Array<UIContextMenuOption>, callback:UIContextMenuCallback, x:Float, y:Float, ?w:Int = 100) { super(); this.options = options.getDefault([]); this.x = x; this.y = y; + this.w = w; this.callback = callback; } @@ -37,7 +42,7 @@ class UIContextMenu extends MusicBeatSubstate { contextCam.scroll.set(0, 7.5); FlxG.cameras.add(contextCam, false); - bg = new UISliceSprite(x, y, 100, 100, 'editors/ui/context-bg'); + bg = new UISliceSprite(x, y, w, 100, 'editors/ui/context-bg'); bg.cameras = [contextCam]; add(bg); @@ -75,7 +80,8 @@ class UIContextMenu extends MusicBeatSubstate { bg.bWidth = maxW + 8; bg.bHeight = Std.int(lastY - bg.y + 4); - if (bg.y + bg.bHeight > FlxG.height) { + if (bg.y + bg.bHeight > FlxG.height && bg.y > FlxG.height*0.5) { + flipped = true; bg.y -= bg.bHeight; for(o in contextMenuOptions) o.y -= bg.bHeight; @@ -102,7 +108,10 @@ class UIContextMenu extends MusicBeatSubstate { super.update(elapsed); - contextCam.scroll.y = CoolUtil.fpsLerp(contextCam.scroll.y, 0, 0.5); + if (FlxG.mouse.wheel != 0.0) + scroll = FlxMath.bound(scroll + (FlxG.mouse.wheel * -20.0), !flipped ? 0.0 : -Math.max(bg.bHeight - FlxG.height*0.5, 0.0), flipped ? 0.0 : Math.max(bg.bHeight - FlxG.height*0.5, 0.0)); + + contextCam.scroll.y = CoolUtil.fpsLerp(contextCam.scroll.y, scroll, 0.5); contextCam.alpha = CoolUtil.fpsLerp(contextCam.alpha, 1, 0.25); } diff --git a/source/funkin/editors/ui/UIDropDown.hx b/source/funkin/editors/ui/UIDropDown.hx index 1cb944b8..0dbd1545 100644 --- a/source/funkin/editors/ui/UIDropDown.hx +++ b/source/funkin/editors/ui/UIDropDown.hx @@ -1,34 +1,73 @@ package funkin.editors.ui; +import flixel.util.typeLimit.OneOfTwo; + class UIDropDown extends UISliceSprite { public var dropButton:UIButton; + public var dropIcon:FlxSprite; public var label:UIText; public var index:Int = 0; public var options:Array<String>; + public var items:Array<DropDownItem>; + public var key(get, never):String; + inline function get_key():String { + return items[index].label; + } + public var value(get, never):Dynamic; + inline function get_value():Dynamic { + return items[index].value; + } public var onChange:Int->Void; var curMenu:UIContextMenu = null; - public function new(x:Float, y:Float, width:Int = 320, height:Int = 32, options:Array<String>, index:Int = 0) { + public static function getItems(options:Array<String>):Array<DropDownItem> { + return [for(i=>o in options) {label: o, value: i}]; + } + + public function new(x:Float, y:Float, width:Int = 320, height:Int = 32, _options:Array<OneOfTwo<DropDownItem, String>>, index:Int = 0) { super(x, y, width - height, height, 'editors/ui/inputbox'); // using same sprite cause fuck you - this.options = options; + var _items:Array<DropDownItem> = []; + + if(_options.length > 0) { + if(_options[0] is String) { + _items = getItems(_options); + } else { + _items = cast _options; + } + } + + this.items = _items; + this.options = [for(o in _items) o.label]; // backwards compatibility this.index = index; - cursor = BUTTON; + cursor = CLICK; - label = new UIText(0, 0, width - height, options[index]); + label = new UIText(0, 0, width - height, items[index].label); members.push(label); - dropButton = new UIButton(0, 0, "V", null, height, height); + dropButton = new UIButton(0, 0, null, null, height, height); members.push(dropButton); + + dropIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/ui/dropdown-button")); + dropIcon.antialiasing = false; + members.push(dropIcon); + } + + public static function indexOfItemValue(items:Array<DropDownItem>, value:Dynamic):Int { + return [for(o in items) o.value].indexOf(value); + } + + public static function indexOfItemLabel(items:Array<DropDownItem>, str:String):Int { + return [for(o in items) o.label].indexOf(str); } public function setOption(newIndex:Int) { if (index != (index = newIndex)) { - label.text = options[index]; + label.text = items[index].label; if (onChange != null) onChange(index); } @@ -37,28 +76,30 @@ class UIDropDown extends UISliceSprite { public override function update(elapsed:Float) { var opened = curMenu.contextMenuOpened(); framesOffset = (opened || (hovered && FlxG.mouse.pressed)) ? 18 : (hovered ? 9 : 0); - if (FlxG.mouse.justReleased && (hovered || dropButton.hovered)) { - if (opened) - UIState.state.curContextMenu.preventOutOfBoxClickDeletion(); - else - openContextMenu(); - } + if (FlxG.mouse.justReleased && (hovered || dropButton.hovered) && !opened) + openContextMenu(); super.update(elapsed); label.follow(this, 4, Std.int((bHeight - label.height) / 2)); dropButton.follow(this, bWidth - bHeight, 0); + dropIcon.follow(dropButton, (dropButton.bWidth/2)-(dropIcon.width/2), (dropButton.bHeight/2)-(dropIcon.height/2)); } public function openContextMenu() { var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]); curMenu = UIState.state.openContextMenu([ - for(k=>o in options) { + for(k=>o in items) { icon: (k == index) ? 1 : 0, - label: o + label: o.label } ], function(_, i, _) { setOption(i); }, __lastDrawCameras[0].x + screenPos.x, __lastDrawCameras[0].y + screenPos.y + bHeight); } +} + +typedef DropDownItem = { + var label:String; + var value:Dynamic; } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIFileExplorer.hx b/source/funkin/editors/ui/UIFileExplorer.hx index a122dcec..4f75fd6e 100644 --- a/source/funkin/editors/ui/UIFileExplorer.hx +++ b/source/funkin/editors/ui/UIFileExplorer.hx @@ -11,24 +11,23 @@ class UIFileExplorer extends UISliceSprite { public var deleteIcon:FlxSprite; public var file:Bytes = null; - public var onFile:Bytes->Void; + public var filePath:String = null; + public var onFile:(String, Bytes)->Void; - public var uiElement:UISprite; + public var uiElement:FlxSprite; + + public var fileType:String = "txt"; - public function new(x:Float, y:Float, ?w:Int, ?h:Int, fileType:String = "txt", ?onFile:Bytes->Void) { + public function new(x:Float, y:Float, ?w:Int, ?h:Int, fileType:String = "txt", ?onFile:(String, Bytes)->Void) { super(x, y, (w != null ? w : 320), (h != null ? h : 58), 'editors/ui/inputbox'); + this.fileType = fileType; if (onFile != null) this.onFile = onFile; - uploadButton = new UIButton(x + 8, y+ 8, "", function () { + uploadButton = new UIButton(x + 8, y+ 8, null, function () { var fileDialog = new FileDialog(); - fileDialog.onOpen.add(function(res) { - file = cast res; - deleteButton.visible = deleteButton.selectable = deleteIcon.visible = !(uploadButton.visible = uploadButton.selectable = false); - - if (this.onFile != null) this.onFile(file); - }); - fileDialog.open(fileType); + fileDialog.onSelect.add(loadFile); + fileDialog.browse(OPEN, this.fileType); }, bWidth - 16, bHeight - 16); members.push(uploadButton); @@ -36,13 +35,13 @@ class UIFileExplorer extends UISliceSprite { uploadIcon.antialiasing = false; uploadButton.members.push(uploadIcon); - deleteButton = new UIButton(x + bWidth - (bHeight - 16) - 8, y + 8, "", removeFile, bHeight - 16, bHeight - 16); + deleteButton = new UIButton(x + bWidth - (bHeight - 16) - 8, y + 8, null, removeFile, bHeight - 16, bHeight - 16); deleteButton.color = 0xFFFF0000; members.push(deleteButton); deleteIcon = new FlxSprite(deleteButton.x + ((bHeight - 16)/2) - 8, deleteButton.y + ((bHeight - 16)/2) - 8).loadGraphic(Paths.image('editors/delete-button')); deleteIcon.antialiasing = false; - members.push(deleteIcon); + deleteButton.members.push(deleteIcon); deleteButton.visible = deleteButton.selectable = deleteIcon.visible = false; } @@ -62,13 +61,21 @@ class UIFileExplorer extends UISliceSprite { } } + public function loadFile(path:String) { + file = cast sys.io.File.getBytes(filePath = path); + deleteButton.visible = deleteButton.selectable = deleteIcon.visible = !(uploadButton.visible = uploadButton.selectable = false); + + if (this.onFile != null) this.onFile(filePath, file); + } + public function removeFile() { + if (!selectable) return; if (uiElement != null) { members.remove(uiElement); uiElement.destroy(); } - file = null; + file = null; onFile(null, null); MemoryUtil.clearMajor(); deleteButton.visible = deleteButton.selectable = deleteIcon.visible = !(uploadButton.visible = uploadButton.selectable = true); diff --git a/source/funkin/editors/ui/UIIconColorPicker.hx b/source/funkin/editors/ui/UIIconColorPicker.hx new file mode 100644 index 00000000..a59dc107 --- /dev/null +++ b/source/funkin/editors/ui/UIIconColorPicker.hx @@ -0,0 +1,73 @@ +package funkin.editors.ui; + +import openfl.display.BitmapData; +import flixel.util.FlxColor; + +using funkin.backend.utils.BitmapUtil; + +class UIIconColorPicker extends UISliceSprite { + public var colorWheel:UIColorwheel; + + public var iconSprite:FlxSprite; + public var iconTextBox:UITextBox; + + public var pickerButton:UIButton; + public var pickerIcon:FlxSprite; + + public function new(x:Float, y:Float, icon:String, antialaising:Bool, colorWheel:UIColorwheel) { + super(x, y, 12+125+12, 4+125+4+32+12, 'editors/ui/inputbox'); + + this.colorWheel = colorWheel; + + iconTextBox = new UITextBox(x+12, y+4+125+4, icon, 125); + iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);} + members.push(iconTextBox); + + members.push(iconSprite = new FlxSprite()); + iconSprite.antialiasing = antialaising; + + pickerButton = new UIButton(x+12+125-26, y+12, null, colorPick, 26, 26); + pickerButton.frames = Paths.getFrames('editors/ui/inputbox'); + members.push(pickerButton); + + pickerIcon = new FlxSprite(pickerButton.x+7, pickerButton.y+7).loadGraphic(Paths.image("editors/ui/picker")); + members.push(pickerIcon); + + updateIcon(icon); + } + + var __path:String = null; + public function updateIcon(icon:String) { + if (iconSprite.animation.exists(icon)) return; + @:privateAccess iconSprite.animation.clearAnimations(); + + var path:String = Paths.image('icons/$icon'); + if (!Assets.exists(path)) path = Paths.image('icons/face'); + + iconSprite.loadGraphic(__path = path, true, 150, 150); + iconSprite.animation.add(icon, [0], 0, false); + iconSprite.animation.play(icon); + + iconSprite.scale.set(125/150, 125/150); + iconSprite.updateHitbox(); + iconSprite.setPosition(x+12, y+4); + + __cachedColor = null; + } + + var __cachedColor:Null<FlxColor> = null; + public function colorPick() { + if (__cachedColor == null) { + var imageBitmap:BitmapData = Assets.getBitmapData(__path, true, false); + __cachedColor = imageBitmap.getMostPresentColor(); + imageBitmap.dispose(); + } + + if (colorWheel != null) { + colorWheel.hue = __cachedColor.hue; + colorWheel.saturation = __cachedColor.saturation; + colorWheel.brightness = __cachedColor.brightness; + colorWheel.updateWheel(); + } + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIImageExplorer.hx b/source/funkin/editors/ui/UIImageExplorer.hx new file mode 100644 index 00000000..ecf98e19 --- /dev/null +++ b/source/funkin/editors/ui/UIImageExplorer.hx @@ -0,0 +1,379 @@ +package funkin.editors.ui; + +import flixel.util.typeLimit.OneOfTwo; +import flixel.text.FlxText.FlxTextFormat; +import flixel.text.FlxText.FlxTextFormatMarkerPair; +import flxanimate.data.SpriteMapData.AnimateAtlas; +import flixel.graphics.frames.FlxFramesCollection; +import haxe.Json; +import haxe.io.Bytes; +import haxe.io.Path; +import openfl.display.BitmapData; +import sys.FileSystem; +import sys.io.File; + +using StringTools; +using funkin.backend.utils.BitmapUtil; +using flixel.util.FlxSpriteUtil; + +typedef ImageSaveData = { + var imageName:String; + var directory:String; + var isAtlas:Bool; + var imageFiles:Map<String, OneOfTwo<String, Bytes>>; +} + +// TODO: make this limited if on web +class UIImageExplorer extends UIFileExplorer { + private var allowAtlases:Bool = true; + private var allowDirectories:Bool = true; + + public var directoryButton:UIButton; + public var directoryIcon:UIText; + + public var directoryBG:UISliceSprite; + public var directoryTextBoxLabel:UIText; + public var directoryTextBox:UITextBox; + + public function new(x:Float, y:Float, image:String, ?w:Int, ?h:Int, ?onFile:(String, Bytes)->Void, ?directory:String = "images") { + super(x, y, w, h, "png, jpg", function (filePath, file) { + if (filePath != null && file != null) uploadImage(filePath, file); + if (onFile != null) onFile(filePath, file); + }); + + deleteButton.bWidth = 26; + deleteButton.bHeight = 26; + + directoryButton = new UIButton(x + bWidth - (bHeight - 16) - 8, y + 8, null, () -> {directoryBG.visible = !directoryBG.visible;}, 26, 26); + members.push(directoryButton); + + directoryIcon = new UIText(directoryButton.x, directoryButton.y, 0, "/", 14); + members.push(directoryIcon); + + directoryBG = new UISliceSprite(0, 0, 210+16, 8+12+4+22+8, 'editors/ui/inputbox'); + directoryBG.alpha = 0.9; directoryBG.members.push(directoryTextBoxLabel = new UIText(8, 6, 0, 'Directory ($directory/)', 12)); + + directoryTextBox = new UITextBox(8, 8+12+4, "", 210, 22, false, true); + directoryBG.members.push(directoryTextBox); + + if (image != null) { + var fullImagePath:String = '${Path.normalize(Sys.getCwd())}/${Paths.image(image)}'.replace('/', '\\'); + var noExt = Path.withoutExtension(fullImagePath); + if (FileSystem.exists('$noExt\\spritemap1.png')) + fullImagePath = '$noExt\\spritemap1.png'; + + if (FileSystem.exists(fullImagePath)) + loadFile(fullImagePath); + } + + allowDirectories = CoolUtil.isMapEmpty(imageFiles); __firstLoad = false; + directoryButton.visible = directoryButton.selectable = directoryIcon.visible = directoryBG.visible = false; + } + + public var isAtlas:Bool = false; + + static var ANIMATE_ATLAS_REGEX = ~/^(?:Animation\.json|spritemap(?:\d+)?\.json)$/i; // no .zip atlases tho + static var SPRITEMAP_JSON_REGEX = ~/^(?:spritemap(?:\d+)?\.json)$/i; + static var SPRITEMAP_PNG_REGEX = ~/^(?:spritemap(?:\d+)?\.png)$/i; + + public var imageName:String = null; + public var imageFiles:Map<String, OneOfTwo<String, Bytes>> = []; + public var animationList:Array<String> = []; + + public var fileText:UIText; + + public var maxSize:FlxPoint = FlxPoint.get(700, 500); + + @:noCompletion var __firstLoad:Bool = true; + public function uploadImage(filePath:String, file:Bytes) { + __resetData(); + + var imagePath:Dynamic = Path.normalize(filePath); + + var directoryPath:String = Path.directory(imagePath); + var fileName:String = Path.withoutDirectory(imagePath).toLowerCase(); + var files:Array<String> = []; + + // CHECK ATLAS + if(allowAtlases && ANIMATE_ATLAS_REGEX.match(fileName)) { + // check if directory has the other files + files = FileSystem.readDirectory(directoryPath); + var hasAnimationJson:Bool = false; + var hasSpritemapJson:Bool = false; + for(file in files) { + if(file == "Animation.json") + hasAnimationJson = true; + else if(SPRITEMAP_JSON_REGEX.match(file.toLowerCase())) + hasSpritemapJson = true; + + if(hasAnimationJson && hasSpritemapJson) { + isAtlas = true; + break; + } + } + } else if(allowAtlases && Path.extension(fileName) == "png") { + // check if the spritemap json files point to the image + files = FileSystem.readDirectory(directoryPath); + var hasAnimationJson:Bool = false; + var foundSpritemapJson:Bool = false; + for(file in files) { + if(SPRITEMAP_JSON_REGEX.match(file)) { + var content:String = CoolUtil.removeBOM(File.getContent(Path.join([directoryPath, file]))); + var json:AnimateAtlas = Json.parse(content); + if(json.meta.image.toLowerCase() == fileName.toLowerCase()) + foundSpritemapJson = true; + } else if(file == "Animation.json") + hasAnimationJson = true; + + if(hasAnimationJson && foundSpritemapJson) { + isAtlas = true; + break; + } + } + } + + // IF ATLAS FIND SPRITMAPS + var spritemaps:Array<String> = []; + var spritemapImages:Array<String> = []; + if(isAtlas) { + // this doesn't check for gaps in the numbering + for(file in files) + if(SPRITEMAP_JSON_REGEX.match(file)) + spritemaps.push(file); + + spritemaps.sort(Reflect.compare); + for(spritemap in spritemaps) { + var content:String = CoolUtil.removeBOM(File.getContent(Path.join([directoryPath, spritemap]))); + var json:AnimateAtlas = Json.parse(content); + var imageToFind:String = json.meta.image.toLowerCase(); + for(file in files) { + // lowercase since windows does case insensitive stuff, so we use lowercase to match behavior on mac and linux + // and we also store the data from the filesystem instead of the json.meta.image + if(file.toLowerCase() == imageToFind) { + spritemapImages.push(file); + break; + } + } + } + + if(spritemapImages.length == 0) + isAtlas = false; + } + + // GATHER ANIMATIONS/DATA FILES!!! + var frames:FlxFramesCollection = null; + if (isAtlas) { + var dataPath:String = '$directoryPath/Animation.json'.replace('/', '\\'); + + if (FileSystem.exists(dataPath)) { + var dataPathFile:String = File.getContent(dataPath); + animationList = CoolUtil.getAnimsListFromAtlas(cast haxe.Json.parse(dataPathFile)); + + imageFiles.set(Path.withoutDirectory(dataPath), dataPathFile); + } + } else { + var dataPathExt:String = CoolUtil.imageHasFrameData(imagePath); + var dataPath:String = Path.withExtension(imagePath, dataPathExt); + var dataPathFile:String = !isAtlas && dataPathExt != null ? File.getContent(dataPath) : null; + + if (dataPathExt != null) { + frames = CoolUtil.loadFramesFromData(dataPathFile, dataPathExt); + animationList = CoolUtil.getAnimsListFromFrames(frames, dataPathExt); + + imageFiles.set(Path.withoutDirectory(dataPath), dataPathFile); + } + } + + // GATHER INFO!!! + var size:Float = 0; + var image:BitmapData = null; + if (isAtlas && spritemapImages.length > 0) { + var spritemapPath:String = Path.join([directoryPath, spritemapImages[0]]); + + imagePath = Path.normalize(spritemapPath); + directoryPath = Path.directory(imagePath); + fileName = Path.withoutDirectory(imagePath).toLowerCase(); + + for(spritemap in spritemapImages) { + var spritemapPath:String = Path.join([directoryPath, spritemap]); + + var info = FileSystem.stat(spritemapPath); + size += info.size; + + spritemapPath = spritemapPath.replace('/', '\\'); + imageFiles.set(Path.withoutDirectory(spritemapPath), sys.io.File.getBytes(spritemapPath)); + } + + file = cast sys.io.File.getBytes(filePath = spritemapPath); + image = BitmapData.fromBytes(file).crop(); + + } else { + size = file.length; + image = BitmapData.fromBytes(file).crop(); + + imageFiles.set(Path.withoutDirectory(imagePath), file); + } + + // DISPLAY IMAGE!! + uiElement = new FlxSprite().loadGraphic(image); + var imageScale:Float = 1; + if (uiElement.width < Math.min(300, maxSize.x) || uiElement.height < Math.min(200, maxSize.y)) + imageScale = Math.max(Math.min(300, maxSize.x) / uiElement.width, Math.min(200, maxSize.y) / uiElement.height); + else if (uiElement.width > maxSize.x || uiElement.height > maxSize.y) + imageScale = Math.min(maxSize.x/uiElement.width, maxSize.y/uiElement.height); + uiElement.scale.set(imageScale, imageScale); + uiElement.updateHitbox(); + + bWidth = Std.int(uiElement.width)+32; bHeight = Std.int(uiElement.height)+32+deleteButton.bHeight+4; + uiElement.x = x+16; uiElement.y = y+16+deleteButton.bHeight+4; + + uiElement.antialiasing = true; + members.push(uiElement); + + //if (!isAtlas && frames != null) { + // for (frame in frames.frames) { + // var rect = frame.frame; + // uiElement.drawRect(rect.x, rect.y, rect.width, rect.height, 0x00161E87, {thickness: Std.int(1.75/imageScale), color: 0xFF11178C}); + // } + //} + // GENERATE TEXT!!! + imagePath = new Path(imagePath); + imageName = isAtlas ? Path.withoutDirectory(directoryPath) : imagePath.file; + + var message = new StringBuf(); + if (isAtlas) message.add('${imageName}/'); + message.add('${imagePath.file}.${imagePath.ext}'); + message.add(' (${CoolUtil.getSizeString(size)}'); + + if (animationList.length > 0) { + if(animationList.length == 1) message.add(', ${animationList.length} ${isAtlas ? "Symbol" : "Animation"} Found'); + else message.add(', ${animationList.length} ${isAtlas ? "Symbol" : "Animation"}s Found'); + } else message.add(', #NO ${isAtlas ? "Symbol" : "Animation"}s Found#'); + + if (isAtlas) { + if(spritemaps.length == 1) message.add(', ${spritemaps.length} Spritemap Found'); + else message.add(', ${spritemaps.length} Spritemaps Found'); + } + message.add(')'); + message.add(isAtlas ? " - Atlas" : " - Spritemap"); + + fileText = new UIText(x+20, y+16, bWidth-20-deleteButton.bWidth-16, ""); + fileText.applyMarkup(message.toString(), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "#")]); + members.push(fileText); + + deleteButton.x = x + bWidth - deleteButton.bWidth - 16; + deleteButton.y = y + 12; + + deleteIcon.x = deleteButton.x + deleteButton.bWidth/2 - 8; + deleteIcon.y = deleteButton.y + deleteButton.bHeight/2 - 8; + + directoryButton.x = deleteButton.x - deleteButton.bWidth - 12; + directoryButton.y = y + 12; + + directoryIcon.x = directoryButton.x + directoryButton.bWidth/2 - (directoryIcon.width/2); + directoryIcon.y = directoryButton.y + directoryButton.bHeight/2 - (directoryIcon.height/2); + + directoryBG.x = x + bWidth - directoryBG.bWidth - 16; + directoryBG.y = directoryButton.y + directoryButton.bHeight + 8; + + directoryTextBoxLabel.x = directoryBG.x + 8; + directoryTextBoxLabel.y = directoryBG.y + 6; + + directoryTextBox.x = directoryBG.x + 8; + directoryTextBox.y = directoryBG.y + 8+12+4; + + if (!__firstLoad) allowDirectories = true; + + directoryButton.visible = directoryButton.selectable = directoryIcon.visible = allowDirectories; + directoryBG.visible = false; + if (directoryButton.visible) fileText.fieldWidth -= directoryButton.bWidth + 12; + + members.push(directoryBG); __firstLoad = false; + } + + public var saveData:ImageSaveData = null; + public inline function getSaveData():ImageSaveData { + return saveData = { + imageName: this.imageName, + directory: Path.removeTrailingSlashes(directoryTextBox.label.text), + isAtlas: this.isAtlas, + imageFiles: this.imageFiles.copy() + }; + } + + public inline function saveFiles(directory:String, ?onFinishSaving:Void->Void, ?checkExisting:Bool = true) { + UIImageExplorer.saveFilesGlobal(getSaveData(), directory, onFinishSaving, checkExisting); + } + + public static function serializeSaveDataGlobal(data:ImageSaveData):String + return haxe.Serializer.run(data); + + public static function deserializeSaveDataGlobal(data:String):ImageSaveData + return cast haxe.Unserializer.run(data); + + public static function saveFilesGlobal(imageData:ImageSaveData, directory:String, ?onFinishSaving:Void->Void, ?checkExisting:Bool = true) { + directory += '${directory.length > 0 ? '/${imageData.directory}' : ""}'; + if (imageData.isAtlas) directory += '/${imageData.imageName}'; + + var alreadlyExistingFiles:Array<String> = []; + for (name => file in imageData.imageFiles) + if (FileSystem.exists('$directory/$name')) + alreadlyExistingFiles.push('$directory/$name'); + + function deleteExistingFiles() { + for (file in alreadlyExistingFiles) + FileSystem.deleteFile(file); + alreadlyExistingFiles = []; + } + + function acuttalySaveFiles() { + for (name => file in imageData.imageFiles) + if (!alreadlyExistingFiles.contains('$directory/$name')) { + CoolUtil.safeSaveFile('$directory/$name', file); + trace('SAVED: $directory/$name'); + } + + if (onFinishSaving != null) onFinishSaving(); + } + + if (alreadlyExistingFiles.length > 0 && checkExisting) @:privateAccess { + (FlxG.state.subState != null && !FlxG.state._requestSubStateReset ? FlxG.state.subState : FlxG.state).openSubState(new UIWarningSubstate("Alreadly Existing Files!!!", + "The following files alreadly exist: \n\n" + alreadlyExistingFiles.join('\n') + "\n\nIMPORTANT: OVERRIDING CAN NOT BE UNDONE!!!!!!!!", + [ { + label: "Override Files", + color: 0xFFFF0000, + onClick: (_) -> { + deleteExistingFiles(); + acuttalySaveFiles(); + } + }, + { + label: "Use Existing", + onClick: (_) -> {if (onFinishSaving != null) onFinishSaving();} + } + ], false)); + } else acuttalySaveFiles(); + } + + public override function removeFile() { + __resetData(); + bWidth = 320; bHeight = 58; + + members.remove(fileText); + fileText.destroy(); + + members.remove(directoryBG); + directoryButton.visible = directoryButton.selectable = directoryIcon.visible = directoryBG.visible = false; + + super.removeFile(); + } + + @:noCompletion inline function __resetData() { + imageFiles.clear(); isAtlas = false; animationList = []; + } + + public override function destroy() { + maxSize.put(); + super.destroy(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UINumericStepper.hx b/source/funkin/editors/ui/UINumericStepper.hx index c561846c..ff640414 100644 --- a/source/funkin/editors/ui/UINumericStepper.hx +++ b/source/funkin/editors/ui/UINumericStepper.hx @@ -7,8 +7,8 @@ class UINumericStepper extends UITextBox { public var max:Null<Float> = null; public var step:Float = 1; - public function new(x:Float, y:Float, value:Float = 0, step:Float = 1, precision:Int = 0, ?min:Float, ?max:Float, w:Int = 180, h:Int = 32) { - super(x, y, "", w - h, h, false); + public function new(x:Float, y:Float, value:Float = 0, step:Float = 1, precision:Int = 0, ?min:Float, ?max:Float, w:Int = 180, h:Int = 32, small:Bool = false) { + super(x, y, "", w - h, h, false, small); onChange = __onChange; @@ -31,7 +31,7 @@ class UINumericStepper extends UITextBox { private function set_value(v:Float) { if (min != null && max != null) { - v = FlxMath.bound(v, min, max); + v = CoolUtil.bound(v, min, max); } else if (min != null) { v = Math.max(v, min); } else if (max != null) { diff --git a/source/funkin/editors/ui/UIRadioButton.hx b/source/funkin/editors/ui/UIRadioButton.hx new file mode 100644 index 00000000..9c009ece --- /dev/null +++ b/source/funkin/editors/ui/UIRadioButton.hx @@ -0,0 +1,102 @@ +package funkin.editors.ui; + +import flixel.util.typeLimit.OneOfTwo; + +class UIRadioButton extends UISprite { + public var checked:Bool = false; + public var onChecked:Bool->Void = null; + + public var field:UIText; + public var check:FlxSprite; + + public var parent:OneOfTwo<UIWindow, UISubstateWindow> = null; + public var forID:String = null; + + public function new(x:Float, y:Float, text:String, checked:Bool = false, forID:String = null, w:Int = 0) { + super(x, y); + this.forID = forID; + loadGraphic(Paths.image('editors/ui/radiobutton'), true, 20, 20); + for(frame=>name in ["normal", "hover", "pressed", "selected"]) + animation.add(name, [frame], 0, false); + + this.checked = checked; + this.targetScale = checked ? 1 : 0; + + field = new UIText(x, y, w, text); + check = new FlxSprite().loadGraphicFromSprite(this); + check.animation.play("selected"); + check.scale.set(targetScale, targetScale); + + members.push(check); + members.push(field); + + cursor = CLICK; + + autoAlpha = false; + } + + public var targetScale:Float = 1; + + public override function update(elapsed:Float) { + // ANIMATION HANDLING + animation.play(hovered ? (pressed ? "pressed" : "hover") : "normal"); + + // CHECKMARK HANDLING + if(autoAlpha) + check.alpha = checked ? 1 : 0; + check.scale.x = CoolUtil.fpsLerp(check.scale.x, targetScale, 0.25); + check.scale.y = CoolUtil.fpsLerp(check.scale.y, targetScale, 0.25); + + // POSITION HANDLING + updatePositions(); + + super.update(elapsed); + } + + public inline function updatePositions() { + check.follow(this); + field.follow(this, 25, 0); + } + + public override function draw() { + updatePositions(); + super.draw(); + } + + public override function onHovered() { + super.onHovered(); + if (FlxG.mouse.justReleased) { + // clicked + checked = !checked; + check.scale.set(1.25, 1.25); + targetScale = checked ? 1 : 0; + + if (Options.editorSFX) + CoolUtil.playMenuSFX(checked ? CHECKED : UNCHECKED, 0.5); + + if(parent != null) { + var members = (parent is UIWindow) ? (cast(parent, UIWindow)).members : (cast(parent, UISubstateWindow)).members; + var radios:Array<UIRadioButton> = cast members.filter((o) -> o is UIRadioButton && o != this); + //trace(radios); + for(radio in radios) { + if(radio.forID != null && radio.forID == forID) { + radio.checked = false; + radio.targetScale = 0; + } + } + } + + if (onChecked != null) + onChecked(checked); + } + } + + public override function updateButton() { + __rect.set(x, y, field.width + 30, field.height > height ? field.height : height); + UIState.state.updateRectButtonHandler(this, __rect, onHovered); + } + + public override function toString() { + return "RadioButton(" + field.text + ", " + checked + ", " + forID + ")"; + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIScaleMode.hx b/source/funkin/editors/ui/UIScaleMode.hx new file mode 100644 index 00000000..5d67bc7c --- /dev/null +++ b/source/funkin/editors/ui/UIScaleMode.hx @@ -0,0 +1,55 @@ +package funkin.editors.ui; + +import flixel.system.scaleModes.StageSizeScaleMode; + +class UIScaleMode extends StageSizeScaleMode { + override public function onMeasure(Width:Int, Height:Int):Void { + if (Width < FlxG.initialWidth || Height < FlxG.initialHeight) { + @:privateAccess { + FlxG.width = FlxG.initialWidth; + FlxG.height = FlxG.initialHeight; + } + + updateGameSize(Width, Height); + updateDeviceSize(Width, Height); + updateScaleOffset(); + updateGamePosition(); + } else { + super.onMeasure(Width, Height); + + for (camera in FlxG.cameras.list) { + camera.width = Width; + camera.height = Height; + } + } + } + + override function updateGameSize(Width:Int, Height:Int):Void + { + var ratio:Float = FlxG.width / FlxG.height; + var realRatio:Float = Width / Height; + + var scaleY:Bool = realRatio < ratio; + + if (scaleY) + { + gameSize.x = Width; + gameSize.y = Math.floor(gameSize.x / ratio); + } + else + { + gameSize.y = Height; + gameSize.x = Math.floor(gameSize.y * ratio); + } + + @:privateAccess { + for(camera in FlxG.cameras.list) { + camera.width = FlxG.initialWidth; + camera.height = FlxG.initialHeight; + } + + FlxG.width = FlxG.initialWidth; + FlxG.height = FlxG.initialHeight; + } + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIScrollBar.hx b/source/funkin/editors/ui/UIScrollBar.hx index 55514e09..b94f4cd5 100644 --- a/source/funkin/editors/ui/UIScrollBar.hx +++ b/source/funkin/editors/ui/UIScrollBar.hx @@ -24,7 +24,7 @@ class UIScrollBar extends UISprite { this.length = length; thumb = new UISliceSprite(0, 0, w, h, 'editors/ui/scrollbar'); - thumb.cursor = BUTTON; + thumb.cursor = CLICK; members.push(thumb); thumbIcon = new FlxSprite(0, 0, Paths.image('editors/ui/scrollbar-icon')); @@ -48,7 +48,7 @@ class UIScrollBar extends UISprite { if (isScrolling) { thumb.framesOffset = 18; var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); - var yPos = FlxMath.bound(FlxMath.remapToRange(mousePos.y, y, y+height, -(size/2), length + size), 0, length); + var yPos = CoolUtil.bound(FlxMath.remapToRange(mousePos.y, y, y+height, -(size/2), length + size), 0, length); if (yPos >= 0 && yPos < length) { value = yPos; if (onChange != null) diff --git a/source/funkin/editors/ui/UISliceSprite.hx b/source/funkin/editors/ui/UISliceSprite.hx index 09226205..15008dbf 100644 --- a/source/funkin/editors/ui/UISliceSprite.hx +++ b/source/funkin/editors/ui/UISliceSprite.hx @@ -5,7 +5,7 @@ import flixel.graphics.frames.FlxFrame; class UISliceSprite extends UISprite { public var bWidth:Int = 120; public var bHeight:Int = 20; - public var framesOffset:Int = 0; + public var framesOffset(default, set):Int = 0; public var incorporeal:Bool = false; @@ -14,14 +14,12 @@ class UISliceSprite extends UISprite { frames = Paths.getFrames(path); resize(w, h); + calculateFrames(); } public override function updateButton() { if (incorporeal) return; - __rect.x = x; - __rect.y = y; - __rect.width = bWidth; - __rect.height = bHeight; + __rect.set(x, y, bWidth, bHeight); UIState.state.updateRectButtonHandler(this, __rect, onHovered); } @@ -30,112 +28,198 @@ class UISliceSprite extends UISprite { bHeight = h; } + public var topAlpha:Null<Float> = null; + public var middleAlpha:Null<Float> = null; + public var bottomAlpha:Null<Float> = null; + public var drawTop:Bool = true; public var drawMiddle:Bool = true; public var drawBottom:Bool = true; + var topleft:FlxFrame = null; + var top:FlxFrame = null; + var topright:FlxFrame = null; + var middleleft:FlxFrame = null; + var middle:FlxFrame = null; + var middleright:FlxFrame = null; + var bottomleft:FlxFrame = null; + var bottom:FlxFrame = null; + var bottomright:FlxFrame = null; + + public var topHeight:Int = 0; + public var bottomHeight:Int = 0; + public var leftWidth:Int = 0; + public var rightWidth:Int = 0; + + override function set_frames(val) { + super.set_frames(val); + calculateFrames(); + return val; + } + + function set_framesOffset(value:Int) { + if(value != framesOffset) { + framesOffset = value; + calculateFrames(); + } + return value; + } + + function calculateFrames() { + if(frames == null) return; + topleft = frames.frames[framesOffset]; + top = frames.frames[framesOffset + 1]; + topright = frames.frames[framesOffset + 2]; + middleleft = frames.frames[framesOffset + 3]; + middle = frames.frames[framesOffset + 4]; + middleright = frames.frames[framesOffset + 5]; + bottomleft = frames.frames[framesOffset + 6]; + bottom = frames.frames[framesOffset + 7]; + bottomright = frames.frames[framesOffset + 8]; + + leftWidth = Std.int(MathUtil.maxSmart(topleft.frame.width, middleleft.frame.width, bottomleft.frame.width)); + rightWidth = Std.int(MathUtil.maxSmart(topright.frame.width, middleright.frame.width, bottomright.frame.width)); + topHeight = Std.int(MathUtil.maxSmart(topleft.frame.height, top.frame.height, topright.frame.height)); + bottomHeight = Std.int(MathUtil.maxSmart(topleft.frame.height, top.frame.height, topright.frame.height)); + } + + @:pure private static inline function getFixedSize(value:Float, total:Float):Float { + return value * Math.min(total / (value * 2), 1); + } + public override function draw() @:privateAccess { + var lastPixelPerfect:Bool = cameras[0] != null ? cameras[0].pixelPerfectRender : false; + if (cameras[0] != null) cameras[0].pixelPerfectRender = false; + + var topLeft = (flipX ? (flipY ? bottomright : topright) : (flipY ? bottomleft : topleft)); + var topMiddle = (flipY ? bottom : top); + var topRight = (flipX ? (flipY ? bottomleft : topleft) : (flipY ? bottomright : topright)); + var middleLeft = (flipX ? middleright : middleleft); + var middleRight = (flipX ? middleleft : middleright); + var bottomLeft = (flipX ? (flipY ? topright : bottomright) : (flipY ? topleft : bottomleft)); + var bottomMiddle = (flipY ? top : bottom); + var bottomRight = (flipX ? (flipY ? topleft : bottomleft) : (flipY ? topright : bottomright)); + var x:Float = this.x; var y:Float = this.y; if (visible && !(bWidth == 0 || bHeight == 0)) { - var topleft:FlxFrame = frames.frames[framesOffset]; - var top:FlxFrame = frames.frames[framesOffset + 1]; - var topright:FlxFrame = frames.frames[framesOffset + 2]; - var middleleft:FlxFrame = frames.frames[framesOffset + 3]; - var middle:FlxFrame = frames.frames[framesOffset + 4]; - var middleright:FlxFrame = frames.frames[framesOffset + 5]; - var bottomleft:FlxFrame = frames.frames[framesOffset + 6]; - var bottom:FlxFrame = frames.frames[framesOffset + 7]; - var bottomright:FlxFrame = frames.frames[framesOffset + 8]; + var topLeftWidth:Float = getFixedSize(topLeft.frame.width, bWidth); + var topLeftHeight:Float = getFixedSize(topLeft.frame.height, bHeight); + //var topMiddleWidth:Float = getFixedSize(topMiddle.frame.width, bWidth); + var topMiddleHeight:Float = getFixedSize(topMiddle.frame.height, bHeight); + var topRightWidth:Float = getFixedSize(topRight.frame.width, bWidth); + var topRightHeight:Float = getFixedSize(topRight.frame.height, bHeight); + var middleLeftWidth:Float = getFixedSize(middleLeft.frame.width, bWidth); + //var middleLeftHeight:Float = getFixedSize(middleLeft.frame.height, bHeight); + //var middleMiddleWidth:Float = getFixedSize(middle.frame.width, bWidth); + //var middleMiddleHeight:Float = getFixedSize(middle.frame.height, bHeight); + var middleRightWidth:Float = getFixedSize(middleRight.frame.width, bWidth); + //var middleRightHeight:Float = getFixedSize(middleRight.frame.height, bHeight); + var bottomLeftWidth:Float = getFixedSize(bottomLeft.frame.width, bWidth); + var bottomLeftHeight:Float = getFixedSize(bottomLeft.frame.height, bHeight); + //var bottomMiddleWidth:Float = getFixedSize(bottomMiddle.frame.width, bWidth); + var bottomMiddleHeight:Float = getFixedSize(bottomMiddle.frame.height, bHeight); + var bottomRightWidth:Float = getFixedSize(bottomRight.frame.width, bWidth); + var bottomRightHeight:Float = getFixedSize(bottomRight.frame.height, bHeight); + var oldAlpha = alpha; // TOP if (drawTop) { // TOP LEFT - frame = topleft; + if(topAlpha != null) alpha = topAlpha; + frame = topLeft; setPosition(x, y); __setSize( - topleft.frame.width * Math.min(bWidth/(topleft.frame.width*2), 1), - topleft.frame.height * Math.min(bHeight/(topleft.frame.height*2), 1) + topLeftWidth, + topLeftHeight ); super.drawSuper(); // TOP - if (bWidth > topleft.frame.width + topright.frame.width) { - frame = top; - setPosition(x + topleft.frame.width, y); - __setSize(bWidth - topleft.frame.width - topright.frame.width, top.frame.height * Math.min(bHeight/(top.frame.height*2), 1)); + if (bWidth > topLeft.frame.width + topRight.frame.width) { + frame = topMiddle; + setPosition(x + topLeft.frame.width, y); + __setSize( + bWidth - topLeft.frame.width - topRight.frame.width, + topMiddleHeight + ); super.drawSuper(); } // TOP RIGHT - setPosition(x + bWidth - (topright.frame.width * Math.min(bWidth/(topright.frame.width*2), 1)), y); - frame = topright; + setPosition(x + bWidth - topRightWidth, y); + frame = topRight; __setSize( - topright.frame.width * Math.min(bWidth/(topright.frame.width*2), 1), - topright.frame.height * Math.min(bHeight/(topright.frame.height*2), 1) + topRightWidth, + topRightHeight ); super.drawSuper(); } // MIDDLE - if (drawMiddle && bHeight > top.frame.height + bottom.frame.height) { - var middleHeight:Float = bHeight - (topleft.frame.height * Math.min(bHeight/(topleft.frame.height*2), 1)) - - bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1); + if (drawMiddle && bHeight > topMiddle.frame.height + bottomMiddle.frame.height) { + if(middleAlpha != null) alpha = middleAlpha; + var middleHeight:Float = bHeight - topLeftHeight - bottomLeftHeight; // MIDDLE LEFT - frame = middleleft; - setPosition(x, y + top.frame.height); - __setSize(middleleft.frame.width * Math.min(bWidth/(middleleft.frame.width*2), 1), middleHeight); + frame = middleLeft; + setPosition(x, y + topMiddle.frame.height); + __setSize(middleLeftWidth, middleHeight); super.drawSuper(); - if (bWidth > (middleleft.frame.width * Math.min(bWidth/(middleleft.frame.width*2), 1)) + middleright.frame.width) { + if (bWidth > (middleLeftWidth) + middleRight.frame.width) { // MIDDLE frame = middle; - setPosition(x + topleft.frame.width, y + top.frame.height); - __setSize(bWidth - middleleft.frame.width - middleright.frame.width, middleHeight); + setPosition(x + topLeft.frame.width, y + topMiddle.frame.height); + __setSize(bWidth - middleLeft.frame.width - middleRight.frame.width, middleHeight); super.drawSuper(); } // MIDDLE RIGHT - frame = middleright; - setPosition(x + bWidth - (topright.frame.width * Math.min(bWidth/(topright.frame.width*2), 1)), y + top.frame.height); - __setSize(middleright.frame.width * Math.min(bWidth/(middleright.frame.width*2), 1), middleHeight); + frame = middleRight; + setPosition(x + bWidth - (topRightWidth), y + topMiddle.frame.height); + __setSize(middleRightWidth, middleHeight); super.drawSuper(); } // BOTTOM if (drawBottom) { + if(bottomAlpha != null) alpha = bottomAlpha; // BOTTOM LEFT - frame = bottomleft; - setPosition(x, y + bHeight - (bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1))); + frame = bottomLeft; + setPosition(x, y + bHeight - (bottomLeftHeight)); __setSize( - bottomleft.frame.width * Math.min(bWidth/(bottomleft.frame.width*2), 1), - bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1) + bottomLeftWidth, + bottomLeftHeight ); super.drawSuper(); - if (bWidth > bottomleft.frame.width + bottomright.frame.width) { + if (bWidth > bottomLeft.frame.width + bottomRight.frame.width) { // BOTTOM - frame = bottom; - setPosition(x + bottomleft.frame.width, y + bHeight - (bottom.frame.height * Math.min(bHeight/(bottom.frame.height*2), 1))); - __setSize(bWidth - bottomleft.frame.width - bottomright.frame.width, bottom.frame.height * Math.min(bHeight/(bottom.frame.height*2), 1)); + frame = bottomMiddle; + setPosition(x + bottomLeft.frame.width, y + bHeight - (bottomMiddleHeight)); + __setSize(bWidth - bottomLeft.frame.width - bottomRight.frame.width, bottomMiddleHeight); super.drawSuper(); } // BOTTOM RIGHT - frame = bottomright; + frame = bottomRight; + setPosition( - x + bWidth - (bottomright.frame.width * Math.min(bWidth/(bottomright.frame.width*2), 1)), - y + bHeight - (bottomright.frame.height * Math.min(bHeight/(bottomright.frame.height*2), 1)) + x + bWidth - (bottomRightWidth), + y + bHeight - (bottomRightHeight) ); __setSize( - bottomright.frame.width * Math.min(bWidth/(bottomright.frame.width*2), 1), - bottomright.frame.height * Math.min(bHeight/(bottomright.frame.height*2), 1) + bottomRightWidth, + bottomRightHeight ); super.drawSuper(); + } + alpha = oldAlpha; } + if (cameras[0] != null) cameras[0].pixelPerfectRender = lastPixelPerfect; setPosition(x, y); super.drawMembers(); diff --git a/source/funkin/editors/ui/UISlider.hx b/source/funkin/editors/ui/UISlider.hx index 20fc219f..659f8a2c 100644 --- a/source/funkin/editors/ui/UISlider.hx +++ b/source/funkin/editors/ui/UISlider.hx @@ -46,7 +46,7 @@ class UISlider extends UISprite { makeGraphic(barWidth, 12, 0x00000000, true); this.drawRoundRect(0, 0, barWidth, 12, 8, 6, 0xFFC0C0C0); this.drawRoundRect(1, 1, barWidth-2, 12-2, 8, 5, 0xFF140013); - cursor = BUTTON; + cursor = CLICK; progressbar = new UISprite(centered ? barWidth/2 : 0); progressbar.makeGraphic(barWidth, 8, 0x00000000, true); @@ -64,7 +64,7 @@ class UISlider extends UISprite { var selectableBar:UISprite = new UISprite(x,y); selectableBar.loadGraphic(Paths.image("editors/ui/slider")); selectableBar.antialiasing = true; - selectableBar.cursor = BUTTON; + selectableBar.cursor = CLICK; members.push(selectableBar); switch (i) { @@ -75,12 +75,12 @@ class UISlider extends UISprite { selectableHitbox = new UISprite(x,y); selectableHitbox.makeSolid(barWidth, 18, -1); - selectableHitbox.cursor = BUTTON; + selectableHitbox.cursor = CLICK; selectableHitbox.alpha = 0; members.push(selectableHitbox); valueStepper = new UINumericStepper(x - 64 - 64, y, 1, 0.01, 2, segments[0].start, segments[segments.length-1].end, 0, 16); - valueStepper.antialiasing = valueStepper.label.antialiasing = true; + valueStepper.antialiasing = true; valueStepper.onChange = function (text:String) { @:privateAccess valueStepper.__onChange(text); this.value = valueStepper.value; @@ -103,12 +103,14 @@ class UISlider extends UISprite { valueStepper.bWidth = Std.int(FlxMath.lerp(valueStepper.bWidth, __stepperWidth, 1/2.25)); valueStepper.follow(this, -startText.width-10 - valueStepper.bWidth - 4, (height-valueStepper.bHeight)/2); + //@:privateAccess valueStepper.__framesDirty = true; + var lastBarProgress:Float = __barProgress; if (selectableHitbox.hovered && FlxG.mouse.justPressed) isSliding = true; if (isSliding) { var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); - __barProgress = FlxMath.bound(mousePos.x-x, 0, barWidth)/barWidth; + __barProgress = CoolUtil.bound(mousePos.x-x, 0, barWidth)/barWidth; mousePos.put(); if (FlxG.mouse.justReleased) isSliding = false; } @@ -120,7 +122,7 @@ class UISlider extends UISprite { visualProgress = FlxMath.lerp(visualProgress, __barProgress, 1/2.25); progressbar.follow(this, progressCentered ? barWidth/2 : 0, (height-progressbar.height)/2); - progressbar.scale.x = FlxMath.bound(visualProgress-(progressCentered?0.5:0),-1,1); + progressbar.scale.x = CoolUtil.bound(visualProgress-(progressCentered?0.5:0),-1,1); progressbar.colorTransform.color = FlxColor.interpolate(progressbar.colorTransform.color, selectableHitbox.hovered ? 0xFF7F00BF : 0xFF67009B, 1/14); selectableBar.follow(this, (visualProgress * barWidth) - (selectableBar.width/2), (height-selectableBar.height)/2); selectableBarHighlight.follow(selectableBar); diff --git a/source/funkin/editors/ui/UISoftcodedWindow.hx b/source/funkin/editors/ui/UISoftcodedWindow.hx new file mode 100644 index 00000000..e791f883 --- /dev/null +++ b/source/funkin/editors/ui/UISoftcodedWindow.hx @@ -0,0 +1,340 @@ +package funkin.editors.ui; + +import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartMetaData; +import funkin.backend.scripting.HScript; +import funkin.backend.scripting.Script; +import funkin.editors.extra.PropertyButton; +import funkin.editors.stage.elements.StageElementButton; +import funkin.editors.ui.UIDropDown; +import haxe.xml.Access; +import hscript.*; +import hscript.Expr.Error; +import hscript.Interp; +import hscript.Parser; +import haxe.io.Path; + +using StringTools; + +class UISoftcodedWindow extends UISubstateWindow { + public var saveButton:UIButton; + public var closeButton:UIButton; + + var elementMap:Map<String, UISprite> = []; + + var xmlPath:String; + var filename:String; + var customVariables:Map<String, Dynamic>; + + public function new(xmlPath:String, customVariables:Map<String, Dynamic> = null) { + super(); + this.xmlPath = xmlPath; + this.filename = Path.withoutDirectory(xmlPath); + this.customVariables = customVariables; + } + + var parser = HScript.initParser(); + var interp = new Interp(); + + function set(name:String, value:Dynamic) { + interp.variables.set(name, value); + } + + function get(name:String):Dynamic { + return interp.variables.get(name); + } + function varExists(name:String):Bool { + return interp.variables.exists(name); + } + + function execString(input:String):String { + if(input == null) return null; + if(input.indexOf("${") == -1) return input; + var text = ""; + while(input.length > 0) { + var idx = input.indexOf("${"); + if(idx == -1) { + text += input; + break; + } + text += input.substring(0, idx); + var ei = input.indexOf("}", idx); + if(ei == -1) { + text += input.substring(idx); + break; + } + var block = input.substring(idx+2, ei); + text += exec(block); + input = input.substring(ei + 1); + } + return text; + } + + function execDefault(code:String, defaultValue:Dynamic):Dynamic { + return CoolUtil.getDefault(exec(code), defaultValue); + } + + function execAtt(el:Access, key:String, ?defaultValue:Dynamic):Dynamic { + return execDefault(el.getAtt(key), defaultValue); + } + + function exec(code:String):Dynamic { + try { + if (code != null && code.trim() != "") + return interp.execute(parser.parseString(code, filename)); + } catch(e:Error) { + _errorHandler(e); + } catch(e) { + _errorHandler(new Error(ECustom(e.toString()), 0, 0, filename, 0)); + } + return null; + } + + function _errorHandler(error:Error) { + var fileName = error.origin; + var fn = '$fileName:${error.line}: '; + var err = error.toString(); + if (err.startsWith(fn)) err = err.substr(fn.length); + + Logs.traceColored([ + Logs.logText(fn, GREEN), + Logs.logText(err, RED) + ], ERROR); + } + + public override function create() { + var layout = new Access(Xml.parse(Assets.getText(Paths.xml("editors/" + xmlPath))).firstElement()); + + for(k=>e in Script.getDefaultVariables()) { + set(k, e); + } + set("self", this); + set("hasSaveButton", true); + set("hasCloseButton", true); + set("getRadioButtons", function(forID:String) { + var radios:Array<UIRadioButton> = cast members.filter((o) -> o is UIRadioButton); + return cast radios.filter((o) -> o.forID == forID); + }); + for(k=>v in customVariables) { + set(k, v); + } + set("last", null); + + if(varExists("winTitle")) winTitle = get("winTitle"); + if(varExists("winWidth")) winWidth = get("winWidth"); + if(varExists("winHeight")) winHeight = get("winHeight"); + + winTitle = execString(layout.getAtt("title").getDefault(winTitle)); + winWidth = Std.parseInt(layout.getAtt("width")).getDefault(winWidth); + winHeight = Std.parseInt(layout.getAtt("height")).getDefault(winHeight); + + super.create(); + + function addLabelOn(ui:UISprite, text:String) + add(new UIText(ui.x, ui.y - 24, 0, text)); + + function getArr(i:Iterator<Access>) { + return [for(e in i) e]; + } + + function addLayout(elements:Array<Access>) { + for(el in elements) { + var type = el.name; + + switch(type) { + case "section": + if(execAtt(el, "if", true) != true) + continue; + if(execAtt(el, "unless", false) != false) + continue; + addLayout(getArr(el.elements)); + continue; + case "set": + set(execString(el.getAtt("name")), execAtt(el, "value")); + continue; + case "exec": + exec(el.innerData); + continue; + } + + var name:String = execString(el.getAtt("name")); + var label:String = execString(el.getAtt("label")); + var x:Float = execAtt(el, "x", 0); + var y:Float = execAtt(el, "y", 0); + + var sprite:FlxSprite = switch(type) { + case "image": + var spr = XMLUtil.createSpriteFromXML(el, "", LOOP); + spr.x = x; + spr.y = y; + cast add(spr); + case "solid": + var spr = new FunkinSprite(x, y).makeSolid( + execAtt(el, "width", 100), + execAtt(el, "height", 100), + execAtt(el, "color", 0xFFFFFFFF) + ); + cast add(spr); + case "textbox": + var text:String = execString(el.getAtt("text")); + text = Std.string(execAtt(el, "value", text)); + var textBox = new UITextBox(x, y, text, execAtt(el, "width"), execAtt(el, "height"), execAtt(el, "multiline", false));//(el.getAtt("multiline").getDefault("false") == "true")); + add(textBox); + addLabelOn(textBox, label); + textBox; + case "title": + cast add(new UIText(x, y, execAtt(el, "width", 0), execString(el.getAtt("text")), execAtt(el, "size", 28))); + case "label": + cast add(new UIText(x, y - 24, execAtt(el, "width", 0), execString(el.getAtt("text")), execAtt(el, "size", 15))); + case "text": + cast add(new UIText(x, y, execAtt(el, "width", 0), execString(el.getAtt("text")), execAtt(el, "size", 15))); + case "stepper": + // x:Float, y:Float, value:Float = 0, step:Float = 1, precision:Int = 0, ?min:Float, ?max:Float, w:Int = 180, h:Int = 32 + var stepper = new UINumericStepper( + x, + y, + execAtt(el, "value", 0), + execAtt(el, "step", 1), + execAtt(el, "precision", 0), + execAtt(el, "min"), + execAtt(el, "max"), + execAtt(el, "width"), + execAtt(el, "height") + ); + add(stepper); + addLabelOn(stepper, label); + stepper; + case "checkbox": + var checkbox = new UICheckbox(x, y, execString(el.getAtt("text")), execAtt(el, "value", false)); + add(checkbox); + addLabelOn(checkbox, label); + checkbox; + case "radio": + var radio = new UIRadioButton(x, y, execString(el.getAtt("text")), execAtt(el, "value", false), execString(el.getAtt("for"))); + radio.parent = this; + add(radio); + addLabelOn(radio, label); + radio; + case "slider": + var slider = new UISlider(x, y, execAtt(el, "width"), execAtt(el, "value", 0), execAtt(el, "segments", []), execAtt(el, "centered", false)); + add(slider); + addLabelOn(slider, label); + slider; + case "dropdown": + var items:Array<DropDownItem> = []; + for(i=>node in el.nodes.item) + items.push({label: execString(node.getAtt("label")), value: execAtt(node, "value", i)}); + var curValue = execAtt(el, "value"); + var index = UIDropDown.indexOfItemValue(items, curValue); + if (index == -1) index = 0; + + var dropdown = new UIDropDown(x, y, execAtt(el, "width"), execAtt(el, "height"), items, index); + add(dropdown); + addLabelOn(dropdown, label); + dropdown; + case "buttonlist": + //x:Float, y:Float, width:Int, height:Int, windowName:String, buttonSize:FlxPoint, ?buttonOffset:FlxPoint, ?buttonSpacing:Float + var buttonlist = new UIButtonList<UIButton>( + x, + y, + execAtt(el, "width"), + execAtt(el, "height"), + execString(el.getAtt("title")).getDefault(""), + FlxPoint.get(execAtt(el, "buttonSizeX", 0), execAtt(el, "buttonSizeY", 0)), + FlxPoint.get(execAtt(el, "buttonOffsetX", 0), execAtt(el, "buttonOffsetY", 0)), + execAtt(el, "buttonSpacing") + ); + var old = get("buttonlist"); + set("buttonlist", buttonlist); + var texture = "editors/ui/inputbox"; + var code = ""; + for(node in getArr(el.elements)) { + switch(node.name) { + case "camSpacing": buttonlist.cameraSpacing = execAtt(node, "value", 0); + case "texture": texture = execString(node.getAtt("path")).getDefault(texture); + case "code": code = node.innerData; + } + } + buttonlist.frames = Paths.getFrames(texture); + if(code != "") + exec(code); + add(buttonlist); + addLabelOn(buttonlist, label); + set("buttonlist", old); + buttonlist; + // UIColorwheel + // UIAudioPlayer + // UIButtonList + // UIFileExplorer + // UIAutoCompleteTextbox + default: { + Logs.trace("Unknown element type: " + type, ERROR, RED); + Logs.trace("Skipping element: " + el.x.toString(), ERROR, RED); + continue; + } + } + + set("last", sprite); + if(name != null) set(name, sprite); + if(sprite is UISprite) elementMap.set(name, cast sprite); + } + } + + addLayout(getArr(layout.elements)); + + if(get("hasSaveButton")) { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { + saveData(); + close(); + }, 125); + saveButton.x -= saveButton.bWidth; + saveButton.y -= saveButton.bHeight; + add(saveButton); + } + + if(get("hasCloseButton")) { + var x = saveButton != null ? saveButton.x - 20 : windowSpr.x + windowSpr.bWidth - 20; + var y = saveButton != null ? saveButton.y : windowSpr.y + windowSpr.bHeight - 20; + closeButton = new UIButton(x, y, "Close", function() { + close(); + }, 125); + if(saveButton != null) + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + if(saveButton == null) + closeButton.y -= closeButton.bHeight; + add(closeButton); + } + } + + function getElement(name:String):UISprite { + return elementMap.get(name); + } + + function callFunc(name:String, args:Array<Dynamic>) { + if (interp == null) return null; + if (!interp.variables.exists(name)) return null; + + var func = interp.variables.get(name); + if (func != null && Reflect.isFunction(func)) + return Reflect.callMethod(null, func, args); + + return null; + } + + override function update(elapsed:Float) { + super.update(elapsed); + callFunc("onUpdate", [elapsed]); + } + + public function saveData() { + for (el in elementMap) { + if(el is UINumericStepper) { + var el:UINumericStepper = cast el; + @:privateAccess el.__onChange(el.label.text); + } + } + + callFunc("onSave", []); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UISprite.hx b/source/funkin/editors/ui/UISprite.hx index 1246e6de..5e0e5ee7 100644 --- a/source/funkin/editors/ui/UISprite.hx +++ b/source/funkin/editors/ui/UISprite.hx @@ -1,6 +1,7 @@ package funkin.editors.ui; import flixel.math.FlxRect; +import funkin.backend.utils.NativeAPI.CodeCursor; import openfl.ui.MouseCursor; @:allow(funkin.editors.ui.UIState) @@ -21,7 +22,7 @@ class UISprite extends FlxSprite { public var hoverCallback:Void->Void = null; - public var cursor:MouseCursor = ARROW; + public var cursor:CodeCursor = ARROW; public var focused(get, set):Bool; public var selectable:Bool = true; @@ -55,7 +56,8 @@ class UISprite extends FlxSprite { FlxCamera._defaultCameras = cameras; for(m in members) - m.update(elapsed); + if(m.exists && m.active) + m.update(elapsed); FlxCamera._defaultCameras = __oldDefCams; } @@ -114,4 +116,8 @@ class UISprite extends FlxSprite { if (hoverCallback != null) hoverCallback(); } + + public function updateSpriteRect() { + __rect.set(x, y, width, height); + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIState.hx b/source/funkin/editors/ui/UIState.hx index 1a66821d..fb578b61 100644 --- a/source/funkin/editors/ui/UIState.hx +++ b/source/funkin/editors/ui/UIState.hx @@ -1,12 +1,16 @@ package funkin.editors.ui; +import openfl.Lib; +import funkin.editors.ui.notifications.UIBaseNotification; import flixel.math.FlxPoint; import flixel.math.FlxRect; import funkin.backend.system.framerate.Framerate; +import funkin.backend.utils.NativeAPI.CodeCursor; import funkin.editors.ui.UIContextMenu.UIContextMenuCallback; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; import lime.ui.KeyCode; import lime.ui.KeyModifier; +import flixel.system.scaleModes.StageSizeScaleMode; import openfl.ui.Mouse; import openfl.ui.MouseCursor; @@ -22,11 +26,15 @@ class UIState extends MusicBeatState { public var hoveredSprite:UISprite = null; public var currentFocus:IUIFocusable = null; - public var currentCursor:MouseCursor = ARROW; + public var currentCursor:CodeCursor = ARROW; + + public var uiCameras:Array<FlxCamera> = []; private var __rect:FlxRect; private var __mousePos:FlxPoint; + static var __point:FlxPoint = new FlxPoint(); + public override function create() { __rect = new FlxRect(); __mousePos = FlxPoint.get(); @@ -59,37 +67,22 @@ class UIState extends MusicBeatState { currentFocus.onTextEdit(str, start, end); } - public inline function updateSpriteRect(spr:UISprite) { - spr.__rect.x = spr.x; - spr.__rect.y = spr.y; - spr.__rect.width = spr.width; - spr.__rect.height = spr.height; - } - public function updateButtonHandler(spr:UISprite, buttonHandler:Void->Void) { - spr.__rect.x = spr.x; - spr.__rect.y = spr.y; - spr.__rect.width = spr.width; - spr.__rect.height = spr.height; + spr.updateSpriteRect(); updateRectButtonHandler(spr, spr.__rect, buttonHandler); } public function isOverlapping(spr:UISprite, rect:FlxRect) { for(camera in spr.__lastDrawCameras) { - var pos = FlxG.mouse.getScreenPosition(camera, FlxPoint.get()); - __rect.x = rect.x; - __rect.y = rect.y; - __rect.width = rect.width; - __rect.height = rect.height; + var pos = FlxG.mouse.getScreenPosition(camera, __point); + __rect.copyFrom(rect); - __rect.x -= camera.scroll.x * spr.scrollFactor.x; - __rect.y -= camera.scroll.y * spr.scrollFactor.y; + __rect.x = __rect.x - camera.scroll.x * spr.scrollFactor.x; + __rect.y = __rect.y - camera.scroll.y * spr.scrollFactor.y; if (((pos.x > __rect.x) && (pos.x < __rect.x + __rect.width)) && ((pos.y > __rect.y) && (pos.y < __rect.y + __rect.height))) { - pos.put(); return true; } - pos.put(); } return false; } @@ -117,18 +110,29 @@ class UIState extends MusicBeatState { FlxG.sound.keysAllowed = currentFocus != null ? !(currentFocus is UITextBox) : true; - if (hoveredSprite != null) { - Mouse.cursor = hoveredSprite.cursor; - hoveredSprite = null; + if (hoveredSprite != null && hoveredSprite.cursor != null) { + NativeAPI.setCursorIcon(hoveredSprite.cursor); } else { - Mouse.cursor = currentCursor; + NativeAPI.setCursorIcon(currentCursor); } + hoveredSprite = null; } public override function destroy() { + if (resolutionAware) { + resolutionAware = false; + + for (camera in FlxG.cameras.list) { + camera.width = FlxG.initialWidth; + camera.height = FlxG.initialHeight; + } + FlxG.scaleMode = Main.scaleMode; + } + super.destroy(); __mousePos.put(); + DrawUtil.destroyDrawers(); WindowUtils.resetAffixes(); SaveWarning.reset(); @@ -145,7 +149,7 @@ class UIState extends MusicBeatState { } } - public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float) { + public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float, ?w:Int) { var state = FlxG.state; while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null)) state = state.subState; @@ -153,7 +157,32 @@ class UIState extends MusicBeatState { state.persistentDraw = true; state.persistentUpdate = true; - state.openSubState(curContextMenu = new UIContextMenu(options, callback, x.getDefault(__mousePos.x), y.getDefault(__mousePos.y))); + state.openSubState(curContextMenu = new UIContextMenu(options, callback, x.getDefault(__mousePos.x), y.getDefault(__mousePos.y), w)); return curContextMenu; } + + public function displayNotification(notification:UIBaseNotification) { + notification.cameras = uiCameras; + notification.onRemove = (notif) -> { + notification.onRemove = null; + remove(notif, true); + }; + add(notification); + notification.update(0); + notification.appearAnimation(); + // TODO: future tooltips + //notification.x = __mousePos.x; + //notification.y = __mousePos.y; + //notification.alpha = 0; + //notification.appearAnimation(); + //FlxTween.tween(notification, {x: __mousePos.x, y: __mousePos.y, alpha: 1}, .3, {ease: FlxEase.circInOut}); + } + + public static var resolutionAware:Bool = false; + public static var uiScaleMode:UIScaleMode = new UIScaleMode(); + + public static function setResolutionAware() { + resolutionAware = true; + FlxG.scaleMode = uiScaleMode; + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UISubstateWindow.hx b/source/funkin/editors/ui/UISubstateWindow.hx index 71550ef0..be975868 100644 --- a/source/funkin/editors/ui/UISubstateWindow.hx +++ b/source/funkin/editors/ui/UISubstateWindow.hx @@ -38,6 +38,7 @@ class UISubstateWindow extends MusicBeatSubstate { var winWidth:Int = 560; var winHeight:Int = 570; var winTitle:String = ""; + var winSkin:String = "editors/ui/normal-popup"; public override function create() { super.create(); @@ -72,11 +73,13 @@ class UISubstateWindow extends MusicBeatSubstate { subCam.zoom = 0.1; FlxG.cameras.add(subCam, false); - windowSpr = new UISliceSprite(0, 0, winWidth, winHeight, "editors/ui/normal-popup"); + windowSpr = new UISliceSprite(0, 0, winWidth, winHeight, winSkin); add(windowSpr); - add(titleSpr = new UIText(windowSpr.x + 25, windowSpr.y, windowSpr.bWidth - 50, winTitle, 15, -1)); - titleSpr.y = windowSpr.y + ((30 - titleSpr.height) / 2); + if(winTitle != null) { + add(titleSpr = new UIText(windowSpr.x + 25, windowSpr.y, windowSpr.bWidth - 50, winTitle, 15, -1)); + titleSpr.y = windowSpr.y + ((30 - titleSpr.height) / 2); + } FlxTween.tween(camera, {alpha: 1}, 0.25, {ease: FlxEase.cubeOut}); FlxTween.tween(camera, {zoom: 1}, 0.66, {ease: FlxEase.elasticOut}); diff --git a/source/funkin/editors/ui/UITextBox.hx b/source/funkin/editors/ui/UITextBox.hx index 42473d83..f2939ac9 100644 --- a/source/funkin/editors/ui/UITextBox.hx +++ b/source/funkin/editors/ui/UITextBox.hx @@ -17,10 +17,10 @@ class UITextBox extends UISliceSprite implements IUIFocusable { var __wasFocused:Bool = false; - public function new(x:Float, y:Float, text:String = "", width:Int = 320, height:Int = 32, multiline:Bool = false) { - super(x, y, width, height, 'editors/ui/inputbox'); + public function new(x:Float, y:Float, text:String = "", width:Int = 320, height:Int = 32, multiline:Bool = false, small:Bool = false) { + super(x, y, width, height, 'editors/ui/inputbox${small ? "-small" : ""}'); - label = new UIText(0, 0, width, text); + label = new UIText(0, 0, width, text, small ? 12 : 15); members.push(label); caretSpr = new FlxSprite(0, 0); diff --git a/source/funkin/editors/ui/UITopMenu.hx b/source/funkin/editors/ui/UITopMenu.hx index c4b0471d..d705da4e 100644 --- a/source/funkin/editors/ui/UITopMenu.hx +++ b/source/funkin/editors/ui/UITopMenu.hx @@ -46,7 +46,7 @@ class UITopMenuButton extends UISliceSprite { super(x, y, 0, 23, "editors/ui/menu-item"); this.contextMenu = contextMenu; this.parent = parent; - cursor = BUTTON; + cursor = CLICK; this.label = new UIText(4, 0, 0, label); this.label.alignment = CENTER; @@ -69,10 +69,7 @@ class UITopMenuButton extends UISliceSprite { var opened = curMenu != null ? curMenu.contextMenuOpened() : false; if(opened && FlxG.mouse.justPressed) { - __rect.x = x; - __rect.y = y; - __rect.width = bWidth; - __rect.height = bHeight; + __rect.set(x, y, bWidth, bHeight); if(UIState.state.isOverlapping(this, __rect)) { curMenu.close(); justClosed = 2; diff --git a/source/funkin/editors/ui/UIUtil.hx b/source/funkin/editors/ui/UIUtil.hx index 2d2a2843..c1054cff 100644 --- a/source/funkin/editors/ui/UIUtil.hx +++ b/source/funkin/editors/ui/UIUtil.hx @@ -1,12 +1,17 @@ package funkin.editors.ui; +import flixel.util.typeLimit.OneOfTwo; +import flixel.input.FlxInput.FlxInputState; import flixel.input.keyboard.FlxKey; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; +import flixel.group.FlxGroup; +@:access(flixel.FlxSprite) class UIUtil { public static function follow(spr:FlxSprite, target:FlxSprite, x:Float = 0, y:Float = 0) { - spr.cameras = target is UISprite ? ({var _:UISprite = cast target;_;}).__lastDrawCameras : target.cameras; - spr.setPosition(target.x + x, target.y + y); + spr._cameras = target is UISprite ? ({var _:UISprite = cast target;_;}).__lastDrawCameras : target.cameras; + spr.x = target.x + x; + spr.y = target.y + y; spr.scrollFactor.set(target.scrollFactor.x, target.scrollFactor.y); } @@ -14,6 +19,19 @@ class UIUtil { return contextMenu != null && UIState.state.curContextMenu == contextMenu; } + @:noUsing public static function fixKey(key:FlxKey):FlxKey { + return switch(key) { + #if mac + case CONTROL: WINDOWS; // Remap control to the meta key + #end + default: key; + } + } + + public static function getKeyState(key:FlxKey, Status:FlxInputState):Bool { + return FlxG.keys.checkStatus(fixKey(key), Status); + } + /** * Process all options with shortcuts present in a `Array<UIContextMenuOption>`. Also checks children. * @param topMenuOptions @@ -41,14 +59,7 @@ class UIUtil { var shouldPress = Std.int(key) > 0; if(!shouldPress) key = -key; - var k = switch(key) { - #if mac - case CONTROL: - WINDOWS; - #end - default: - key; - } + var k = fixKey(key); if (FlxG.keys.checkStatus(k, shouldPress ? JUST_PRESSED : JUST_RELEASED)) { justPressed = true; } else if (!FlxG.keys.checkStatus(k, shouldPress ? PRESSED : RELEASED)) { @@ -116,4 +127,36 @@ class UIUtil { public static inline function prettify(str:String) { return [for(s in str.split(" ")) [for(k=>l in s.split("")) k == 0 ? l.toUpperCase() : l.toLowerCase()].join("")].join(" "); } + + /** + * For when the user closes out before confriming all ui selections + * @param ui the thingy you wanna check + */ + public static function confirmUISelections(ui:Dynamic) { + var members:Array<FlxBasic> = []; + + if (Reflect.fields(ui).contains("members")) // hasField doesnt work >:( + members = Reflect.field(ui, "members"); + else return; + + for (member in members) { + if (member is UINumericStepper) @:privateAccess { + var stepper:UINumericStepper = cast member; + if (stepper.onChange != stepper.__onChange && stepper.__wasFocused) { + stepper.onChange(stepper.label.text); + stepper.__wasFocused = false; + } else stepper.__onChange(stepper.label.text); + + } else if (member is UITextBox) @:privateAccess { + var textbox:UITextBox = cast member; + if (textbox.__wasFocused) { + textbox.onChange(textbox.label.text); + textbox.__wasFocused = false; + } + } + + if (member is UISprite || ui is UIState) + confirmUISelections(cast member); + } + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIWarningSubstate.hx b/source/funkin/editors/ui/UIWarningSubstate.hx index 4f4a0380..7b1f775a 100644 --- a/source/funkin/editors/ui/UIWarningSubstate.hx +++ b/source/funkin/editors/ui/UIWarningSubstate.hx @@ -22,6 +22,8 @@ class UIWarningSubstate extends MusicBeatSubstate { _; }; + public var bHeight:Int = 232; + var title:String; var message:String; var buttons:Array<WarningButton>; @@ -69,14 +71,25 @@ class UIWarningSubstate extends MusicBeatSubstate { warnCam.zoom = 0.1; FlxG.cameras.add(warnCam, false); - var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), 232, 'editors/ui/${isError ? "normal" : "grayscale"}-popup'); + + var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), bHeight, 'editors/ui/${isError ? "normal" : "grayscale"}-popup'); + + var sprIcon:FlxSprite = new FlxSprite(spr.x + 18, spr.y + 28 + 26).loadGraphic(Paths.image('editors/warnings/${isError ? "error" : "warning"}')); + sprIcon.scale.set(1.4, 1.4); + sprIcon.updateHitbox(); + + messageSpr = new UIText(0,0, spr.bWidth - 100 - (26 * 2), message); + spr.bHeight = Std.int(bHeight + Math.abs(Math.min(sprIcon.height-messageSpr.height, 0))); + spr.x = (FlxG.width - spr.bWidth) / 2; spr.y = (FlxG.height - spr.bHeight) / 2; spr.color = isError ? 0xFFFF0000 : 0xFFFFFF00; add(spr); - add(titleSpr = new UIText(spr.x + 25, spr.y, spr.bWidth - 50, title, 15, -1)); - titleSpr.y = spr.y + ((30 - titleSpr.height) / 2); + if(title != null) { + add(titleSpr = new UIText(spr.x + 25, spr.y, spr.bWidth - 50, title, 15, -1)); + titleSpr.y = spr.y + ((30 - titleSpr.height) / 2); + } var sprIcon:FlxSprite = new FlxSprite(spr.x + 18, spr.y + 28 + 26).loadGraphic(Paths.image('editors/warnings/${isError ? "error" : "warning"}')); sprIcon.scale.set(1.4, 1.4); @@ -84,7 +97,9 @@ class UIWarningSubstate extends MusicBeatSubstate { sprIcon.antialiasing = true; add(sprIcon); - add(messageSpr = new UIText(sprIcon.x + 70 + 16 + 20, sprIcon.y + 16, spr.bWidth - 100 - (26 * 2), message)); + messageSpr.x = sprIcon.x + 70 + 16 + 20; + messageSpr.y = sprIcon.y + 16; + add(messageSpr); var xPos = (FlxG.width - (30 + (170 * buttons.length))) / 2; for(k=>b in buttons) { diff --git a/source/funkin/editors/ui/UIWindow.hx b/source/funkin/editors/ui/UIWindow.hx index 6853ce06..e6ac6388 100644 --- a/source/funkin/editors/ui/UIWindow.hx +++ b/source/funkin/editors/ui/UIWindow.hx @@ -2,19 +2,49 @@ package funkin.editors.ui; class UIWindow extends UISliceSprite { public var titleSpr:UIText; + public var collapsable:Bool = false; + public var content:FlxTypedGroup<FlxBasic>; - public override function new(x:Float, y:Float, w:Int, h:Int, title:String) { + public override function new(x:Float, y:Float, w:Int, h:Int, ?title:String) { super(x, y, w, h, "editors/ui/normal-popup"); - members.push(titleSpr = new UIText(x + 25, y, bWidth - 50, title, 15, -1)); - titleSpr.y = y + ((30 - titleSpr.height) / 2); + if(title != null && title.length != 0) { + members.push(titleSpr = new UIText(x + 25, y, bWidth - 50, title, 15, -1)); + titleSpr.y = y + ((30 - titleSpr.height) / 2); + } + + content = new FlxTypedGroup<FlxBasic>(); + members.push(content); } public override function update(elapsed:Float) { super.update(elapsed); - __rect.x = x; __rect.y = y+23; - __rect.width = bWidth; __rect.height = bHeight-23; - hovered = UIState.state.isOverlapping(this, __rect); + cursor = ARROW; + + if(collapsable) { + __rect.set(x, y, bWidth, topHeight); + + if(UIState.state.isOverlapping(this, __rect)) { + cursor = CLICK; + if(FlxG.mouse.justPressed) { + content.exists = !content.exists; + drawMiddle = !drawMiddle; + drawBottom = !drawBottom; + } + } else { + if(!content.exists) { + cursor = null; + } + } + } + + __rect.x = x; + __rect.width = bWidth; + if(content.exists) { + __rect.y = y+topHeight; + __rect.height = bHeight-topHeight; + } + hovered = content.exists && UIState.state.isOverlapping(this, __rect); } -} \ No newline at end of file +} diff --git a/source/funkin/editors/ui/notifications/UIBaseNotification.hx b/source/funkin/editors/ui/notifications/UIBaseNotification.hx new file mode 100644 index 00000000..b86ba3c7 --- /dev/null +++ b/source/funkin/editors/ui/notifications/UIBaseNotification.hx @@ -0,0 +1,166 @@ +package funkin.editors.ui.notifications; + +import flixel.group.FlxGroup; +import flixel.math.FlxRect; +import flixel.tweens.FlxTween; +import flixel.util.FlxTimer; + +using flixel.util.FlxSpriteUtil; + +class UIBaseNotification extends UISliceSprite { + public var textField:UIText; + public var closeButton:UIButton; + + public var onRemove:(notif:UIBaseNotification)->Void = null; + + public var showedAnimation:Bool = false; + public var closed:Bool = false; + + public function new(text:String, showTimer:Float = 5, corner:Corner = BOTTOM_LEFT) { + super(x, y, 340, 46, "editors/ui/inputbox"); + + closeButton = new UIButton(0, 0, "X", () -> { + if (closed) return; + closed = true; + + (new FlxTimer()).start(0.2, (_) -> {disappearAnimation();}); + }, 32, 32); + closeButton.hoverCallback = () -> { + closeButton.color = 0xFFAC3D3D; + closeButton.field.color = 0xFFffffff; + //closeButton.field.color = closeButton.hovered ? 0xFFffffff : 0xFF7f7f7f; + } + closeButton.autoAlpha = false; + closeButton.alpha = 0.0; + + textField = new UIText(0, 0, 0, text, 14); + textField.alignment = LEFT; + textField.fieldWidth = bWidth - 32 - 8; + members.push(textField); + + if(textField.height > bHeight) + bHeight = Std.int(textField.height); + + alpha = 0; + textField.alpha = 0; + + showingTimer = showTimer; + + members.push(closeButton); + + var margin = 20; + + switch(corner) { + case TOP_LEFT: + x = margin; + y = margin; + case TOP_RIGHT: + x = FlxG.width - bWidth - margin; + y = margin; + case BOTTOM_LEFT: + x = margin; + y = FlxG.height - bHeight - margin; + case BOTTOM_RIGHT: + x = FlxG.width - bWidth - margin; + y = FlxG.height - bHeight - margin; + } + + defaultX = x; + defaultY = y; + + switch(corner) { + case TOP_LEFT | BOTTOM_LEFT: + x -= bWidth - margin - 20; + case TOP_RIGHT | BOTTOM_RIGHT: + x += bWidth - margin - 20; + } + + closingX = x; + closingY = y; + + alpha = 0; + } + + public var defaultX:Float = 0; + public var defaultY:Float = 0; + + public var closingX:Float = 0; + public var closingY:Float = 0; + + public var showingTimer:Float = 5; + public var timerActive:Bool = false; + + public var progress:Float = 0; + public override function update(elapsed:Float) { + closeButton.color = 0xFFFFFFFF; + closeButton.field.color = 0xFF7f7f7f; + super.update(elapsed); + + if(timerActive) { + showingTimer -= elapsed; + if(showingTimer <= 0) { + timerActive = false; + idleDisappearAnimation(); + } + } + + textField.follow(this, 5, bHeight / 2 - textField.height / 2); + closeButton.follow(this, bWidth-closeButton.bWidth - 7, bHeight / 2 - closeButton.bHeight / 2); + closeButton.field.alpha = alpha * .7; + //closeButton.alpha = alpha; + textField.alpha = alpha; + } + + public function appearAnimation() { + x = closingX; alpha=0; FlxTween.cancelTweensOf(this, ["x", "alpha"]); + FlxTween.tween(this, {x: defaultX}, .4, {ease: FlxEase.circInOut}); + FlxTween.tween(this, {alpha: 1}, .3, {ease: FlxEase.sineOut, startDelay: .1, onComplete: (_) -> { + if(showingTimer > 0) timerActive = true; + }}); + + showedAnimation = true; + } + + public var isClosing:Bool = false; + + public function disappearAnimation() { + if (isClosing) return; + isClosing = true; + + // TODO: make it so you can click through the notification + closeButton.active = false; + closeButton.selectable = false; + closeButton.canBeHovered = false; + selectable = false; + canBeHovered = false; + + x = defaultX; alpha = 1; FlxTween.cancelTweensOf(this, ["x", "alpha"]); + FlxTween.tween(this, {alpha: 0}, .3, {ease: FlxEase.sineOut, onComplete: (_) -> { + if(onRemove != null) onRemove(this); + destroy(); + }}); + + showedAnimation = false; + } + + public function idleDisappearAnimation() { + if (isClosing) return; + isClosing = true; + + x = defaultX; alpha = 1; FlxTween.cancelTweensOf(this, ["x", "alpha"]); + FlxTween.tween(this, {alpha: 0}, .3, {ease: FlxEase.sineOut}); + FlxTween.tween(this, {x: closingX}, .4, {ease: FlxEase.circInOut, onComplete: (_) -> { + if(onRemove != null) onRemove(this); + destroy(); + }}); + + showedAnimation = false; + } +} + +enum abstract Corner(Int) { + var TOP_LEFT = 0; + var TOP_RIGHT = 1; + var BOTTOM_LEFT = 2; + var BOTTOM_RIGHT = 3; +} \ No newline at end of file diff --git a/source/funkin/editors/ui/old/OldUISliceSpriteTri.hx b/source/funkin/editors/ui/old/OldUISliceSpriteTri.hx new file mode 100644 index 00000000..e560dee5 --- /dev/null +++ b/source/funkin/editors/ui/old/OldUISliceSpriteTri.hx @@ -0,0 +1,434 @@ +package funkin.editors.ui.old; + +import flixel.math.FlxRect; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxGraphicsShader; +import flixel.graphics.tile.FlxDrawTrianglesItem.DrawData; + +class OldUISliceSpriteTri extends UISprite { + public var bWidth(default, set):Int = 120; + public var bHeight(default, set):Int = 20; + public var framesOffset(default, set):Int = 0; + + public var incorporeal:Bool = false; + + public function new(x:Float, y:Float, w:Int, h:Int, path:String) { + super(x, y); + + frames = Paths.getFrames(path); + resize(w, h); + + calculateFrames(); + __genMesh(); + } + + public override function updateButton() { + if (incorporeal) return; + __rect.set(x, y, bWidth, bHeight); + UIState.state.updateRectButtonHandler(this, __rect, onHovered); + } + + public function resize(w:Int, h:Int) { + bWidth = w; + bHeight = h; + } + + @:deprecated public var topAlpha:Null<Float> = null; + @:deprecated public var middleAlpha:Null<Float> = null; + @:deprecated public var bottomAlpha:Null<Float> = null; + + public var drawTop(default, set):Bool = true; + public var drawMiddle(default, set):Bool = true; + public var drawBottom(default, set):Bool = true; + + private function set_drawTop(value:Bool):Bool { + if(value != drawTop) { + drawTop = value; + __meshDirty = true; + } + return value; + } + + private function set_drawMiddle(value:Bool):Bool { + if(value != drawMiddle) { + drawMiddle = value; + __meshDirty = true; + } + return value; + } + + private function set_drawBottom(value:Bool):Bool { + if(value != drawBottom) { + drawBottom = value; + __meshDirty = true; + } + return value; + } + + var topleft:FlxFrame = null; + var top:FlxFrame = null; + var topright:FlxFrame = null; + var middleleft:FlxFrame = null; + var middle:FlxFrame = null; + var middleright:FlxFrame = null; + var bottomleft:FlxFrame = null; + var bottom:FlxFrame = null; + var bottomright:FlxFrame = null; + + public var topHeight:Int = 0; + public var bottomHeight:Int = 0; + public var leftWidth:Int = 0; + public var rightWidth:Int = 0; + + override function set_frames(val) { + super.set_frames(val); + __framesDirty = true; + return val; + } + + function set_framesOffset(value:Int) { + if(value != framesOffset) { + framesOffset = value; + __framesDirty = true; + } + return value; + } + + function calculateFrames() { + if(frames == null) return; + topleft = frames.frames[framesOffset]; + top = frames.frames[framesOffset + 1]; + topright = frames.frames[framesOffset + 2]; + middleleft = frames.frames[framesOffset + 3]; + middle = frames.frames[framesOffset + 4]; + middleright = frames.frames[framesOffset + 5]; + bottomleft = frames.frames[framesOffset + 6]; + bottom = frames.frames[framesOffset + 7]; + bottomright = frames.frames[framesOffset + 8]; + + leftWidth = Std.int(MathUtil.maxSmart(topleft.frame.width, middleleft.frame.width, bottomleft.frame.width)); + rightWidth = Std.int(MathUtil.maxSmart(topright.frame.width, middleright.frame.width, bottomright.frame.width)); + topHeight = Std.int(MathUtil.maxSmart(topleft.frame.height, top.frame.height, topright.frame.height)); + bottomHeight = Std.int(MathUtil.maxSmart(topleft.frame.height, top.frame.height, topright.frame.height)); + + __meshDirty = true; + } + + public override function draw() @:privateAccess { + checkEmptyFrame(); + + if (alpha != 0 && _frame.type != FlxFrameType.EMPTY) { + if (__framesDirty) calculateFrames(); + if (__meshDirty) __genMesh(); + + for (camera in cameras) + { + if (!camera.visible || !camera.exists || !isOnScreen(camera)) + continue; + + getScreenPosition(_point, camera).subtractPoint(offset); + + #if !flash + camera.drawTriangles(graphic, vertices, indices, uvtData, colors, _point, blend, false, antialiasing, colorTransform, shader); + #else + camera.drawTriangles(graphic, vertices, indices, uvtData, colors, _point, blend, false, antialiasing); + #end + + #if FLX_DEBUG + FlxBasic.visibleCount++; + #end + } + } + + super.drawMembers(); + __lastDrawCameras = cameras.copy(); + } + + var vertices:DrawData<Float> = new DrawData<Float>(); + var indices:DrawData<Int> = new DrawData<Int>(); + var uvtData:DrawData<Float> = new DrawData<Float>(); + var colors:DrawData<Int> = new DrawData<Int>(); + + var __framesDirty:Bool = false; + var __meshDirty:Bool = false; + + private function set_bWidth(value:Int):Int { + if(value != bWidth) { + bWidth = value; + __meshDirty = true; + } + return value; + } + + private function set_bHeight(value:Int):Int { + if(value != bHeight) { + bHeight = value; + __meshDirty = true; + } + return value; + } + + private static inline function getFixedSize(value:Float, total:Float):Float { + return value * Math.min(total / (value * 2), 1); + } + + private function __genMesh() { + indices.length = 0; + vertices.length = 0; + //colors.length = 0; + uvtData.length = 0; + + var sliceTL:Quad = null; + var sliceTM:Quad = null; + var sliceTR:Quad = null; + var sliceML:Quad = null; + var sliceMM:Quad = null; + var sliceMR:Quad = null; + var sliceBL:Quad = null; + var sliceBM:Quad = null; + var sliceBR:Quad = null; + + // TOP PART + if (drawTop) { + // TOP LEFT + var topLeftWidth:Float = getFixedSize(topleft.frame.width, bWidth); + var topLeftHeight:Float = getFixedSize(topleft.frame.height, bHeight); + sliceTL = __genSliceQuad( + 0, 0, + topLeftWidth, topLeftHeight, + topleft, null, null + ); + + // TOP MIDDLE + if (bWidth > topleft.frame.width + topright.frame.width) { + var topWidth:Float = bWidth - topleft.frame.width - topright.frame.width; + var topHeight:Float = getFixedSize(top.frame.height, bHeight); + sliceTM = __genSliceQuad( + topLeftWidth, 0, + topWidth, topHeight, + top, sliceTL, null + ); + } + + // TOP RIGHT + var topRightWidth:Float = getFixedSize(topright.frame.width, bWidth); + var topRightHeight:Float = getFixedSize(topright.frame.height, bHeight); + sliceTR = __genSliceQuad( + bWidth - getFixedSize(topright.frame.width, bWidth), 0, + topRightWidth, topRightHeight, + topright, sliceTM, null + ); + } + + if (drawMiddle && bHeight > top.frame.height + bottom.frame.height) { + var middleHeight:Float = bHeight - getFixedSize(topleft.frame.height, bHeight) - getFixedSize(bottomleft.frame.height, bHeight); + + // MIDDLE LEFT + var middleLeftWidth:Float = getFixedSize(middleleft.frame.width, bWidth); + sliceML = __genSliceQuad( + 0, top.frame.height, + middleLeftWidth, middleHeight, + middleleft, null, sliceTL + ); + + // MIDDLE + if (bWidth > getFixedSize(middleleft.frame.width, bWidth) + middleright.frame.width) { + var middleWidth:Float = bWidth - middleleft.frame.width - middleright.frame.width; + sliceMM = __genSliceQuad( + topleft.frame.width, top.frame.height, + middleWidth, middleHeight, + middle, sliceML, sliceTM + ); + } + + // MIDDLE RIGHT + var middleRightWidth:Float = getFixedSize(middleright.frame.width, bWidth); + sliceMR = __genSliceQuad( + bWidth - getFixedSize(middleright.frame.width, bWidth), top.frame.height, + middleRightWidth, middleHeight, + middleright, sliceMM, sliceTR + ); + } + + // BOTTOM + if (drawBottom) { + // BOTTOM LEFT + var bottomLeftWidth:Float = getFixedSize(bottomleft.frame.width, bWidth); + var bottomLeftHeight:Float = getFixedSize(bottomleft.frame.height, bHeight); + //var bottomLeftHeight:Float = bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1); + sliceBL = __genSliceQuad( + 0, bHeight - getFixedSize(bottomleft.frame.height, bHeight), + bottomLeftWidth, bottomLeftHeight, + bottomleft, null, sliceML + ); + + // BOTTOM MIDDLE + if (bWidth > bottomleft.frame.width + bottomright.frame.width) { + var bottomMiddleWidth:Float = bWidth - bottomleft.frame.width - bottomright.frame.width; + var bottomMiddleHeight:Float = getFixedSize(bottom.frame.height, bHeight); + sliceBM = __genSliceQuad( + bottomleft.frame.width, bHeight - getFixedSize(bottom.frame.height, bHeight), + bottomMiddleWidth, bottomMiddleHeight, + bottom, sliceBL, sliceMM + ); + } + + // BOTTOM RIGHT + var bottomRightWidth:Float = getFixedSize(bottomright.frame.width, bWidth); + var bottomRightHeight:Float = getFixedSize(bottomright.frame.height, bHeight); + sliceBR = __genSliceQuad( + bWidth - getFixedSize(bottomright.frame.width, bWidth), + bHeight - getFixedSize(bottomright.frame.height, bHeight), + bottomRightWidth, bottomRightHeight, + bottomright, sliceBM, sliceMR + ); + } + + __meshDirty = false; + } + + private function __genSliceQuad(x:Float, y:Float, width:Float, height:Float, frame:FlxFrame, leftSlice:Quad, topSlice:Quad):Quad { + var indicesOffset:Int = Std.int(vertices.length / 2); + + if(leftSlice != null && topSlice != null) { + indices.push(topSlice.bl); + indices.push(topSlice.br); + indices.push(indicesOffset); + + indices.push(leftSlice.tr); + indices.push(indicesOffset); + indices.push(leftSlice.br); + + vertices.push(x + width); + vertices.push(y + height); + + uvtData.push(frame.uv.width); + uvtData.push(frame.uv.height); + + // add when openfl fixes this + //colors.push(color); + + return new Quad(topSlice.bl, topSlice.br, indicesOffset, leftSlice.br); + } + if(leftSlice != null) { + indices.push(leftSlice.tr); + indices.push(indicesOffset); + indices.push(indicesOffset + 1); + + indices.push(leftSlice.tr); + indices.push(indicesOffset + 1); + indices.push(leftSlice.br); + + vertices.push(x + width); + vertices.push(y); + vertices.push(x + width); + vertices.push(y + height); + + uvtData.push(frame.uv.width); + uvtData.push(frame.uv.y); + uvtData.push(frame.uv.width); + uvtData.push(frame.uv.height); + + // add when openfl fixes this + //colors.push(color); + //colors.push(color); + + return new Quad(leftSlice.tr, indicesOffset, indicesOffset + 1, leftSlice.br); + } + if(topSlice != null) { + indices.push(topSlice.bl); + indices.push(topSlice.br); + indices.push(indicesOffset); + + indices.push(topSlice.bl); + indices.push(indicesOffset); + indices.push(indicesOffset + 1); + + vertices.push(x + width); + vertices.push(y + height); + vertices.push(x); + vertices.push(y + height); + + uvtData.push(frame.uv.width); + uvtData.push(frame.uv.height); + uvtData.push(frame.uv.x); + uvtData.push(frame.uv.height); + + // add when openfl fixes this + //colors.push(color); + //colors.push(color); + + return new Quad(topSlice.bl, topSlice.br, indicesOffset, indicesOffset + 1); + } + indices.push(indicesOffset); + indices.push(indicesOffset + 1); + indices.push(indicesOffset + 2); + + indices.push(indicesOffset); + indices.push(indicesOffset + 2); + indices.push(indicesOffset + 3); + + vertices.push(x); + vertices.push(y); + vertices.push(x + width); + vertices.push(y); + vertices.push(x + width); + vertices.push(y + height); + vertices.push(x); + vertices.push(y + height); + + uvtData.push(frame.uv.x); + uvtData.push(frame.uv.y); + uvtData.push(frame.uv.width); + uvtData.push(frame.uv.y); + uvtData.push(frame.uv.width); + uvtData.push(frame.uv.height); + uvtData.push(frame.uv.x); + uvtData.push(frame.uv.height); + + // add when openfl fixes this + //colors.push(color); + //colors.push(color); + //colors.push(color); + //colors.push(color); + + return new Quad(indicesOffset, indicesOffset + 1, indicesOffset + 2, indicesOffset + 3); + } + + @:access(flixel.FlxCamera) + override function getBoundingBox(camera:FlxCamera):FlxRect { + getScreenPosition(_point, camera); + + _rect.set(_point.x, _point.y, bWidth, bHeight); + _rect = camera.transformRect(_rect); + + // if (isPixelPerfectRender(camera)) + // _rect.floor(); + + return _rect; + } + + override public function destroy():Void + { + vertices = null; + indices = null; + uvtData = null; + colors = null; + + super.destroy(); + } +} + +final class Quad { + public var tl:Int; + public var tr:Int; + public var br:Int; + public var bl:Int; + + public function new(tl:Int, tr:Int, br:Int, bl:Int) { + this.tl = tl; + this.tr = tr; + this.br = br; + this.bl = bl; + } +} \ No newline at end of file diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index 56de1058..15a9729f 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -1,5 +1,8 @@ package funkin.game; +import sys.FileSystem; +import flixel.util.FlxSpriteUtil; +import openfl.display.Graphics; import flixel.util.typeLimit.OneOfTwo; import flixel.graphics.frames.FlxFrame; import flixel.math.FlxPoint; @@ -17,6 +20,7 @@ import funkin.backend.scripting.events.DrawEvent; import funkin.backend.system.Conductor; import funkin.backend.system.interfaces.IBeatReceiver; import funkin.backend.system.interfaces.IOffsetCompatible; +import funkin.backend.utils.MatrixUtil; import funkin.backend.utils.XMLUtil; import haxe.Exception; import haxe.io.Path; @@ -28,7 +32,7 @@ using StringTools; @:allow(funkin.desktop.editors.CharacterEditor) @:allow(funkin.game.StrumLine) @:allow(funkin.game.PlayState) -class Character extends FunkinSprite implements IBeatReceiver implements IOffsetCompatible { +class Character extends FunkinSprite implements IBeatReceiver implements IOffsetCompatible implements IPrePostDraw { public var isPlayer:Bool = false; public var curCharacter:String = Flags.DEFAULT_CHARACTER; public var sprite:String = Flags.DEFAULT_CHARACTER; @@ -44,6 +48,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public var cameraOffset:FlxPoint = FlxPoint.get(0, 0); public var globalOffset:FlxPoint = FlxPoint.get(0, 0); + public var extraOffset:FlxPoint = FlxPoint.get(0, 0); public var xml:Access; public var scripts:ScriptPack; @@ -76,16 +81,13 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if(!disableScripts) script = Script.create(Paths.script(Path.withoutExtension(Paths.xml('characters/$curCharacter')), null, true)); - else - script = new DummyScript(curCharacter); - script.load(); - - buildCharacter(xml); - scripts.call("create"); - if (script == null) script = new DummyScript(curCharacter); + script.load(); + + scripts.call("create"); + buildCharacter(xml); scripts.call("postCreate"); } @@ -101,9 +103,6 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (isPlayer != playerOffsets && switchAnims) swapLeftRightAnimations(); - var o:FlxPoint = getAnimOffset(getAnimName()); - frameOffset.set(o.x, o.y); - o.put(); if (isPlayer) flipX = !flipX; __baseFlipped = flipX; } @@ -173,13 +172,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset } /** - * Whenever the character should dance on beat or not. Set to false for `gf`, since the dance animation is automatically handled by PlayState. + * Whenever the character should dance on beat or not. */ public var danceOnBeat:Bool = true; public override function beatHit(curBeat:Int) { scripts.call("beatHit", [curBeat]); - if (danceOnBeat && (curBeat + beatOffset) % beatInterval == 0 && !__lockAnimThisFrame) + if (skipNegativeBeats && curBeat < 0) return; + if (danceOnBeat && (curBeat + beatOffset) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame) tryDance(); } @@ -206,34 +206,51 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset } public function isFlippedOffsets() - return (isPlayer != playerOffsets) != (flipX != __baseFlipped); + return debugMode ? false : (isPlayer != playerOffsets) != (flipX != __baseFlipped); + var __reversePreDrawProcedure:Bool = false; + + public function preDraw() { + if (!ghostDraw) { + x += extraOffset.x; + y += extraOffset.y; + } + + if (__reversePreDrawProcedure = isFlippedOffsets()) { + __reverseDrawProcedure = true; + flipX = !flipX; + scale.x *= -1; + } + } + + public function postDraw() { + if (__reversePreDrawProcedure) { + flipX = !flipX; + scale.x *= -1; + __reverseDrawProcedure = false; + } + + if (!ghostDraw) { + x -= extraOffset.x; + y -= extraOffset.y; + } + } + + public var ghostDraw:Bool = false; public override function draw() { var e = EventManager.get(DrawEvent).recycle(); script.call("draw", [e]); - if(!e.cancelled) { - if (isFlippedOffsets()) { - __reverseDrawProcedure = true; - flipX = !flipX; - scale.x *= -1; - - super.draw(); - - flipX = !flipX; - scale.x *= -1; - __reverseDrawProcedure = false; - } else super.draw(); - } + preDraw(); + super.draw(); + postDraw(); script.call("postDraw", [e]); } public var singAnims = ["singLEFT", "singDOWN", "singUP", "singRIGHT"]; public inline function getSingAnim(direction:Int, suffix:String = ""):String - { return singAnims[direction % singAnims.length] + suffix; - } /** * Like `playSingAnimUnsafe` but checks if the character has the animation with the suffix part, otherwise it plays the animation without the suffix part. @@ -287,6 +304,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset cameraOffset.put(); globalOffset.put(); + extraOffset.put(); } @:noCompletion var __reverseTrailProcedure:Bool = false; @@ -409,7 +427,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (holdTime != 4) xml.set("holdTime", Std.string(FlxMath.roundDecimal(holdTime, 4))); - if (flipX) xml.set("flipX", Std.string(flipX)); + var realFlipped:Bool = isPlayer ? !__baseFlipped : __baseFlipped; + if (realFlipped) xml.set("flipX", "true"); xml.set("icon", getIcon()); if (gameOverCharacter != Character.FALLBACK_DEAD_CHARACTER) xml.set("gameOverChar", gameOverCharacter); @@ -419,7 +438,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (scale.x != 1) xml.set("scale", Std.string(FlxMath.roundDecimal(scale.x, 4))); if (!antialiasing) xml.set("antialiasing", antialiasing == true ? "true" : "false"); - if (playerOffsets) xml.set("isPlayer", playerOffsets == true ? "true" : "false"); + if (isPlayer) xml.set("isPlayer", isPlayer == true ? "true" : "false"); var anims:Array<AnimData> = []; if (animsOrder != null) { diff --git a/source/funkin/game/GameOverSubstate.hx b/source/funkin/game/GameOverSubstate.hx index 2d7af30d..706b798c 100644 --- a/source/funkin/game/GameOverSubstate.hx +++ b/source/funkin/game/GameOverSubstate.hx @@ -101,30 +101,8 @@ class GameOverSubstate extends MusicBeatSubstate if (__cancelDefault) return; - if (controls.ACCEPT) - { - endBullshit(); - } - - if (controls.BACK) - { - if (PlayState.chartingMode && Charter.undos.unsaved) - game.saveWarn(false); - else { - PlayState.resetSongInfos(); - if (Charter.instance != null) Charter.instance.__clearStatics(); - - if (FlxG.sound.music != null) - FlxG.sound.music.stop(); - FlxG.sound.music = null; - - if (PlayState.isStoryMode) - FlxG.switchState(new StoryMenuState()); - else - FlxG.switchState(new FreeplayState()); - } - - } + if (controls.ACCEPT) endBullshit(); + if (controls.BACK) exit(); if (!isEnding && ((!lossSFX.playing) || (character.getAnimName() == "firstDeath" && character.isAnimFinished())) && (FlxG.sound.music == null || !FlxG.sound.music.playing)) { @@ -193,6 +171,25 @@ class GameOverSubstate extends MusicBeatSubstate }); } + function exit() + { + var event = new CancellableEvent(); + gameoverScript.call('onReturnToMenu', [event]); + + if (event.cancelled) + return; + + if (PlayState.chartingMode && Charter.undos.unsaved) game.saveWarn(false); + else { + if (Charter.instance != null) Charter.instance.__clearStatics(); + + if (FlxG.sound.music != null) FlxG.sound.music.stop(); + FlxG.sound.music = null; + + FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState()); + } + } + override function destroy() { gameoverScript.call("destroy"); diff --git a/source/funkin/game/HealthIcon.hx b/source/funkin/game/HealthIcon.hx index 5d2c8225..935eefb1 100644 --- a/source/funkin/game/HealthIcon.hx +++ b/source/funkin/game/HealthIcon.hx @@ -1,12 +1,32 @@ package funkin.game; +import flixel.graphics.FlxGraphic; +import flixel.math.FlxPoint; +import flixel.util.typeLimit.OneOfTwo; +import funkin.backend.scripting.events.healthicon.HealthIconChangeEvent; -class HealthIcon extends FlxSprite +class HealthIcon extends FunkinSprite { /** - * Used for FreeplayState! If you use it elsewhere, prob gonna annoying + * Attaches the icon to a sprite, following it's position */ public var sprTracker:FlxSprite; + /** + * Where to place the icon in relation to the sprite + * + * LEFT: Left of the sprite + * + * CENTER: Center of the sprite + * + * RIGHT: Right of the sprite + */ + public var sprTrackerAlignment:TrackerAlignment = RIGHT; + /** + * Offset of the icon in relation to the sprite + * + * By default it is set to (10, -30) and is intended to be used with Alphabet + */ + public var sprTrackerOffset:FlxPoint = new FlxPoint(10, -30); /** * The currently showing icon @@ -20,31 +40,60 @@ class HealthIcon extends FlxSprite /** * Health steps in this format: - * Min Percentage => Frame Index + * + * Min Percentage => Frame Index / Animation Name */ - public var healthSteps:Map<Int, Int> = null; + public var healthSteps:Map<Int, OneOfTwo<String, Int>> = null; /** - * current animation state + * Current animation state */ - public var curAnimState:Int = -1; + public var curAnimState:OneOfTwo<String, Int> = -1; + + /** + * The Default Scale For The Icon + * + * This is what scale the icon should return to when its bump animation is finished + */ + public var defaultScale:Float = 1; + + /** + * Whenever or not the icon is animated or not + */ + public var animated:Bool = false; + + /** + * XML Animated Icon data + * + * `null` if the icon is not animated or its invalid + */ + public var xmlData:Xml; + + /** + * Extra offsets to add when updating the hitbox + */ + public var extraOffsets:FlxPoint = FlxPoint.get(); /** * Helper for HScript who can't make maps - * @param steps Something like this: `[[0, 1], [20, 0]]` + * + * THIS IS DEPRECATED AND WILL BE REMOVED IN THE FUTURE + * + * Please set the healthSteps directly in the script instead, hscript does support maps + * + * @param steps Something like this: `[[0, 1], [20, 0]]` or `[[0, "losing"], [20, "neutral"]]` for animated icons */ - public function setHealthSteps(steps:Array<Array<Int>>) { // helper for hscript that can't do maps + @:dox(hide) @:noCompletion @:deprecated("use healthSteps instead") + public function setHealthSteps(steps:Array<Array<OneOfTwo<String, Int>>>):Void { // helper for hscript that can't do maps if (steps == null) return; healthSteps = []; - for(s in steps) + for (s in steps) if (s.length > 1) healthSteps[s[0]] = s[1]; - var am = 0; - for(k=>e in healthSteps) am++; - if (am <= 0) healthSteps = [ - 0 => 1, // losing icon - 20 => 0, // normal icon + if (Lambda.count(healthSteps) <= 0) healthSteps = [ + 0 => (this.animated ? "losing" : 1), // losing icon + 20 => (this.animated ? "neutral" : 0), // normal icon ]; } @@ -58,50 +107,357 @@ class HealthIcon extends FlxSprite scrollFactor.set(); } - public function setIcon(char:String, width:Int = 150, height:Int = 150) { - if(curCharacter != char || this.width != width || this.height != height) { - curCharacter = char; - var path = Paths.image('icons/$char'); - if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON); - - loadGraphic(path, true, width, height); - - animation.add(char, [for(i in 0...frames.frames.length) i], 0, false, isPlayer); - antialiasing = true; - animation.play(char); - - healthSteps = [ - 0 => 1, // losing icon - 20 => 0, // normal icon - ]; - - if (frames.frames.length >= 3) - healthSteps[80] = 2; // winning icon - } + /** + * Called every beat, and causes the icon to become bigger + **/ + public dynamic function bump():Void { + var iconScale = Flags.BOP_ICON_SCALE; + scale.set(defaultScale * iconScale, defaultScale * iconScale); + updateHitbox(); } - override function update(elapsed:Float) - { + /** + * Called every frame and causes the icon to become smaller + **/ + public dynamic function updateBump():Void { + var iconLerp = Flags.ICON_LERP; + scale.set(CoolUtil.fpsLerp(scale.x, defaultScale, iconLerp), CoolUtil.fpsLerp(scale.y, defaultScale, iconLerp)); + updateHitbox(); + } + + /** + * Sets the icon to the specified character + * @param char Character to set the icon to + * @param allowAnimated Whenever the icon can be animated + **/ + public function setIcon(char:String, allowAnimated:Bool = true):Void { + if (curCharacter == char) return; + + var oldIconPath = 'icons/$char'; + var newIconPath = 'icons/$char/icon'; + + if (!Assets.exists(Paths.image(oldIconPath)) && !Assets.exists(Paths.image(newIconPath))) { + char = 'face'; + oldIconPath = 'icons/$char'; + newIconPath = 'icons/$char/icon'; + } + curCharacter = char; + + var iconPath = Assets.exists(Paths.image(oldIconPath)) ? oldIconPath : newIconPath; + var iconXmlPath = Paths.getPath('images/icons/$char/data.xml'); + var iconFoundData = Assets.exists(iconXmlPath); + + try { + xmlData = iconFoundData ? Xml.parse(Assets.getText(iconXmlPath)) : null; + } catch(e) { + Logs.trace('Error while parsing icon data for $char: ${e.message}', ERROR); + xmlData = null; + } + var xmlValid = xmlData != null && (xmlData = xmlData.firstElement()) != null; + + this.animated = allowAnimated && iconFoundData && xmlValid && Paths.framesExists(Paths.image(newIconPath), true, true, true); + + var iconAmt:Int = 0; + var iconSize:Int = 0; + var iconIsPlayer = xmlValid ? xmlData.get("facing").getDefault("right").toLowerCase() == "left" : false; + + animateAtlas = null; // reset + if (this.animated) + loadSprite(Paths.image(newIconPath)); + else { + var iconAsset:FlxGraphic = FlxG.bitmap.add(Paths.image(iconPath)); + var assetW:Float = iconAsset.width; + var assetH:Float = iconAsset.height; + + iconAmt = Math.round(assetW / assetH); // Just in case the icon is in a weird aspect ratio + iconSize = Math.floor(assetW / iconAmt); + if (iconSize * iconAmt > assetW) { + iconSize = Math.floor(assetW / iconAmt); + iconAmt = Math.floor(assetW / iconSize); + } + + loadGraphic(iconAsset, true, Std.int(Math.min(iconSize, assetW)), Std.int(Math.min(iconSize, assetH))); + + setGraphicSize(150); + updateHitbox(); + + animation.add(char, [for(i in 0...iconAmt) i], 0, false, isPlayer != iconIsPlayer); + animation.play(char); + } + + if(!animation.onFinishEnd.has(animFinishCallback)) + animation.onFinishEnd.add(animFinishCallback); + if(animateAtlas != null && !animateAtlas.anim.onFinishEnd.has(animFinishCallback)) + animateAtlas.anim.onFinishEnd.add(animFinishCallback); + + var parsedSteps:Map<Int, String> = []; + + antialiasing = true; + if (xmlValid) { + if (xmlData.exists("antialiasing")) + antialiasing = xmlData.get("antialiasing").toLowerCase() == "true"; + if (xmlData.exists("offsetX")) + extraOffsets.x = Std.parseFloat(xmlData.get("offsetX")).getDefault(0); + if (xmlData.exists("offsetY")) + extraOffsets.y = Std.parseFloat(xmlData.get("offsetY")).getDefault(0); + + for (node in xmlData.elements()) + switch(node.nodeName) { + case "transition": + if (this.animated == false) { + Logs.trace('Icon ${char} data <transition> is not allowed when not animated', WARNING); + continue; + } + if (!node.exists("anim")) { + Logs.trace('Icon ${char} data <transition> is missing anim', WARNING); + continue; + } + if (!node.exists("to")) { + Logs.trace('Icon ${char} data <transition> is missing to', WARNING); + continue; + } + if (!node.exists("from")) { + Logs.trace('Icon ${char} data <transition> is missing from', WARNING); + continue; + } + + var animName = 'from-${node.get("from")}-to-${node.get("to")}'; + + var offsetX:Float = 0; + var offsetY:Float = 0; + if (node.exists("offsetX")) + offsetX = Std.parseFloat(node.get("offsetX")).getDefault(0); + else if (node.exists("offsetx")) + offsetX = Std.parseFloat(node.get("offsetx")).getDefault(0); + + if (node.exists("offsetY")) + offsetY = Std.parseFloat(node.get("offsetY")).getDefault(0); + else if (node.exists("offsety")) + offsetY = Std.parseFloat(node.get("offsety")).getDefault(0); + + addOffset(animName, offsetX, offsetY); + + addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), false); // don't allow looping for transitions + if (animateAtlas == null && animation.exists(animName)) + animation.getByName(animName).flipX = isPlayer != iconIsPlayer; + case "anim": + if (this.animated == false) { + Logs.trace('Icon ${char} data <anim> is not allowed when not animated', WARNING); + continue; + } + if (!node.exists("name")) { + Logs.trace('Icon ${char} data <anim> is missing name', WARNING); + continue; + } + if (!node.exists("anim")) { + Logs.trace('Icon ${char} data <anim> is missing anim', WARNING); + continue; + } + + var animName = node.get("name"); + + var offsetX:Float = 0; + var offsetY:Float = 0; + if (node.exists("offsetX")) + offsetX = Std.parseFloat(node.get("offsetX")).getDefault(0); + else if (node.exists("offsetx")) + offsetX = Std.parseFloat(node.get("offsetx")).getDefault(0); + + if (node.exists("offsetY")) + offsetY = Std.parseFloat(node.get("offsetY")).getDefault(0); + else if (node.exists("offsety")) + offsetY = Std.parseFloat(node.get("offsety")).getDefault(0); + + addOffset(animName, offsetX, offsetY); + + var looped:Bool = false; + if (node.exists("looped")) + looped = node.get("looped").toLowerCase() == "true"; + else if (node.exists("loop")) + looped = node.get("loop").toLowerCase() == "true"; + + addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), looped); + if (animateAtlas == null && animation.exists(animName)) + animation.getByName(animName).flipX = isPlayer != iconIsPlayer; + case "step": + if (!node.exists("percent")) { + Logs.trace('Icon ${char} data <step> is missing percent', WARNING); + continue; + } + if (!node.exists("name")) { + Logs.trace('Icon ${char} data <step> is missing name', WARNING); + continue; + } + + parsedSteps.set(Std.parseInt(node.get("percent")).getDefault(0), node.get("name")); + } + } + + if (Lambda.count(parsedSteps) > 0) { + healthSteps = parsedSteps; + } else { + var hasLosing = this.animated ? hasAnim("losing") : iconAmt >= 2; + var hasWinning = this.animated ? hasAnim("winning") : iconAmt >= 3; + + if (hasLosing) { + healthSteps = [ + 0 => (this.animated ? "losing" : 1), // losing icon + 20 => (this.animated ? "neutral" : 0), // normal icon + ]; + } else { + healthSteps = [ + 0 => (this.animated ? "neutral" : 0), // normal icon + ]; + } + if (hasWinning) + healthSteps.set(80, this.animated ? "winning" : 2); // winning icon + } + var data = getIconAnim(health); + if (data.isValid) { + if (this.animated) + playAnim(data.animState); + else + animation.curAnim.curFrame = data.animState; + curAnimState = data.animState; + } + + if (animateAtlas != null) { + @:bypassAccessor + frameWidth = 150; + @:bypassAccessor + frameHeight = 150; + extraOffsets.x -= frameWidth / 2; + extraOffsets.y -= frameHeight / 2; + updateHitbox(); + } else { + setGraphicSize(150); + updateHitbox(); + } + + defaultScale = (xmlValid && xmlData.exists("scale")) ? Std.parseFloat(xmlData.get("scale")).getDefault(scale.x) : scale.x; + scale.set(defaultScale, defaultScale); + updateHitbox(); + } + + var normalizedNames = ["neutral", "losing", "winning"]; + private function normalizeAnim(anim:OneOfTwo<String, Int>):OneOfTwo<String, Int> { + if(this.animated) { + if (anim is Int) { + var _:Int = cast anim; + if(_ >= 0 && _ < normalizedNames.length) + anim = normalizedNames[anim]; + } + } else { + if (anim is String) { + var _ = normalizedNames.indexOf(cast anim); + if(_ >= 0) + anim = _; + } + } + return anim; + } + + /** + * Gets the animation data for a specific health percentage + * @param health Health percentage + * @return Animation data (-1 if invalid) + */ + public dynamic function getIconAnim(health:Float):IconAnimData { + var i:OneOfTwo<String, Int> = -1; + var oldKey:Int = -1; + var isValid = false; + for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) { + oldKey = k; + i = icon; + isValid = true; + } + + i = normalizeAnim(i); + + return new IconAnimData(i, isValid); + } + + override function update(elapsed:Float):Void { super.update(elapsed); - if (sprTracker != null) - setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30); + if (sprTracker != null) { + setPosition(sprTracker.x + (switch sprTrackerAlignment { + case LEFT: -width; + case CENTER: (sprTracker.width - width) / 2; + case RIGHT: sprTracker.width; + }) + sprTrackerOffset.x, sprTracker.y + sprTrackerOffset.y); + } - if (animation.curAnim != null) { - var i:Int = -1; - var oldKey:Int = -1; - for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) { - oldKey = k; - i = icon; - } - if (i >= 0 && curAnimState != i) { - var event = EventManager.get(funkin.backend.scripting.events.healthicon.HealthIconChangeEvent).recycle(i, this); + if (animation.curAnim != null || (this.animated && animateAtlas != null)) { + var data = getIconAnim(health); + var localAnimState = data.animState; + + if (data.isValid && curAnimState != localAnimState) { + var event = EventManager.get(HealthIconChangeEvent).recycle(localAnimState, this); funkin.backend.scripting.GlobalScript.event("onHealthIconAnimChange", event); - if (!event.cancelled) - animation.curAnim.curFrame = event.amount; + if (!event.cancelled) { + if (this.animated) { + var transAnim = 'from-$curAnimState-to-${event.anim}'; + playAnim(hasAnim(transAnim) ? transAnim : event.anim); + } else { + if(animation.curAnim != null) + animation.curAnim.curFrame = event.anim; + } + } - curAnimState = event.amount; + curAnimState = localAnimState; } } } + + function animFinishCallback(anim:String):Void { + if (this.animated) + if (anim.startsWith("from-")) + playAnim(anim.substr(anim.lastIndexOf('-') + 1)); + } + + override function updateHitbox():Void { + super.updateHitbox(); + offset += extraOffsets; + } } + +@:dox(hide) +class IconAnimData { + public var animState:OneOfTwo<String, Int>; + public var isValid:Bool; + + public function new(animState:OneOfTwo<String, Int>, isValid:Bool) { + this.animState = animState; + this.isValid = isValid; + } + + public function toString():String { + return '$animState (Valid: $isValid)'; + } +} + +/** + * Used for `funkin.game.HealthIcon.sprTrackerAlignment`. + * This determines the position of the icon in relation to the sprite tracker. +**/ +enum abstract TrackerAlignment(Int) { + /** + * Left of the sprite tracker + * + * Mathematically: `tracker.x - icon.width` + */ + var LEFT = 0; + /** + * Center of the sprite tracker + * + * Mathematically: `tracker.x + (tracker.width - icon.width) / 2` + */ + var CENTER = 1; + /** + * Right of the sprite tracker + * + * Mathematically: `tracker.x + tracker.width` + */ + var RIGHT = 2; +} \ No newline at end of file diff --git a/source/funkin/game/Note.hx b/source/funkin/game/Note.hx index d14b87fd..10519461 100644 --- a/source/funkin/game/Note.hx +++ b/source/funkin/game/Note.hx @@ -75,6 +75,11 @@ class Note extends FlxSprite public var scrollSpeed:Null<Float> = null; public var noteAngle:Null<Float> = null; + public var copyStrumAngle:Bool = true; + public var updateNotesPosX:Bool = true; + public var updateNotesPosY:Bool = true; + public var updateFlipY:Bool = true; + public var noteType(get, never):String; @:dox(hide) public var __strumCameras:Array<FlxCamera> = null; @@ -146,7 +151,9 @@ class Note extends FlxSprite case 0: animation.addByPrefix('scroll', 'purple0'); animation.addByPrefix('hold', 'purple hold piece'); - animation.addByPrefix('holdend', 'pruple end hold'); + animation.addByPrefix("holdend", "pruple end hold"); + if (animation.exists("holdend") != true) // null or false + animation.addByPrefix('holdend', 'purple hold end'); case 1: animation.addByPrefix('scroll', 'blue0'); animation.addByPrefix('hold', 'blue hold piece'); @@ -191,7 +198,7 @@ class Note extends FlxSprite } public var lastScrollSpeed:Null<Float> = null; - public var gapFix:Single = 0; + public var gapFix:SingleOrFloat = 0; public var useAntialiasingFix(get, set):Bool; inline function set_useAntialiasingFix(v:Bool) { if(v != useAntialiasingFix) { @@ -211,7 +218,7 @@ class Note extends FlxSprite override function drawComplex(camera:FlxCamera) { var downscrollCam = (camera is HudCamera ? ({var _:HudCamera=cast camera;_;}).downscroll : false); - flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0)); + if (updateFlipY) flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0)); if (downscrollCam) { frameOffset.y += __notePosFrameOffset.y * 2; super.drawComplex(camera); @@ -267,30 +274,22 @@ class Note extends FlxSprite public function updateSustain(strum:Strum) { var scrollSpeed = strum.getScrollSpeed(this); - var len = 0.45 * CoolUtil.quantize(scrollSpeed, 100); - - if (nextSustain != null && lastScrollSpeed != scrollSpeed) { - // is long sustain + if (lastScrollSpeed != scrollSpeed) { lastScrollSpeed = scrollSpeed; - - scale.y = (sustainLength * len) / frameHeight; - updateHitbox(); - scale.y += gapFix / frameHeight; + if (nextSustain != null) { + scale.y = (sustainLength * 0.45 * scrollSpeed) / frameHeight; + updateHitbox(); + scale.y += gapFix / frameHeight; + } } - if (!wasGoodHit) return; - var t = FlxMath.bound((Conductor.songPosition - strumTime) / (height) * len, 0, 1); - var swagRect = this.clipRect == null ? new FlxRect() : this.clipRect; - swagRect.x = 0; - swagRect.y = t * frameHeight; - swagRect.width = frameWidth; - swagRect.height = frameHeight; - - setClipRect(swagRect); + updateSustainClip(); } - public inline function setClipRect(rect:FlxRect) { - this.clipRect = rect; + public function updateSustainClip() if (wasGoodHit) { + var t = FlxMath.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1); + var rect = clipRect == null ? FlxRect.get() : clipRect; + clipRect = rect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t)); } @:noCompletion @@ -306,5 +305,7 @@ class Note extends FlxSprite public override function destroy() { super.destroy(); + + clipRect = FlxDestroyUtil.put(clipRect); } } diff --git a/source/funkin/game/NoteGroup.hx b/source/funkin/game/NoteGroup.hx index ac56dfe8..ec67ba5c 100644 --- a/source/funkin/game/NoteGroup.hx +++ b/source/funkin/game/NoteGroup.hx @@ -65,9 +65,9 @@ class NoteGroup extends FlxTypedGroup<Note> { } } - public override function draw() { - @:privateAccess var oldDefaultCameras = FlxCamera._defaultCameras; - @:privateAccess if (cameras != null) FlxCamera._defaultCameras = cameras; + public override function draw() @:privateAccess { + var oldDefaultCameras = FlxCamera._defaultCameras; + if (_cameras != null) FlxCamera._defaultCameras = _cameras; var oldCur = __currentlyLooping; __currentlyLooping = true; @@ -84,7 +84,7 @@ class NoteGroup extends FlxTypedGroup<Note> { } __currentlyLooping = oldCur; - @:privateAccess FlxCamera._defaultCameras = oldDefaultCameras; + FlxCamera._defaultCameras = oldDefaultCameras; } /** diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index e84034f0..b06d5f82 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -7,6 +7,7 @@ import flixel.math.FlxPoint; import flixel.sound.FlxSound; import flixel.text.FlxText; import flixel.tweens.FlxTween; +import flixel.tweens.misc.VarTween; import flixel.ui.FlxBar; import flixel.util.FlxColor; import flixel.util.FlxSort; @@ -18,9 +19,9 @@ import funkin.backend.chart.EventsData; import funkin.backend.scripting.DummyScript; import funkin.backend.scripting.Script; import funkin.backend.scripting.ScriptPack; +import funkin.backend.scripting.events.*; import funkin.backend.scripting.events.gameplay.*; import funkin.backend.scripting.events.note.*; -import funkin.backend.scripting.events.*; import funkin.backend.system.Conductor; import funkin.backend.system.RotatingSpriteGroup; import funkin.editors.SaveWarning; @@ -29,7 +30,7 @@ import funkin.editors.charter.CharterSelection; import funkin.game.SplashHandler; import funkin.game.cutscenes.*; import funkin.menus.*; -import funkin.menus.StoryMenuState.WeekData; +import funkin.backend.week.WeekData; import funkin.savedata.FunkinSave; import haxe.io.Path; @@ -69,15 +70,15 @@ class PlayState extends MusicBeatState */ public static var fromMods:Bool = false; /** - * Whenever Charting Mode has been enabled for this song. + * Whenever Charting Mode is enabled for this song. */ public static var chartingMode:Bool = false; /** - * Whenever the song has been started with opponent mode on. + * Whenever the song is started with opponent mode on. */ public static var opponentMode:Bool = Flags.DEFAULT_OPPONENT_MODE; /** - * Whenever the song has been started with co-op mode on. + * Whenever the song is started with co-op mode on. */ public static var coopMode:Bool = Flags.DEFAULT_COOP_MODE; @@ -138,8 +139,8 @@ class PlayState extends MusicBeatState */ public var downscroll(get, set):Bool; - @:dox(hide) private function set_downscroll(v:Bool) {return camHUD.downscroll = v;} - @:dox(hide) private function get_downscroll():Bool {return camHUD.downscroll;} + private inline function set_downscroll(v:Bool) return camHUD.downscroll = v; + private inline function get_downscroll():Bool return camHUD.downscroll; /** * Instrumental sound (Inst.ogg). @@ -225,23 +226,6 @@ class PlayState extends MusicBeatState */ public var doIconBop:Bool = true; - /** - * Whenever cam zooming is enabled, enables on a note hit if not cancelled. - */ - public var camZooming:Bool = false; - /** - * Interval of cam zooming (beats). - * For example: if set to 4, the camera will zoom every 4 beats. - */ - public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL; - /** - * How strong the cam zooms should be (defaults to 1) - */ - public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH; - /** - * Maximum amount of zoom for the camera. - */ - public var maxCamZoom:Float = Flags.MAX_CAMERA_ZOOM; /** * Current song name (lowercase) */ @@ -263,12 +247,12 @@ class PlayState extends MusicBeatState /** * Current health. Goes from 0 to maxHealth (defaults to 2) */ - public var health:Float = 1; + public var health(default, set):Float = 1; /** * Maximum health the player can have. Defaults to 2. */ - @:isVar public var maxHealth(get, set):Float = Flags.DEFAULT_MAX_HEALTH; + public var maxHealth(default, set):Float = Flags.DEFAULT_MAX_HEALTH; /** * Current combo. */ @@ -304,6 +288,11 @@ class PlayState extends MusicBeatState * Opponent's icon */ public var iconP2:HealthIcon; + /** + * Every active icon that will be updated during gameplay (defaults to `iconP1` and `iconP1` between `create` and `postCreate` in scripts) + */ + public var iconArray:Array<HealthIcon> = []; + /** * Camera for the HUD (notes, misses). */ @@ -383,6 +372,45 @@ class PlayState extends MusicBeatState */ public var camHUDZoomLerp:Float = Flags.DEFAULT_HUD_ZOOM_LERP; + /** + * Whenever cam zooming is enabled, enables on a note hit if not cancelled. + */ + public var camZooming:Bool = false; + /** + * Interval of cam zooming (in conductor values). + * Example: If Interval is 1 and Beat Type is on MEASURE, it'll zoom every a measure. + * NOTE: Will set to 4 if not found any other time signatures unlike 4/4 + */ + public var camZoomingInterval:Float = Flags.DEFAULT_CAM_ZOOM_INTERVAL; + /** + * Number of Conductor values to offset camZooming by. + */ + public var camZoomingOffset:Float = Flags.DEFAULT_CAM_ZOOM_OFFSET; + /** + * Beat type for interval of cam zooming. + * Example: If Beat Type is on STEP and Interval is 2, it'll zoom every 2 steps. + * NOTE: Will set to BEAT if not found any other time signatures unlike 4/4 + */ + public var camZoomingEvery:BeatType = MEASURE; + /** + * Stores what was the last beat for the cam zooming intervals. + */ + public var camZoomingLastBeat:Float; + /** + * How strong the cam zooms should be (defaults to 1) + */ + public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH; + /** + * Default multiplier for `maxCamZoom`. + */ + public var maxCamZoomMult:Float = Flags.MAX_CAMERA_ZOOM_MULT; + /** + * Maximum amount of zoom for the camera (based on `maxCamZoomMult` and the camera's zoom IF not set). + */ + public var maxCamZoom(get, default):Float = Math.NaN; + + private inline function get_maxCamZoom() return Math.isNaN(maxCamZoom) ? maxCamZoomMult * defaultCamZoom : maxCamZoom; + /** * Zoom for the pixel assets. */ @@ -496,7 +524,7 @@ class PlayState extends MusicBeatState @:noCompletion @:dox(hide) private var _endSongCalled:Bool = false; @:dox(hide) - var __vocalOffsetViolation:Float = 0; + var __vocalSyncTimer:Float = 0; private function get_accuracy():Float { if (accuracyPressedNotes <= 0) return -1; @@ -532,10 +560,10 @@ class PlayState extends MusicBeatState */ public function updateRating() { var rating = null; - var acc = get_accuracy(); + var acc = accuracy; // caching since it has a getter with an operation - Nex - for(e in comboRatings) - if (e.percent <= acc && (rating == null || rating.percent < e.percent)) + if (comboRatings != null && comboRatings.length > 0) for (e in comboRatings) + if ((e.percent <= acc && e.maxMisses >= misses) && (rating == null || (rating.percent < e.percent && e.maxMisses >= misses))) rating = e; var event = gameAndCharsEvent("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating)); @@ -543,13 +571,13 @@ class PlayState extends MusicBeatState curRating = event.rating; } - private inline function get_maxHealth() - return this.maxHealth; - private function set_maxHealth(v:Float) { - if (healthBar != null && healthBar.max == this.maxHealth) { - healthBar.setRange(healthBar.min, v); - } - return this.maxHealth = v; + private inline function set_health(v:Float) + return health = FlxMath.bound(v, 0, maxHealth); + private inline function set_maxHealth(v:Float) { + if (healthBar != null && healthBar.max == maxHealth) healthBar.setRange(healthBar.min, v); + maxHealth = v; + health = health; // running the setter - Nex + return maxHealth; } private inline function get_curStage() @@ -581,6 +609,7 @@ class PlayState extends MusicBeatState @:dox(hide) override public function create() { Note.__customNoteTypeExists = []; + // SCRIPTING & DATA INITIALIZATION #if REGION instance = this; @@ -609,8 +638,8 @@ class PlayState extends MusicBeatState detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay"; // Checks if cutscene files exists - var cutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene'); - var endCutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene-end'); + var cutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene'); + var endCutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene-end'); if (Assets.exists(cutscenePath)) cutscene = cutscenePath; if (Assets.exists(endCutscenePath)) endCutscene = endCutscenePath; @@ -637,10 +666,11 @@ class PlayState extends MusicBeatState // case "": // ADD YOUR HARDCODED SCRIPTS HERE! default: - var scriptsFolders:Array<String> = ['songs/${SONG.meta.name.toLowerCase()}/scripts', 'data/charts/', 'songs/']; + var normal = 'songs/${SONG.meta.name}/scripts'; + var scriptsFolders:Array<String> = [normal, normal + '/$difficulty/', 'data/charts/', 'songs/']; - for(folder in scriptsFolders) { - for(file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) { + for (folder in scriptsFolders) { + for (file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) { if (folder == 'data/charts/') Logs.warn('data/charts/ is deprecated and will be removed in the future. Please move script $file to songs/', DARKYELLOW, "PlayState"); @@ -652,7 +682,7 @@ class PlayState extends MusicBeatState for (event in SONG.events) songEvents.pushOnce(event.name); for (file in Paths.getFolderContent('data/events/', true, fromMods ? MODS : BOTH)) { - var fileName:String = Path.withoutExtension(Path.withoutDirectory(file)); + var fileName:String = CoolUtil.getFilename(file); if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName)) { addScript(file); } @@ -701,14 +731,16 @@ class PlayState extends MusicBeatState case 2: "girlfriend"; }) : strumLine.position; if (strumLine.characters != null) for(k=>charName in strumLine.characters) { - var char = new Character(0, 0, charName, stage.isCharFlipped(charPosName, strumLine.type == 1)); + var char = new Character(0, 0, charName, stage.isCharFlipped(stage.characterPoses[charName] != null ? charName : charPosName, strumLine.type == 1)); stage.applyCharStuff(char, charPosName, k); chars.push(char); } var strOffset:Float = strumLine.strumLinePos != null ? strumLine.strumLinePos : (strumLine.type == 1 ? 0.75 : 0.25); var strScale:Float = strumLine.strumScale != null ? strumLine.strumScale : 1; - var strXPos:Float = (FlxG.width * strOffset) - (Note.swagWidth * strScale * 2); + var strSpacing:Float = strumLine.strumSpacing == null ? 1 : strumLine.strumSpacing; + var keyCount:Int = strumLine.keyCount == null ? 4 : strumLine.keyCount; + var strXPos:Float = StrumLine.calculateStartingXPos(strOffset, strScale, strSpacing, keyCount); var startingPos:FlxPoint = strumLine.strumPos != null ? FlxPoint.get(strumLine.strumPos[0] == 0 ? strXPos : strumLine.strumPos[0], strumLine.strumPos[1]) : FlxPoint.get(strXPos, this.strumLine.y); @@ -780,9 +812,9 @@ class PlayState extends MusicBeatState if (Flags.DEFAULT_HEALTH != null) health = Flags.DEFAULT_HEALTH; else health = maxHealth / 2; - iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true); - iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false); - for(icon in [iconP1, iconP2]) { + iconArray.push(iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true)); + iconArray.push(iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false)); + for (icon in iconArray) { icon.y = healthBar.y - (icon.height / 2); add(icon); } @@ -799,7 +831,8 @@ class PlayState extends MusicBeatState scoreTxt.alignment = RIGHT; missesTxt.alignment = CENTER; accuracyTxt.alignment = LEFT; - updateRatingStuff(); + if (updateRatingStuff != null) + updateRatingStuff(); for(e in [healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt]) e.cameras = [camHUD]; @@ -824,10 +857,7 @@ class PlayState extends MusicBeatState SaveWarning.showWarning = Charter.undos.unsaved; SaveWarning.selectionClass = CharterSelection; SaveWarning.warningFunc = saveWarn; - SaveWarning.saveFunc = () -> { - @:privateAccess Chart.save('${Paths.getAssetsRoot()}/songs/${Charter.__song.toLowerCase()}', - SONG, Charter.__diff.toLowerCase(), {saveMetaInChart: false, prettyPrint: Options.editorPrettyPrint}); - } + SaveWarning.saveFunc = () -> Charter.saveEverything(false); } } @@ -839,7 +869,8 @@ class PlayState extends MusicBeatState // Make icons appear in the correct spot during cutscenes healthBar.update(0); - updateIconPositions(); + if (updateIconPositions != null) + updateIconPositions(); __updateNote_event = EventManager.get(NoteUpdateEvent); @@ -848,6 +879,7 @@ class PlayState extends MusicBeatState /** * Function used to update Discord Presence. + * * This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts. */ public dynamic function updateDiscordPresence() @@ -868,7 +900,7 @@ class PlayState extends MusicBeatState return; } - var songName = SONG.meta.name.toLowerCase(); + var songName = SONG.meta.name; if (cutsceneScriptPath == null) cutsceneScriptPath = Paths.script('songs/$songName/${prefix}cutscene'); @@ -981,25 +1013,10 @@ class PlayState extends MusicBeatState inst.onComplete = endSong; - if (!paused) { - FlxG.sound.setMusic(inst); - FlxG.sound.music.play(); - } - vocals.play(); - - vocals.pause(); - inst.pause(); - for (strumLine in strumLines.members) { - strumLine.vocals.play(); - strumLine.vocals.pause(); - } - inst.time = vocals.time = (chartingMode && Charter.startHere) ? Charter.startTime : 0; - for (strumLine in strumLines.members) { - strumLine.vocals.time = vocals.time; - strumLine.vocals.play(); - } - vocals.play(); - inst.play(); + var time = (chartingMode && Charter.startHere) ? Charter.startTime : 0; + for (strumLine in strumLines.members) strumLine.vocals.play(true, time); + vocals.play(true, time); + inst.play(true, time); updateDiscordPresence(); @@ -1010,15 +1027,14 @@ class PlayState extends MusicBeatState scripts.call("destroy"); for(g in __cachedGraphics) g.useCount--; - @:privateAccess - for (strumLine in strumLines.members) - FlxG.sound.destroySound(strumLine.vocals); - super.destroy(); - scripts = FlxDestroyUtil.destroy(scripts); @:privateAccess { - FlxG.sound.destroySound(inst); + for (strumLine in strumLines.members) FlxG.sound.destroySound(strumLine.vocals); + if (FlxG.sound.music != inst) FlxG.sound.destroySound(inst); FlxG.sound.destroySound(vocals); } + scripts = FlxDestroyUtil.destroy(scripts); + + super.destroy(); WindowUtils.resetAffixes(); SaveWarning.reset(); @@ -1028,47 +1044,55 @@ class PlayState extends MusicBeatState Note.__customNoteTypeExists = []; } - public static function resetSongInfos() { - deathCounter = 0; - seenCutscene = false; - } + @:dox(hide) @:deprecated("scrollSpeedTween is deprecated, use eventsTween['scrollSpeed'] instead") + public var scrollSpeedTween(get, set):FlxTween; + private inline function get_scrollSpeedTween() return eventsTween.get("scrollSpeed"); + private inline function set_scrollSpeedTween(val:FlxTween) {eventsTween.set("scrollSpeed", val); return val;} + // End of backwards compat @:dox(hide) private function generateSong(?songData:ChartData):Void { if (songData == null) songData = SONG; - events = songData.events != null ? songData.events.copy() : []; - // get first camera focus - for(e in events) { - if (e.time > 10) break; - if (e.name == "Camera Movement") { - executeEvent(e); - break; + var foundCam = false; + var foundSigs = songData.meta.beatsPerMeasure.getDefault(4) != 4 || songData.meta.stepsPerBeat.getDefault(4) != 4; + + if (events == null) events = []; + else events = [ + for (e in songData.events) { + switch (e.name) { + case "Camera Movement": if (!foundCam && e.time < 10) { + foundCam = true; + executeEvent(e); + } + case "Time Signature Change": if (!foundSigs && (e.params[0] != 4 || e.params[1] != 4)) { + foundSigs = true; + } + } + e; } + ]; + + if (!foundSigs) { + camZoomingInterval = 4; + camZoomingEvery = BEAT; } + events.sort(function(p1, p2) { return FlxSort.byValues(FlxSort.DESCENDING, p1.time, p2.time); }); - var beatsPerMeasure:Float = songData.meta.beatsPerMeasure.getDefault(Flags.DEFAULT_BEATS_PER_MEASURE); - var stepsPerBeat:Int = songData.meta.stepsPerBeat.getDefault(Flags.DEFAULT_STEPS_PER_BEAT); - - camZoomingInterval = Std.int(beatsPerMeasure); - - Conductor.changeBPM(songData.meta.bpm, beatsPerMeasure, stepsPerBeat); - curSong = songData.meta.name.toLowerCase(); curSongID = curSong.replace(" ", "-"); - inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty)); + FlxG.sound.setMusic(inst = FlxG.sound.load(Assets.getMusic(Paths.inst(SONG.meta.name, difficulty)))); if (SONG.meta.needsVoices != false && Assets.exists(Paths.voices(SONG.meta.name, difficulty))) // null or true - vocals = FlxG.sound.load(Paths.voices(SONG.meta.name, difficulty)); + vocals = FlxG.sound.load(Options.streamedVocals ? Assets.getMusic(Paths.voices(SONG.meta.name, difficulty)) : Paths.voices(SONG.meta.name, difficulty)); else vocals = new FlxSound(); - inst.group = FlxG.sound.defaultMusicGroup; - vocals.group = FlxG.sound.defaultMusicGroup; - inst.persist = vocals.persist = false; + vocals.group = FlxG.sound.defaultMusicGroup; + vocals.persist = false; generatedMusic = true; } @@ -1080,7 +1104,7 @@ class PlayState extends MusicBeatState @:dox(hide) private inline function generateStrums(amount:Null<Int> = null):Void { for(p in strumLines) { - var kc = amount != null ? amount : Flags.DEFAULT_STRUM_AMOUNT; + var kc = amount != null ? amount : p.data.keyCount; p.generateStrums(kc); } } @@ -1169,17 +1193,11 @@ class PlayState extends MusicBeatState @:dox(hide) function resyncVocals():Void { - vocals.pause(); - for (strumLine in strumLines.members) strumLine.vocals.pause(); + var time = Conductor.songPosition + Conductor.songOffset; + for (strumLine in strumLines.members) strumLine.vocals.play(true, time); + vocals.play(true, time); + inst.play(true, time); - FlxG.sound.music.play(); - Conductor.songPosition = FlxG.sound.music.time; - vocals.time = Conductor.songPosition + Conductor.songOffset; - for (strumLine in strumLines.members) { - strumLine.vocals.time = vocals.time; - strumLine.vocals.play(); - } - vocals.play(); gameAndCharsCall("onVocalsResync"); } @@ -1224,7 +1242,7 @@ class PlayState extends MusicBeatState if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, [])); } else { WindowUtils.preventClosing = false; WindowUtils.resetClosing(); - Sys.exit(0); + openfl.system.System.exit(0); } } }, @@ -1237,7 +1255,7 @@ class PlayState extends MusicBeatState if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, [])); } else { WindowUtils.preventClosing = false; WindowUtils.resetClosing(); - Sys.exit(0); + openfl.system.System.exit(0); } } }, @@ -1251,18 +1269,17 @@ class PlayState extends MusicBeatState ])); } - function updateIconPositions() { + dynamic function updateIconPositions() { var iconOffset = Flags.ICON_OFFSET; + var healthBarPercent = healthBar.percent; - var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0); + var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBarPercent, 0, 100, 1, 0); iconP1.x = center - iconOffset; iconP2.x = center - (iconP2.width - iconOffset); - health = FlxMath.bound(health, 0, maxHealth); - - iconP1.health = healthBar.percent / 100; - iconP2.health = 1 - (healthBar.percent / 100); + iconP1.health = healthBarPercent / 100; + iconP2.health = 1 - (healthBarPercent / 100); } // bypass the caching in FormatUtil by doing it manually so its faster @@ -1271,7 +1288,7 @@ class PlayState extends MusicBeatState private var TEXT_GAME_COMBOBREAKS = TU.getRaw("game.comboBreaks"); private var TEXT_GAME_ACCURACY = TU.getRaw("game.accuracy"); - function updateRatingStuff() { + dynamic function updateRatingStuff() { scoreTxt.text = TEXT_GAME_SCORE.format([songScore]); missesTxt.text = (comboBreaks ? TEXT_GAME_COMBOBREAKS : TEXT_GAME_MISSES).format([misses]); @@ -1301,78 +1318,60 @@ class PlayState extends MusicBeatState return; } - updateRatingStuff(); + if (updateRatingStuff != null) + updateRatingStuff(); - if (controls.PAUSE && startedCountdown && canPause) - pauseGame(); + if (canAccessDebugMenus && chartingMode && controls.DEV_ACCESS) + FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false)); - if (canAccessDebugMenus) { - if (chartingMode && FlxG.keys.justPressed.SEVEN) { - FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false)); + if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom) { + var beat = Conductor.getBeats(camZoomingEvery, camZoomingInterval, camZoomingOffset); + if (camZoomingLastBeat != beat) { + camZoomingLastBeat = beat; + FlxG.camera.zoom += 0.015 * camZoomingStrength; + camHUD.zoom += 0.03 * camZoomingStrength; } - /*if (FlxG.keys.justPressed.F5) { - Logs.warn('Reloading scripts...', "PlayState"); - scripts.reload(); - Logs.warn('Song scripts successfully reloaded.', GREEN, "PlayState"); - }*/ } - if (doIconBop) { - var iconLerp = Flags.ICON_LERP; - iconP1.scale.set(lerp(iconP1.scale.x, 1, iconLerp), lerp(iconP1.scale.y, 1, iconLerp)); - iconP2.scale.set(lerp(iconP2.scale.x, 1, iconLerp), lerp(iconP2.scale.y, 1, iconLerp)); + if (doIconBop) + for (icon in iconArray) + if (icon.updateBump != null) + icon.updateBump(); - iconP1.updateHitbox(); - iconP2.updateHitbox(); - } - updateIconPositions(); + if (updateIconPositions != null) + updateIconPositions(); - if (startingSong) - { - if (startedCountdown) - { + if (startingSong) { + if (startedCountdown) { Conductor.songPosition += Conductor.songOffset + elapsed * 1000; if (Conductor.songPosition >= 0) startSong(); } - } else if (FlxG.sound.music != null) { - var instTime = FlxG.sound.music.time; - var isOffsync = vocals.time != instTime || [for(strumLine in strumLines.members) strumLine.vocals.time != instTime].contains(true); - __vocalOffsetViolation = Math.max(0, __vocalOffsetViolation + (isOffsync ? elapsed : -elapsed / 2)); - if (__vocalOffsetViolation > Flags.VOCAL_OFFSET_VIOLATION_THRESHOLD) { - resyncVocals(); - __vocalOffsetViolation = 0; + } + else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) { + __vocalSyncTimer = 1; + + var instTime = FlxG.sound.music.getActualTime(); + var isOffsync:Bool = vocals.loaded && Math.abs(instTime - vocals.getActualTime()) > 30; + if (!isOffsync) { + for (strumLine in strumLines.members) { + if ((isOffsync = strumLine.vocals.loaded && Math.abs(instTime - strumLine.vocals.getActualTime()) > 30)) break; + } } + + if (isOffsync) resyncVocals(); } while(events.length > 0 && events.last().time <= Conductor.songPosition) executeEvent(events.pop()); - if (generatedMusic && strumLines.members[curCameraTarget] != null) - { - var pos = FlxPoint.get(); - var r = 0; - for(c in strumLines.members[curCameraTarget].characters) { - if (c == null || !c.visible) continue; - var cpos = c.getCameraPosition(); - pos.x += cpos.x; - pos.y += cpos.y; - r++; - //cpos.put(); - } - if (r > 0) { - pos.x /= r; - pos.y /= r; + if (controls.PAUSE && startedCountdown && canPause) + pauseGame(); - var event = gameAndCharsEvent("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r)); - if (!event.cancelled) - camFollow.setPosition(pos.x, pos.y); - } - pos.put(); - } + if (generatedMusic) + moveCamera(); - if (camZooming) - { + if (camZooming) { FlxG.camera.zoom = lerp(FlxG.camera.zoom, defaultCamZoom, camGameZoomLerp); camHUD.zoom = lerp(camHUD.zoom, defaultHudZoom, camHUDZoomLerp); } @@ -1406,7 +1405,52 @@ class PlayState extends MusicBeatState scripts.event("postDraw", e); } - public var scrollSpeedTween:FlxTween = null; + public function moveCamera() if (strumLines.members[curCameraTarget] != null) { + var data:CamPosData = getStrumlineCamPos(curCameraTarget); + if (data.amount > 0) { + var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(data.pos, strumLines.members[curCameraTarget], data.amount)); + if (!event.cancelled) + camFollow.setPosition(event.position.x, event.position.y); + } + data.put(); + } + + /** + * Returns the camera position of the specified strumline. + * @param strumLine The strumline to get the camera position of. + * @param pos The position to put the camera position in. If `null`, a new FlxPoint will be created. + * @param ignoreInvisible Whenever invisible characters should be ignored. + **/ + public inline function getStrumlineCamPos(strumLine:Int, ?pos:FlxPoint = null, ?ignoreInvisible:Bool = true):CamPosData { + return getCharactersCamPos(strumLines.members[strumLine].characters, pos, ignoreInvisible); + } + + /** + * Returns the camera position of the specified characters. + * @param chars The characters to get the camera position of. + * @param pos The position to put the camera position in. If `null`, a new FlxPoint will be created. + * @param ignoreInvisible Whenever invisible characters should be ignored. + **/ + public dynamic function getCharactersCamPos(chars:Array<Character>, ?pos:FlxPoint = null, ?ignoreInvisible:Bool = true):CamPosData { + if (pos == null) pos = FlxPoint.get(); + var amount = 0; + for(c in chars) { + if (c == null || (ignoreInvisible && !c.visible)) continue; + var cpos = c.getCameraPosition(); + pos.x += cpos.x; + pos.y += cpos.y; + amount++; + //cpos.put(); // not actually in the pool, so no need + } + if (amount > 0) { + pos.x /= amount; + pos.y /= amount; + } + return new CamPosData(pos, amount); + } + + + public var eventsTween:Map<String, FlxTween> = []; public function executeEvent(event:ChartEvent) @:privateAccess { if (event == null || event.params == null) return; @@ -1418,15 +1462,101 @@ class PlayState extends MusicBeatState switch(event.name) { case "HScript Call": - scripts.call(event.params[0], event.params[1].split(',')); + var scriptPacks:Array<ScriptPack> = [scripts, stateScripts]; + for (strLine in strumLines.members) for (char in strLine.characters) scriptPacks.push(char.scripts); + var args:Array<String> = event.params[1].split(','); + + for (pack in scriptPacks) { + pack.call(event.params[0], args); + //public functions + if (pack.publicVariables.exists(event.params[0])) { + var func = pack.publicVariables.get(event.params[0]); + if (func != null && Reflect.isFunction(func)) + Reflect.callMethod(null, func, args); + } + } + + //static functions + if (Script.staticVariables.exists(event.params[0])) { + var func = Script.staticVariables.get(event.params[0]); + if (func != null && Reflect.isFunction(func)) + Reflect.callMethod(null, func, args); + } case "Camera Movement": + var tween = eventsTween.get("cameraMovement"); + if (tween != null) { + if (tween.onComplete != null) tween.onComplete(tween); + tween.cancel(); + } + curCameraTarget = event.params[0]; + moveCamera(); + + if (strumLines.members[curCameraTarget] != null) { + if (event.params[1] == false) FlxG.camera.snapToTarget(); + else if (event.params[3] != null && event.params[3] != "CLASSIC") { // making more nullchecks in this event because of the default save value being false - Nex + var oldFollow = FlxG.camera.followEnabled; + FlxG.camera.followEnabled = false; + eventsTween.set("cameraMovement", FlxTween.tween(FlxG.camera.scroll, {x: camFollow.x - FlxG.camera.width * 0.5, y: camFollow.y - FlxG.camera.height * 0.5}, + (Conductor.stepCrochet / 1000) * (event.params[2] == null ? 4 : event.params[2]), { + ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4]), + onComplete: (_) -> FlxG.camera.followEnabled = oldFollow + }) + ); + } + } + case "Camera Position": + var tween = eventsTween.get("cameraMovement"); + if (tween != null) { + if (tween.onComplete != null) tween.onComplete(tween); + tween.cancel(); + } + + curCameraTarget = -1; + var isOffset = event.params[6] == true; + camFollow.setPosition(isOffset ? (camFollow.x + event.params[0]) : event.params[0], isOffset ? (camFollow.y + event.params[1]) : event.params[1]); + + if (event.params[2] == false) FlxG.camera.snapToTarget(); + else if (event.params[4] != null && event.params[4] != "CLASSIC") { + var oldFollow = FlxG.camera.followEnabled; + FlxG.camera.followEnabled = false; + eventsTween.set("cameraMovement", FlxTween.tween(FlxG.camera.scroll, {x: camFollow.x - FlxG.camera.width * 0.5, y: camFollow.y - FlxG.camera.height * 0.5}, + (Conductor.stepCrochet / 1000) * (event.params[3] == null ? 4 : event.params[3]), { + ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]), + onComplete: (_) -> FlxG.camera.followEnabled = oldFollow + }) + ); + } case "Add Camera Zoom": var camera:FlxCamera = event.params[1] == "camHUD" ? camHUD : camGame; camera.zoom += event.params[0]; + case "Camera Zoom": + var cam = event.params[2] == "camHUD" ? camHUD : camGame; + var name = (event.params[2] == "camHUD" ? "camHUD" : "camGame") + ".zoom"; // avoiding having different values from these 2 - Nex + var tween = eventsTween.get(name); + if (tween != null) tween.cancel(); + + var finalZoom:Float = event.params[1] * (event.params[6] == 'direct' ? FlxCamera.defaultZoom : stage.defaultZoom); + if (event.params[7] == true) finalZoom *= cam.zoom; + + if (event.params[0] == false) { + cam.zoom = finalZoom; + if (cam == camHUD) defaultHudZoom = finalZoom; + else defaultCamZoom = finalZoom; + } else + eventsTween.set(name, FlxTween.tween(cam, {zoom: finalZoom}, (Conductor.stepCrochet / 1000) * event.params[3], {ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]), onUpdate: function(_) { + if (cam == camHUD) defaultHudZoom = cam.zoom; + else defaultCamZoom = cam.zoom; + }})); case "Camera Modulo Change": camZoomingInterval = event.params[0]; camZoomingStrength = event.params[1]; + if (event.params[2] != null) camZoomingEvery = switch (event.params[2].toUpperCase()) { + case "STEP": STEP; + case "MEASURE": MEASURE; + default: BEAT; + } + if (event.params[3] != null) camZoomingOffset = event.params[3]; case "Camera Flash": var camera:FlxCamera = event.params[3] == "camHUD" ? camHUD : camGame; @@ -1436,12 +1566,16 @@ class PlayState extends MusicBeatState camera.flash(event.params[1], (Conductor.stepCrochet / 1000) * event.params[2], null, true); case "BPM Change": // automatically handled by conductor case "Scroll Speed Change": - if (scrollSpeedTween != null) scrollSpeedTween.cancel(); + var tween = eventsTween.get("scrollSpeedTween"); + if (tween != null) tween.cancel(); + + var finalScroll:Float = event.params[1]; + if (event.params[5] == true) finalScroll *= scrollSpeed; if (event.params[0] == false) - scrollSpeed = event.params[1]; + scrollSpeed = finalScroll; else - scrollSpeedTween = FlxTween.tween(this, {scrollSpeed: event.params[1]}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])}); + eventsTween.set("scrollSpeedTween", FlxTween.tween(this, {scrollSpeed: finalScroll}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])})); case "Alt Animation Toggle": var strLine = strumLines.members[event.params[2]]; if (strLine != null) { @@ -1456,7 +1590,7 @@ class PlayState extends MusicBeatState case "Play Animation": if (strumLines.members[event.params[0]] != null && strumLines.members[event.params[0]].characters != null) for (char in strumLines.members[event.params[0]].characters) - if (char != null) char.playAnim(event.params[1], event.params[2], null); + if (char != null) char.playAnim(event.params[1], event.params[2], event.params[3] == "NONE" ? null : event.params[3]); case "Unknown": // nothing } } @@ -1511,6 +1645,7 @@ class PlayState extends MusicBeatState */ public function endSong():Void { + endingSong = true; gameAndCharsCall("onSongEnd"); canPause = false; inst.volume = 0; @@ -1551,8 +1686,6 @@ class PlayState extends MusicBeatState * Immediately switches to the next song, or goes back to the Story/Freeplay menu. */ public function nextSong() { - resetSongInfos(); - if (isStoryMode) { campaignScore += songScore; @@ -1586,7 +1719,7 @@ class PlayState extends MusicBeatState FlxG.sound.music.stop(); - __loadSong(storyPlaylist[0].toLowerCase(), difficulty); + __loadSong(storyPlaylist[0], difficulty); FlxG.switchState(new PlayState()); } @@ -1716,9 +1849,9 @@ class PlayState extends MusicBeatState var event:NoteHitEvent; if (strumLine != null && !strumLine.cpu) - event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick"); + event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick", 0.5, true, 0.7, true, true, iconP1); else - event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false); + event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false, 0.5, true, 0.7, true, true, iconP2); event.deleteNote = !note.isSustainNote; // work around, to allow sustain notes to be deleted event = scripts.event(strumLine != null && !strumLine.cpu ? "onPlayerHit" : "onDadHit", event); strumLine.onHit.dispatch(event); @@ -1873,22 +2006,10 @@ class PlayState extends MusicBeatState { super.beatHit(curBeat); - if (camZoomingInterval < 1) camZoomingInterval = 1; - if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && curBeat % camZoomingInterval == 0) - { - FlxG.camera.zoom += Flags.CAM_BOP_STRENGTH * camZoomingStrength; - camHUD.zoom += Flags.HUD_BOP_STRENGTH * camZoomingStrength; - } - if (doIconBop) - { - var iconScale = Flags.BOP_ICON_SCALE; - iconP1.scale.set(iconScale, iconScale); - iconP2.scale.set(iconScale, iconScale); - - iconP1.updateHitbox(); - iconP2.updateHitbox(); - } + for (icon in iconArray) + if (icon.bump != null) + icon.bump(); scripts.call("beatHit", [curBeat]); } @@ -1979,7 +2100,7 @@ class PlayState extends MusicBeatState public static function loadWeek(weekData:WeekData, ?difficulty:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; storyWeek = weekData; - storyPlaylist = [for(e in weekData.songs) e.name]; + storyPlaylist = [for (e in weekData.songs) e.name]; isStoryMode = true; campaignScore = 0; campaignMisses = 0; @@ -2013,6 +2134,8 @@ class PlayState extends MusicBeatState */ public static function __loadSong(_name:String, _difficulty:String) { difficulty = _difficulty; + seenCutscene = false; + deathCounter = 0; SONG = Chart.parse(_name, _difficulty); fromMods = SONG.fromMods; @@ -2023,8 +2146,10 @@ final class ComboRating { public var percent:Float; public var rating:String; public var color:FlxColor; + public var maxMisses:Float; // Float since it could be Math.POSITIVE_INFINITY - Nex - public function new(percent:Float, rating:String, color:FlxColor) { + public function new(?percent:Float, ?rating:String, ?color:FlxColor, ?misses:Float) { + maxMisses = misses == null || Math.isNaN(misses) ? Math.POSITIVE_INFINITY : misses; this.percent = percent; this.rating = TranslationUtil.get('game.rating.$rating', rating); this.color = color; @@ -2039,3 +2164,28 @@ typedef PlayStateTransitionData = { var camFollowY:Float; var camZoom:Float; } + +class CamPosData { + /** + * The camera position. + **/ + public var pos:FlxPoint; + /** + * The amount of characters that was involved in the calculation. + **/ + public var amount:Int; + + public function new(pos:FlxPoint, amount:Int) { + this.pos = pos; + this.amount = amount; + } + + /** + * Puts the position back into the pool, making it reusable. + **/ + public function put() { + if(pos == null) return; + pos.put(); + pos = null; + } +} \ No newline at end of file diff --git a/source/funkin/game/Splash.hx b/source/funkin/game/Splash.hx new file mode 100644 index 00000000..6352e31f --- /dev/null +++ b/source/funkin/game/Splash.hx @@ -0,0 +1,39 @@ +package funkin.game; + +class Splash extends FunkinSprite +{ + /** + * The current splash strum + * WARNING: It can be null + */ + public var strum:Null<Strum>; + + /** + * Shortcut to `strum.ID` + * WARNING: It can be null + */ + public var strumID:Null<Int>; + + public static function copyFrom(source:Splash):Splash + { + var spr = new Splash(); + @:privateAccess { + spr.setPosition(source.x, source.y); + spr.frames = source.frames; + if (source.animateAtlas != null && source.atlasPath != null) + spr.loadSprite(source.atlasPath); + spr.animation.copyFrom(source.animation); + spr.visible = source.visible; + spr.alpha = source.alpha; + spr.antialiasing = source.antialiasing; + spr.scale.set(source.scale.x, source.scale.y); + spr.scrollFactor.set(source.scrollFactor.x, source.scrollFactor.y); + spr.skew.set(source.skew.x, source.skew.y); + spr.transformMatrix = source.transformMatrix; + spr.matrixExposed = source.matrixExposed; + spr.zoomFactor = source.zoomFactor; + spr.animOffsets = source.animOffsets.copy(); + } + return spr; + } +} diff --git a/source/funkin/game/SplashGroup.hx b/source/funkin/game/SplashGroup.hx index 9b9be000..7600f479 100644 --- a/source/funkin/game/SplashGroup.hx +++ b/source/funkin/game/SplashGroup.hx @@ -2,7 +2,7 @@ package funkin.game; import haxe.xml.Access; -class SplashGroup extends FlxTypedGroup<FunkinSprite> { +class SplashGroup extends FlxTypedGroup<Splash> { /** * Whenever the splash group has successfully loaded or not. */ @@ -37,6 +37,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { // immediately draw once and put image in GPU to prevent freezes // TODO: change to graphics cache + @:privateAccess splash.drawComplex(FlxG.camera); } catch(e:Dynamic) { Logs.error('Couldn\'t parse splash data for "${path}": ${e.toString()}'); @@ -45,18 +46,21 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { maxSize = Flags.MAX_SPLASHES; } + var _scale:Float = 1.0; + var _alpha:Float = 1.0; + var _antialiasing:Bool = true; + function createSplash(imagePath:String) { - var splash = new FunkinSprite(); - splash.antialiasing = true; + var splash = new Splash(); splash.active = splash.visible = false; splash.loadSprite(Paths.image(imagePath)); - if (xml.has.scale) splash.scale.scale(Std.parseFloat(xml.att.scale).getDefaultFloat(1)); - if (xml.has.alpha) splash.alpha = Std.parseFloat(xml.att.alpha).getDefaultFloat(1); - if (xml.has.antialiasing) splash.antialiasing = xml.att.antialiasing == "true"; + _scale = xml.has.scale ? Std.parseFloat(xml.att.scale).getDefault(1) : 1; + _alpha = xml.has.alpha ? Std.parseFloat(xml.att.alpha).getDefault(1) : 1; + _antialiasing = xml.has.antialiasing ? xml.att.antialiasing == "true" : true; return splash; } - function setupAnims(xml:Access, splash:FunkinSprite) { + function setupAnims(xml:Access, splash:Splash) { for(strum in xml.nodes.strum) { var id:Null<Int> = Std.parseInt(strum.att.id); if (id != null) { @@ -82,15 +86,19 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { } splash.animation.finishCallback = function(name:String) { splash.active = splash.visible = false; + splash.strum = null; + splash.strumID = null; }; } - function pregenerateSplashes(splash:FunkinSprite) { + function pregenerateSplashes(splash:Splash) { // make 7 additional splashes for(i in 0...7) { - var spr = FunkinSprite.copyFrom(splash); + var spr = Splash.copyFrom(splash); spr.animation.finishCallback = function(name:String) { spr.active = spr.visible = false; + spr.strum = null; + spr.strumID = null; }; add(spr); } @@ -99,17 +107,26 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { public function getSplashAnim(id:Int):String { if (animationNames.length <= 0) return null; id %= animationNames.length; - if (animationNames[id] == null || animationNames[id].length <= 0) return null; - return animationNames[id][FlxG.random.int(0, animationNames[id].length - 1)]; + var animNames = animationNames[id]; + if (animNames == null || animNames.length <= 0) return null; + return animNames[FlxG.random.int(0, animNames.length - 1)]; } - var __splash:FunkinSprite; + var __splash:Splash; public function showOnStrum(strum:Strum) { if (!valid) return null; __splash = recycle(); + __splash.strum = strum; + __splash.strumID = strum.ID; + + @:privateAccess + __splash.scale.x = __splash.scale.y = _scale * strum.strumLine.strumScale; + __splash.alpha = _alpha; + __splash.antialiasing = _antialiasing; + __splash.cameras = strum.lastDrawCameras; - __splash.setPosition(strum.x + ((strum.width - __splash.width) / 2), strum.y + ((strum.height - __splash.height) / 2)); + __splash.setPosition(strum.x + 0.5 * (strum.width - __splash.width), strum.y + 0.5 * (strum.height - __splash.height)); __splash.active = __splash.visible = true; __splash.playAnim(getSplashAnim(strum.ID), true); __splash.scrollFactor.set(strum.scrollFactor.x, strum.scrollFactor.y); diff --git a/source/funkin/game/SplashHandler.hx b/source/funkin/game/SplashHandler.hx index ad14ad8c..d17981a7 100644 --- a/source/funkin/game/SplashHandler.hx +++ b/source/funkin/game/SplashHandler.hx @@ -2,7 +2,7 @@ package funkin.game; import funkin.backend.scripting.events.splash.*; -class SplashHandler extends FlxTypedGroup<FunkinSprite> { +class SplashHandler extends FlxTypedGroup<Splash> { /** * Map containing all of the splashes group. */ diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index 5bbc5b47..4b5cc8c8 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -1,5 +1,6 @@ package funkin.game; +import flixel.util.typeLimit.OneOfTwo; import flixel.FlxState; import flixel.math.FlxPoint; import haxe.xml.Access; @@ -18,21 +19,30 @@ using StringTools; * A class that handles loading a stage and putting the sprites into the state. **/ class Stage extends FlxBasic implements IBeatReceiver { - public var stageName:String = ""; + public var extra:Map<String, Dynamic> = []; + public var stageXML:Access; public var stagePath:String; + public var stageFile:String; + public var stageName:String; public var stageSprites:Map<String, FlxSprite> = []; public var stageScript:Script; public var state:FlxState; public var characterPoses:Map<String, StageCharPos> = []; public var xmlImportedScripts:Array<XMLImportedScriptInfo> = []; - private var spritesParentFolder = ""; + public var defaultZoom:Float = 1.05; + public var startCam = new FlxPoint(); - /** - * Gets a sprite from the stage. - **/ - public function getSprite(name:String) + public var onXMLLoaded:(Access, Array<Access>)->Array<Access> = null; + public var onNodeLoaded:(Access, Dynamic)->Dynamic = null; + public var onNodeFinished:(Access, Dynamic)->Void = null; + public var onAddSprite:(FlxObject)->Void = null; + public var onXMLPostLoaded:(Access, Array<Access>)->Array<Access> = null; + + public var spritesParentFolder = ""; + + public inline function getSprite(name:String) return stageSprites[name]; /** @@ -42,50 +52,71 @@ class Stage extends FlxBasic implements IBeatReceiver { for (k=>e in stageSprites) script.set(k, e); public function prepareInfos(node:Access) - return XMLImportedScriptInfo.prepareInfos(node, PlayState.instance.scripts, (infos) -> xmlImportedScripts.push(infos)); + return PlayState.instance == null ? null : XMLImportedScriptInfo.prepareInfos(node, PlayState.instance.scripts, (infos) -> xmlImportedScripts.push(infos)); - public function new(stage:String, ?state:FlxState) { + public function new(stage:String, ?state:FlxState, autoLoad:Bool = true) { super(); if (state == null) state = PlayState.instance; if (state == null) state = FlxG.state; this.state = state; - stagePath = Paths.xml('stages/$stage'); + stageFile = stage; + stagePath = Paths.xml('stages/$stageFile'); try if (Assets.exists(stagePath)) stageXML = new Access(Xml.parse(Assets.getText(stagePath)).firstElement()) - catch(e) Logs.error('Couldn\'t load stage "$stage": ${e.message}'); + catch(e) Logs.trace('Couldn\'t load stage "$stageFile": ${e.message}', ERROR); - if (PlayState.instance != null) { - stageScript = Script.create(Paths.script('data/stages/$stage')); + if (autoLoad) loadXml(stageXML); + } + + public static var DEFAULT_ATTRIBUTES:Array<String> = ["name", "startCamPosX", "startCamPosY", "zoom", "folder"]; + + public function loadXml(xml:Access, forceLoadAll:Bool = false) { + if (PlayState.instance == state) { + stageScript = Script.create(Paths.script('data/stages/$stageFile')); PlayState.instance.scripts.add(stageScript); stageScript.load(); } var event = null; - if (stageXML != null) { - stageName = stageXML.getAtt("name").getDefault(stage); + var elems:Array<Access> = []; + if (xml != null) { + var parsed:Null<Float>; + if((parsed = Std.parseFloat(xml.getAtt("startCamPosX"))).isNotNull()) startCam.x = parsed; + if((parsed = Std.parseFloat(xml.getAtt("startCamPosY"))).isNotNull()) startCam.y = parsed; + if((parsed = Std.parseFloat(xml.getAtt("zoom"))).isNotNull()) defaultZoom = parsed; - if (PlayState.instance != null) { - var parsed:Null<Float>; - if(stageXML.has.startCamPosX && (parsed = Std.parseFloat(stageXML.att.startCamPosX)) != null) PlayState.instance.camFollow.x = parsed; - if(stageXML.has.startCamPosY && (parsed = Std.parseFloat(stageXML.att.startCamPosY)) != null) PlayState.instance.camFollow.y = parsed; - if(stageXML.has.zoom && (parsed = Std.parseFloat(stageXML.att.zoom)) != null) PlayState.instance.defaultCamZoom = parsed; + stageName = xml.getAtt("name").getDefault(stageFile); + + if (PlayState.instance == state) { + if(xml.has.startCamPosX) PlayState.instance.camFollow.x = startCam.x; + if(xml.has.startCamPosY) PlayState.instance.camFollow.y = startCam.y; + if(xml.has.zoom) PlayState.instance.defaultCamZoom = defaultZoom; } - if (stageXML.has.folder) { - spritesParentFolder = stageXML.att.folder; + if (xml.has.folder) { + spritesParentFolder = xml.att.folder; if (!spritesParentFolder.endsWith("/")) spritesParentFolder += "/"; } - var elems = []; - for(node in stageXML.elements) { - if (node.name == "high-memory" && !Options.lowMemoryMode) for(e in node.elements) __pushNcheckNode(elems, e); + // Load custom attributes + for(att in xml.x.attributes()) + if(!DEFAULT_ATTRIBUTES.contains(att)) + extra.set(att, xml.x.get(att)); + + // some way to tag that the sprites are from the group + for(node in xml.elements) { + if (node.name == "high-memory" && (!Options.lowMemoryMode || forceLoadAll)) for(e in node.elements) __pushNcheckNode(elems, e); + else if (node.name == "low-memory" && (Options.lowMemoryMode || forceLoadAll)) for(e in node.elements) __pushNcheckNode(elems, e); else __pushNcheckNode(elems, node); } - if (PlayState.instance != null) { + if (PlayState.instance == state) { event = EventManager.get(StageXMLEvent).recycle(this, stageXML, elems); elems = PlayState.instance.gameAndCharsEvent("onStageXMLParsed", event).elems; } + if(onXMLLoaded != null) { + elems = onXMLLoaded(xml, elems); + } for(node in elems) { var sprite:Dynamic = switch(node.name) { @@ -94,55 +125,37 @@ class Stage extends FlxBasic implements IBeatReceiver { var spr = XMLUtil.createSpriteFromXML(node, spritesParentFolder, LOOP); - if (!node.has.zoomfactor && PlayState.instance != null) - spr.initialZoom = PlayState.instance.defaultCamZoom; - stageSprites.set(spr.name, spr); - state.add(spr); - spr; + addSprite(spr); case "box" | "solid": if (!node.has.name || !node.has.width || !node.has.height) continue; - var spr = new FlxSprite( - (node.has.x) ? Std.parseFloat(node.att.x).getDefaultFloat(0) : 0, - (node.has.y) ? Std.parseFloat(node.att.y).getDefaultFloat(0) : 0 - ); + var isSolid = node.name == "solid"; - (node.name == "solid" ? spr.makeSolid : spr.makeGraphic)( + var spr = new FunkinSprite(); + (isSolid ? spr.makeSolid : spr.makeGraphic)( Std.parseInt(node.att.width), Std.parseInt(node.att.height), (node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1 ); - stageSprites.set(node.getAtt("name"), spr); - state.add(spr); - spr; + if (isSolid) node.x.remove("updateHitbox"); + for (a in ["width", "height", "color"]) node.x.remove(a); + XMLUtil.loadSpriteFromXML(spr, node, "", NONE, false); + + stageSprites.set(spr.name, spr); + addSprite(spr); case "boyfriend" | "bf" | "player": - addCharPos("boyfriend", node, { - x: 770, - y: 100, - scroll: 1, - flip: true - }); + addCharPos("boyfriend", node, getDefaultPos("boyfriend")); case "girlfriend" | "gf": - addCharPos("girlfriend", node, { - x: 400, - y: 130, - scroll: 0.95, - flip: false - }); + addCharPos("girlfriend", node, getDefaultPos("girlfriend")); case "dad" | "opponent": - addCharPos("dad", node, { - x: 100, - y: 100, - scroll: 1, - flip: false - }); + addCharPos("dad", node, getDefaultPos("dad")); case "character" | "char": if (!node.has.name) continue; addCharPos(node.att.name, node); case "ratings" | "combo": - if (PlayState.instance == null) continue; + if (PlayState.instance != state) continue; PlayState.instance.comboGroup.setPosition( Std.parseFloat(node.getAtt("x")).getDefaultFloat(PlayState.instance.comboGroup.x), Std.parseFloat(node.getAtt("y")).getDefaultFloat(PlayState.instance.comboGroup.y) @@ -155,61 +168,85 @@ class Stage extends FlxBasic implements IBeatReceiver { default: null; } - if(PlayState.instance != null) { + if(PlayState.instance == state) { sprite = PlayState.instance.gameAndCharsEvent("onStageNodeParsed", EventManager.get(StageNodeEvent).recycle(this, node, sprite, node.name)).sprite; } + if(onNodeLoaded != null) { + sprite = onNodeLoaded(node, sprite); + } if (sprite != null) { for(e in node.nodes.property) XMLUtil.applyXMLProperty(sprite, e); } + + if(onNodeFinished != null) { + onNodeFinished(node, sprite); + } } } if (characterPoses["girlfriend"] == null) - addCharPos("girlfriend", null, { - x: 400, - y: 130, - scroll: 0.95, - flip: false - }); + addCharPos("girlfriend", null, getDefaultPos("girlfriend")); if (characterPoses["dad"] == null) - addCharPos("dad", null, { - x: 100, - y: 100, - scroll: 1, - flip: false - }); + addCharPos("dad", null, getDefaultPos("dad")); if (characterPoses["boyfriend"] == null) - addCharPos("boyfriend", null, { + addCharPos("boyfriend", null, getDefaultPos("boyfriend")); + + if (PlayState.instance == state) { + setStagesSprites(stageScript); + + // i know this for gets run twice under, but its better like this in case a script modifies the short lived ones, i dont wanna save them in an array; more dynamic like this - Nex + for (info in xmlImportedScripts) if (info.importStageSprites) { + var script = info.getScript(); + if (script != null) setStagesSprites(script); + } + + // idk lemme check anyways just in case scripts did smth - Nex + if (event != null) PlayState.instance.scripts.event("onPostStageCreation", event); + + // shortlived scripts destroy when the stage finishes setting up - Nex + for (info in xmlImportedScripts) if (info.shortLived) { + var script = info.getScript(); + if (script == null) continue; + + PlayState.instance.scripts.remove(script); + script.destroy(); + } + } + if(onXMLPostLoaded != null) { + elems = onXMLPostLoaded(xml, elems); + } + } + + public static function getDefaultPos(name:String):StageCharPosInfo { + return switch(name) { + case "boyfriend" | "bf" | "player": { x: 770, y: 100, scroll: 1, flip: true - }); - - if (PlayState.instance == null) return; - - setStagesSprites(stageScript); - - // i know this for gets run twice under, but its better like this in case a script modifies the short lived ones, i don't wanna save them in an array; more dynamic like this - Nex - for (info in xmlImportedScripts) if (info.importStageSprites) { - var script = info.getScript(); - if (script != null) setStagesSprites(script); - } - - // idk lemme check anyways just in case scripts did smth - Nex - if (event != null) PlayState.instance.gameAndCharsEvent("onPostStageCreation", event); - - // shortlived scripts destroy when the stage finishes setting up - Nex - for (info in xmlImportedScripts) if (info.shortLived) { - var script = info.getScript(); - if (script == null) continue; - - PlayState.instance.scripts.remove(script); - script.destroy(); + }; + case "girlfriend" | "gf": { + x: 400, + y: 130, + scroll: 0.95, + flip: false + }; + case "dad" | "opponent": { + x: 100, + y: 100, + scroll: 1, + flip: false + }; + default: { + x: 0, + y: 0, + scroll: 1, + flip: false + }; } } @@ -228,6 +265,7 @@ class Stage extends FlxBasic implements IBeatReceiver { public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo):StageCharPos { var charPos = new StageCharPos(); charPos.visible = charPos.active = false; + charPos.name = name; if (nonXMLInfo != null) { charPos.setPosition(nonXMLInfo.x, nonXMLInfo.y); @@ -236,35 +274,53 @@ class Stage extends FlxBasic implements IBeatReceiver { } if (node != null) { - charPos.x = Std.parseFloat(node.getAtt("x")).getDefaultFloat(charPos.x); - charPos.y = Std.parseFloat(node.getAtt("y")).getDefaultFloat(charPos.y); - charPos.camxoffset = Std.parseFloat(node.getAtt("camxoffset")).getDefaultFloat(charPos.camxoffset); - charPos.camyoffset = Std.parseFloat(node.getAtt("camyoffset")).getDefaultFloat(charPos.camyoffset); - charPos.skewX = Std.parseFloat(node.getAtt("skewx")).getDefaultFloat(charPos.skewX); - charPos.skewY = Std.parseFloat(node.getAtt("skewy")).getDefaultFloat(charPos.skewY); - charPos.alpha = Std.parseFloat(node.getAtt("alpha")).getDefaultFloat(charPos.alpha); + charPos.x = Std.parseFloat(node.getAtt("x")).getDefault(charPos.x); + charPos.y = Std.parseFloat(node.getAtt("y")).getDefault(charPos.y); + charPos.charSpacingX = Std.parseFloat(node.getAtt("spacingx")).getDefault(charPos.charSpacingX); + charPos.charSpacingY = Std.parseFloat(node.getAtt("spacingy")).getDefault(charPos.charSpacingY); + charPos.camxoffset = Std.parseFloat(node.getAtt("camxoffset")).getDefault(charPos.camxoffset); + charPos.camyoffset = Std.parseFloat(node.getAtt("camyoffset")).getDefault(charPos.camyoffset); + charPos.skewX = Std.parseFloat(node.getAtt("skewx")).getDefault(charPos.skewX); + charPos.skewY = Std.parseFloat(node.getAtt("skewy")).getDefault(charPos.skewY); + charPos.alpha = Std.parseFloat(node.getAtt("alpha")).getDefault(charPos.alpha); + charPos.angle = Std.parseFloat(node.getAtt("angle")).getDefault(charPos.angle); charPos.flipX = (node.has.flip || node.has.flipX) ? (node.getAtt("flip") == "true" || node.getAtt("flipX") == "true") : charPos.flipX; + charPos.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(charPos.zoomFactor); - var scale = Std.parseFloat(node.getAtt("scale")).getDefaultFloat(charPos.scale.x); - charPos.scale.set(scale, scale); + if (node.has.scale) { + var scale:Null<Float> = Std.parseFloat(node.att.scale); + if (scale.isNotNull()) charPos.scale.set(scale, scale); + } + if (node.has.scalex) { + var scale:Null<Float> = Std.parseFloat(node.att.scalex); + if (scale.isNotNull()) charPos.scale.x = scale; + } + if (node.has.scaley) { + var scale:Null<Float> = Std.parseFloat(node.att.scaley); + if (scale.isNotNull()) charPos.scale.y = scale; + } if (node.has.scroll) { var scroll:Null<Float> = Std.parseFloat(node.att.scroll); if (scroll != null) charPos.scrollFactor.set(scroll, scroll); - } else { - if (node.has.scrollx) { - var scroll:Null<Float> = Std.parseFloat(node.att.scrollx); - if (scroll != null) charPos.scrollFactor.x = scroll; - } - if (node.has.scrolly) { - var scroll:Null<Float> = Std.parseFloat(node.att.scrolly); - if (scroll != null) charPos.scrollFactor.y = scroll; - } + } + if (node.has.scrollx) { + var scroll:Null<Float> = Std.parseFloat(node.att.scrollx); + if (scroll != null) charPos.scrollFactor.x = scroll; + } + if (node.has.scrolly) { + var scroll:Null<Float> = Std.parseFloat(node.att.scrolly); + if (scroll != null) charPos.scrollFactor.y = scroll; } } - state.add(charPos); - return characterPoses[name] = charPos; + return addSprite(characterPoses[name] = charPos); + } + + function addSprite<T:FlxObject>(sprite:T):T { + state.add(sprite); + if(onAddSprite != null) onAddSprite(sprite); + return sprite; } /** @@ -298,23 +354,36 @@ class Stage extends FlxBasic implements IBeatReceiver { public function measureHit(curMeasure:Int) {} + public override function destroy() { + startCam.put(); + super.destroy(); + } + /** * Gets a list of stages that are available to be used. * @param mods Whenever only the mods folder should be checked **/ - public static function getList(?mods:Bool = false):Array<String> { + public static function getList(?mods:Bool = false, ?xmlOnly:Bool = false):Array<String> { var list:Array<String> = []; - for (path in Paths.getFolderContent('data/stages/', true, mods ? MODS : BOTH)) - if (Path.extension(path) == "xml" || Path.extension(path) == "hx") { - var file:String = Path.withoutDirectory(Path.withoutExtension(path)); - list.pushOnce(file); + var extensions:Array<String> = ["xml"]; + if (!xmlOnly) extensions.push("hx"); + + for (path in Paths.getFolderContent("data/stages/", false, mods ? MODS : BOTH)) { + var extension = Path.extension(path); + if (extensions.contains(extension)) { + list.pushOnce("test"); + list.pushOnce(Path.withoutExtension(path)); } + } return list; } } class StageCharPos extends FlxObject { + public var extra:Map<String, Dynamic> = []; + + public var name:String; public var charSpacingX:Float = 20; public var charSpacingY:Float = 0; public var camxoffset:Float = 0; @@ -324,6 +393,7 @@ class StageCharPos extends FlxObject { public var alpha:Float = 1; public var flipX:Bool = false; public var scale:FlxPoint = FlxPoint.get(1, 1); + public var zoomFactor:Float = 1; public function new() { super(); @@ -336,13 +406,52 @@ class StageCharPos extends FlxObject { super.destroy(); } + private var _id:Float = -1; + + private var oldInfo:OldCharInfo = null; + public function prepareCharacter(char:Character, id:Float = 0) { + _id = id; + oldInfo = getOldInfo(char); char.setPosition(x + (id * charSpacingX), y + (id * charSpacingY)); char.scrollFactor.set(scrollFactor.x, scrollFactor.y); - char.scale.x *= scale.x; char.scale.y *= scale.y; + if (!char.debugMode) + char.scale.x *= scale.x; char.scale.y *= scale.y; char.cameraOffset += FlxPoint.weak(camxoffset, camyoffset); char.skew.x += skewX; char.skew.y += skewY; char.alpha *= alpha; + char.angle += angle; + char.zoomFactor *= zoomFactor; + } + + public function getOldInfo(char:Character) { + return { + x: char.x, y: char.y, + scrollX: char.scrollFactor.x, scrollY: char.scrollFactor.y, + scaleX: char.scale.x, scaleY: char.scale.y, + camxoffset: char.cameraOffset.x, camyoffset: char.cameraOffset.y, + skewX: char.skew.x, skewY: char.skew.y, + alpha: char.alpha, zoomFactor: char.zoomFactor, + angle: char.angle + } + } + + public function revertCharacter(char:Character) { + if(oldInfo == null) return; + for(field in Reflect.fields(oldInfo)) { + switch(field) { + case "scrollX": char.scrollFactor.x = oldInfo.scrollX; + case "scrollY": char.scrollFactor.y = oldInfo.scrollY; + case "scaleX": char.scale.x = oldInfo.scaleX; + case "scaleY": char.scale.y = oldInfo.scaleY; + case "camxoffset": char.cameraOffset.x = oldInfo.camxoffset; + case "camyoffset": char.cameraOffset.y = oldInfo.camyoffset; + case "skewX": char.skew.x = oldInfo.skewX; + case "skewY": char.skew.y = oldInfo.skewY; + default: Reflect.setProperty(char, field, Reflect.field(oldInfo, field)); + } + } + oldInfo = null; } } typedef StageCharPosInfo = { @@ -350,4 +459,20 @@ typedef StageCharPosInfo = { var y:Float; var flip:Bool; var scroll:Float; -} \ No newline at end of file +} + +typedef OldCharInfo = { + var x:Float; + var y:Float; + var scrollX:Float; + var scrollY:Float; + var scaleX:Float; + var scaleY:Float; + var camxoffset:Float; + var camyoffset:Float; + var skewX:Float; + var skewY:Float; + var alpha:Float; + var zoomFactor:Float; + var angle:Float; +} diff --git a/source/funkin/game/Strum.hx b/source/funkin/game/Strum.hx index f743591b..d5031945 100644 --- a/source/funkin/game/Strum.hx +++ b/source/funkin/game/Strum.hx @@ -27,7 +27,7 @@ class Strum extends FlxSprite { /** * The strum line that this strum belongs to. **/ - public var strumLine:StrumLine; + public var strumLine:StrumLine = null; /** * The scroll speed of the notes. @@ -42,6 +42,18 @@ class Strum extends FlxSprite { public var lastDrawCameras(default, null):Array<FlxCamera> = []; + // Copy fields + public var copyStrumCamera:Bool = true; + public var copyStrumScrollX:Bool = true; + public var copyStrumScrollY:Bool = true; + public var copyStrumAngle:Bool = true; + public var updateNotesPosX:Bool = true; + public var updateNotesPosY:Bool = true; + public var extraCopyFields(default, set):Array<String> = []; + + private inline function set_extraCopyFields(val:Array<String>) + return extraCopyFields = val == null ? [] : val; + /** * Whenever the strum is pressed. **/ @@ -56,7 +68,7 @@ class Strum extends FlxSprite { public var getJustReleased:StrumLine->Bool = null; @:dox(hide) public inline function __getPressed(strumLine:StrumLine):Bool { - return getPressed != null ? getPressed(strumLine) : switch(ID) { + return getPressed != null ? getPressed(strumLine) : strumLine.members.length != 4 ? ControlsUtil.getPressed(strumLine.controls, strumLine.members.length+"k"+ID) : switch(ID) { case 0: strumLine.controls.NOTE_LEFT; case 1: strumLine.controls.NOTE_DOWN; case 2: strumLine.controls.NOTE_UP; @@ -65,7 +77,7 @@ class Strum extends FlxSprite { } } @:dox(hide) public inline function __getJustPressed(strumLine:StrumLine) { - return getJustPressed != null ? getJustPressed(strumLine) : switch(ID) { + return getJustPressed != null ? getJustPressed(strumLine) : strumLine.members.length != 4 ? ControlsUtil.getJustPressed(strumLine.controls, strumLine.members.length+"k"+ID) : switch(ID) { case 0: strumLine.controls.NOTE_LEFT_P; case 1: strumLine.controls.NOTE_DOWN_P; case 2: strumLine.controls.NOTE_UP_P; @@ -74,7 +86,7 @@ class Strum extends FlxSprite { } } @:dox(hide) public inline function __getJustReleased(strumLine:StrumLine) { - return getJustReleased != null ? getJustReleased(strumLine) : switch(ID) { + return getJustReleased != null ? getJustReleased(strumLine) : strumLine.members.length != 4 ? ControlsUtil.getJustReleased(strumLine.controls, strumLine.members.length+"k"+ID) : switch(ID) { case 0: strumLine.controls.NOTE_LEFT_R; case 1: strumLine.controls.NOTE_DOWN_R; case 2: strumLine.controls.NOTE_UP_R; @@ -120,7 +132,7 @@ class Strum extends FlxSprite { super.draw(); } - @:noCompletion public static final PIX180:Float = 565.4866776461628; // 180 * Math.PI + @:noCompletion public static inline final PIX180:Float = 565.4866776461628; // 180 * Math.PI @:noCompletion public static final N_WIDTHDIV2:Float = Note.swagWidth / 2; /** @@ -130,47 +142,61 @@ class Strum extends FlxSprite { public function updateNotePosition(daNote:Note) { if (!daNote.exists) return; - daNote.__strumCameras = lastDrawCameras; daNote.__strum = this; - daNote.scrollFactor.set(scrollFactor.x, scrollFactor.y); - daNote.__noteAngle = getNotesAngle(daNote); - daNote.angle = daNote.isSustainNote ? daNote.__noteAngle : angle; + if (copyStrumCamera) daNote.__strumCameras = lastDrawCameras; + if (copyStrumScrollX) daNote.scrollFactor.x = scrollFactor.x; + if (copyStrumScrollY) daNote.scrollFactor.y = scrollFactor.y; + if (copyStrumAngle && daNote.copyStrumAngle) { + daNote.__noteAngle = getNotesAngle(daNote); + daNote.angle = daNote.isSustainNote ? daNote.__noteAngle : angle; + } updateNotePos(daNote); + for (field in extraCopyFields) + CoolUtil.cloneProperty(daNote, field, this); // TODO: make this cached to reduce the reflection calls - Neo } private inline function updateNotePos(daNote:Note) { - if (daNote.strumRelativePos) { - daNote.setPosition((this.width - daNote.width) / 2, (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100))); - if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2; - } else { - var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100))); - var realOffset = FlxPoint.get(0, 0); + var shouldX = updateNotesPosX && daNote.updateNotesPosX; + var shouldY = updateNotesPosY && daNote.updateNotesPosY; - if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2; - - if (Std.int(daNote.__noteAngle % 360) != 0) { - var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180); - var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180); - - var aOffset:FlxPoint = FlxPoint.get( - (daNote.origin.x / daNote.scale.x) - daNote.offset.x, - (daNote.origin.y / daNote.scale.y) - daNote.offset.y - ); - realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y)); - realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y)); - - aOffset.put(); + if (shouldX || shouldY) { + if (daNote.strumRelativePos) { + if (shouldX) daNote.x = (this.width - daNote.width) / 2; + if (shouldY) { + daNote.y = (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)); + if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2; + } } else { - realOffset.x = offset.x; - realOffset.y = offset.y; + var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100))); + var realOffset = FlxPoint.get(0, 0); + + if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2; + + if (Std.int(daNote.__noteAngle % 360) != 0) { + var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180); + var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180); + + var aOffset:FlxPoint = FlxPoint.get( + (daNote.origin.x / daNote.scale.x) - daNote.offset.x, + (daNote.origin.y / daNote.scale.y) - daNote.offset.y + ); + realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y)); + realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y)); + + aOffset.put(); + } else { + realOffset.x = offset.x; + realOffset.y = offset.y; + } + realOffset.y *= -1; + + if (shouldX) daNote.x = x + realOffset.x; + if (shouldY) daNote.y = y + realOffset.y; + + offset.put(); + realOffset.put(); } - realOffset.y *= -1; - - daNote.setPosition(x + realOffset.x, y + realOffset.y); - - offset.put(); - realOffset.put(); } } diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx index f46eff2e..ae5303b8 100644 --- a/source/funkin/game/StrumLine.hx +++ b/source/funkin/game/StrumLine.hx @@ -114,7 +114,9 @@ class StrumLine extends FlxTypedGroup<Strum> { this.opponentSide = opponentSide; this.controls = controls; this.notes = new NoteGroup(); - vocals = vocalPrefix != "" ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, vocalPrefix)) : new FlxSound(); + + var v = Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, vocalPrefix); + vocals = vocalPrefix != "" ? FlxG.sound.load(Options.streamedVocals ? Assets.getMusic(v) : v) : new FlxSound(); vocals.persist = false; } @@ -221,7 +223,7 @@ class StrumLine extends FlxTypedGroup<Strum> { if (cpu && __updateNote_event.__autoCPUHit && !daNote.avoid && !daNote.wasGoodHit && daNote.strumTime < __updateNote_songPos) PlayState.instance.goodNoteHit(this, daNote); - if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.sustainLength) < __updateNote_songPos) { + if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + daNote.sustainLength < __updateNote_songPos) { deleteNote(daNote); return; } @@ -247,8 +249,9 @@ class StrumLine extends FlxTypedGroup<Strum> { var __notePerStrum:Array<Note> = []; function __inputProcessPressed(note:Note) { - if (__pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) { + if (__pressed[note.strumID] && note.isSustainNote && note.strumTime < __updateNote_songPos && !note.wasGoodHit) { PlayState.instance.goodNoteHit(this, note); + note.updateSustainClip(); } } function __inputProcessJustPressed(note:Note) { @@ -347,11 +350,13 @@ class StrumLine extends FlxTypedGroup<Strum> { * Creates a strum and returns the created strum (needs to be added manually). * @param i Index of the strum * @param animPrefix (Optional) Animation prefix (`left` = `arrowLEFT`, `left press`, `left confirm`). + * @param spritePath (Optional) The sprite's graphic path if you don't want the default one. + * @param playIntroAnimation (Optional) Whenever the intro animation should be played, by default might be `true` under certain conditions. */ - public function createStrum(i:Int, ?animPrefix:String) { + public function createStrum(i:Int, ?animPrefix:String, ?spritePath:String, ?playIntroAnimation:Bool) { if (animPrefix == null) animPrefix = strumAnimPrefix[i % strumAnimPrefix.length]; - var babyArrow:Strum = new Strum(startingPos.x + ((Note.swagWidth * strumScale) * i), startingPos.y); + var babyArrow:Strum = new Strum(startingPos.x + (Note.swagWidth * strumScale * (data.strumSpacing != null ? data.strumSpacing : 1) * i), startingPos.y + (Note.swagWidth*0.5) - (Note.swagWidth * strumScale * 0.5)); babyArrow.ID = i; babyArrow.strumLine = this; @@ -359,7 +364,8 @@ class StrumLine extends FlxTypedGroup<Strum> { babyArrow.scrollSpeed = data.scrollSpeed; var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix); - event.__doAnimation = !MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true); + event.__doAnimation = playIntroAnimation == null ? (!MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true)) : playIntroAnimation; + if (spritePath != null) event.sprite = spritePath; if (PlayState.instance != null) event = PlayState.instance.gameAndCharsEvent("onStrumCreation", event); if (!event.cancelled) { @@ -385,7 +391,7 @@ class StrumLine extends FlxTypedGroup<Strum> { { babyArrow.y -= 10; babyArrow.alpha = 0; - FlxTween.tween(babyArrow, {y: babyArrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * i)}); + FlxTween.tween(babyArrow, {y: babyArrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * i * (4 / (data.keyCount != null ? data.keyCount : 4)))}); } babyArrow.playAnim('static'); @@ -411,6 +417,10 @@ class StrumLine extends FlxTypedGroup<Strum> { } } + public static inline function calculateStartingXPos(hudXRatio:Float, scale:Float, spacing:Float, keyCount:Int) { + return (FlxG.width * hudXRatio) - ((Note.swagWidth * scale * ((keyCount/2)-0.5) * spacing) + Note.swagWidth * 0.5 * scale); + } + /** * SETTERS & GETTERS */ diff --git a/source/funkin/game/cutscenes/Cutscene.hx b/source/funkin/game/cutscenes/Cutscene.hx index 9845e916..51e6ed1f 100644 --- a/source/funkin/game/cutscenes/Cutscene.hx +++ b/source/funkin/game/cutscenes/Cutscene.hx @@ -8,11 +8,11 @@ import funkin.menus.PauseSubState; */ class Cutscene extends MusicBeatSubstate { var __callback:Void->Void; - var __pausable:Bool; var game:PlayState = PlayState.instance; public var paused:Bool = false; public var pauseItems:Array<String>; + public var pausable:Bool; public var skippable(default, set):Bool; public function set_skippable(val:Bool):Bool { @@ -26,9 +26,9 @@ class Cutscene extends MusicBeatSubstate { canOpenCustomTransition = true; __callback = callback; - __pausable = allowPause; pauseItems = Flags.DEFAULT_CUTSCENE_PAUSE_ITEMS; + pausable = allowPause; skippable = canSkip; } @@ -38,7 +38,7 @@ class Cutscene extends MusicBeatSubstate { } public function pauseCheck():Bool - return controls.PAUSE && __pausable; + return controls.PAUSE && pausable; var _before:Array<Bool> = [false, true]; public function pauseCutscene() { diff --git a/source/funkin/game/cutscenes/DialogueCutscene.hx b/source/funkin/game/cutscenes/DialogueCutscene.hx index 61d8ddff..f3c3ee13 100644 --- a/source/funkin/game/cutscenes/DialogueCutscene.hx +++ b/source/funkin/game/cutscenes/DialogueCutscene.hx @@ -93,8 +93,8 @@ class DialogueCutscene extends ScriptedCutscene { bubble: node.getAtt('bubble').getDefault('normal'), callback: node.getAtt('callback'), changeDefAnim: node.getAtt('changeDefAnim'), - speed: Std.parseFloat(node.getAtt("speed")).getDefaultFloat(0.05), - musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.speed).getDefaultFloat(0.8)) : null, + speed: Std.parseFloat(node.getAtt("speed")).getDefault(0.05), + musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.musicVolume).getDefault(0.8)) : null, changeMusic: node.has.changeMusic ? FlxG.sound.load(Paths.music(node.att.changeMusic), volume, true) : null, playSound: node.has.playSound ? FlxG.sound.load(Paths.sound(node.att.playSound)) : null, nextSound: node.has.nextSound ? FlxG.sound.load(Paths.sound(node.att.nextSound)) : null, @@ -202,7 +202,7 @@ class DialogueCutscene extends ScriptedCutscene { if (curMusic != null && !curMusic.persist) curMusic.destroy(); super.destroy(); - cutscene = null; + if (cutscene == this) cutscene = null; FlxG.cameras.remove(dialogueCamera); } } diff --git a/source/funkin/game/cutscenes/VideoCutscene.hx b/source/funkin/game/cutscenes/VideoCutscene.hx index 9e5a50bd..f3e3e274 100644 --- a/source/funkin/game/cutscenes/VideoCutscene.hx +++ b/source/funkin/game/cutscenes/VideoCutscene.hx @@ -186,7 +186,7 @@ class VideoCutscene extends Cutscene { text.destroy(); }}); - __pausable = __loaded = true; + pausable = __loaded = true; video.play(); } diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 20059ad2..92619a7a 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -24,6 +24,7 @@ import flixel.tweens.FlxEase; import flixel.util.FlxDestroyUtil; import funkin.backend.system.Flags; +import funkin.Types; import funkin.menus.ui.Alphabet; import flixel.group.FlxGroup.FlxTypedGroup; diff --git a/source/funkin/menus/BetaWarningState.hx b/source/funkin/menus/BetaWarningState.hx deleted file mode 100644 index 9d6688e9..00000000 --- a/source/funkin/menus/BetaWarningState.hx +++ /dev/null @@ -1,57 +0,0 @@ -package funkin.menus; - -import flixel.text.FlxText; -import flixel.util.FlxColor; -import funkin.backend.FunkinText; - -class BetaWarningState extends MusicBeatState { - var titleAlphabet:Alphabet; - var disclaimer:FunkinText; - - var transitioning:Bool = false; - - public override function create() { - super.create(); - - titleAlphabet = new Alphabet(0, 0, TU.translate("betaWarning.title"), "bold"); - titleAlphabet.screenCenter(X); - add(titleAlphabet); - - disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 10, FlxG.width - 32, "", 32); - disclaimer.alignment = CENTER; - disclaimer.applyMarkup(TU.translate("betaWarning.desc", [Flags.RELEASE_CYCLE]) + "\n\n" + TU.translate("betaWarning.proceed", ["ENTER"]), - [ - new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*") - ] - ); - add(disclaimer); - - var off = Std.int((FlxG.height - (disclaimer.y + disclaimer.height)) / 2); - disclaimer.y += off; - titleAlphabet.y += off; - - DiscordUtil.call("onMenuLoaded", ["Beta Warning"]); - } - - public override function update(elapsed:Float) { - super.update(elapsed); - - if (controls.ACCEPT && transitioning) { - FlxG.camera.stopFX(); FlxG.camera.visible = false; - goToTitle(); - } - - if (controls.ACCEPT && !transitioning) { - transitioning = true; - CoolUtil.playMenuSFX(CONFIRM); - FlxG.camera.flash(FlxColor.WHITE, 1, function() { - FlxG.camera.fade(FlxColor.BLACK, 2.5, false, goToTitle); - }); - } - } - - private function goToTitle() { - MusicBeatState.skipTransIn = MusicBeatState.skipTransOut = true; - FlxG.switchState(new TitleState()); - } -} \ No newline at end of file diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index e4a50d1f..736f1d6b 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -139,6 +139,8 @@ class FreeplayState extends MusicBeatState var icon:HealthIcon = new HealthIcon(songs[i].icon); icon.sprTracker = songText; + icon.setUnstretchedGraphicSize(150, 150, true); + icon.updateHitbox(); // using a FlxGroup is too much fuss! iconArray.push(icon); @@ -229,14 +231,14 @@ class FreeplayState extends MusicBeatState } scoreText.text = TEXT_FREEPLAY_SCORE.format([Math.round(lerpScore)]); - scoreBG.scale.set(Math.max(Math.max(diffText.width, scoreText.width), coopText.width) + 8, (coopText.visible ? coopText.y + coopText.height : 66)); + scoreBG.scale.set(MathUtil.maxSmart(diffText.width, scoreText.width, coopText.width) + 8, (coopText.visible ? coopText.y + coopText.height : 66)); scoreBG.updateHitbox(); scoreBG.x = FlxG.width - scoreBG.width; scoreText.x = coopText.x = scoreBG.x + 4; diffText.x = Std.int(scoreBG.x + ((scoreBG.width - diffText.width) / 2)); - interpColor.fpsLerpTo(songs[curSelected].parsedColor, 0.0625); + interpColor.fpsLerpTo(songs[curSelected].color, 0.0625); bg.color = interpColor.color; #if PRELOAD_ALL @@ -287,12 +289,13 @@ class FreeplayState extends MusicBeatState function updateCoopModes() { __opponentMode = false; __coopMode = false; - if (songs[curSelected].coopAllowed && songs[curSelected].opponentModeAllowed) { + var curSong = songs[curSelected]; + if (curSong.coopAllowed && curSong.opponentModeAllowed) { __opponentMode = curCoopMode % 2 == 1; __coopMode = curCoopMode >= 2; - } else if (songs[curSelected].coopAllowed) { + } else if (curSong.coopAllowed) { __coopMode = curCoopMode == 1; - } else if (songs[curSelected].opponentModeAllowed) { + } else if (curSong.opponentModeAllowed) { __opponentMode = curCoopMode == 1; } } @@ -323,7 +326,7 @@ class FreeplayState extends MusicBeatState public function convertChart() { trace('Converting ${songs[curSelected].name} (${songs[curSelected].difficulties[curDifficulty]}) to Codename format...'); var chart = Chart.parse(songs[curSelected].name, songs[curSelected].difficulties[curDifficulty]); - Chart.save('${Main.pathBack}assets/songs/${songs[curSelected].name}', chart, songs[curSelected].difficulties[curDifficulty].toLowerCase()); + Chart.save('${Main.pathBack}assets/songs/${songs[curSelected].name}', chart, songs[curSelected].difficulties[curDifficulty]); } /** @@ -346,9 +349,9 @@ class FreeplayState extends MusicBeatState updateScore(); if (curSong.difficulties.length > 1) - diffText.text = '< ${curSong.difficulties[curDifficulty]} >'; + diffText.text = '< ${curSong.difficulties[curDifficulty].toUpperCase()} >'; else - diffText.text = validDifficulties ? curSong.difficulties[curDifficulty] : "-"; + diffText.text = validDifficulties ? curSong.difficulties[curDifficulty].toUpperCase() : "-"; } function updateScore() { @@ -458,16 +461,10 @@ class FreeplaySonglist { public function getSongsFromSource(source:funkin.backend.assets.AssetSource, useTxt:Bool = true) { var path:String = Paths.txt('freeplaySonglist'); - var songsFound:Array<String> = []; - if (useTxt && Paths.assetsTree.existsSpecific(path, "TEXT", source)) { - songsFound = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); - } else { - songsFound = Paths.getFolderDirectories('songs', false, source); - } + var songsFound:Array<String> = useTxt && Paths.assetsTree.existsSpecific(path, "TEXT", source) ? CoolUtil.coolTextFile(path) : Paths.getFolderDirectories('songs', false, source); if (songsFound.length > 0) { - for(s in songsFound) - songs.push(Chart.loadChartMeta(s, Flags.DEFAULT_DIFFICULTY, source == MODS)); + for (s in songsFound) songs.push(Chart.loadChartMeta(s, Flags.DEFAULT_DIFFICULTY, source == MODS)); return false; } return true; @@ -490,4 +487,4 @@ class FreeplaySonglist { return songList; } -} \ No newline at end of file +} diff --git a/source/funkin/menus/GitarooPause.hx b/source/funkin/menus/GitarooPause.hx index cff8aec9..f72e7d2b 100644 --- a/source/funkin/menus/GitarooPause.hx +++ b/source/funkin/menus/GitarooPause.hx @@ -64,9 +64,7 @@ class GitarooPause extends MusicBeatState } else { - PlayState.resetSongInfos(); if (Charter.instance != null) Charter.instance.__clearStatics(); - FlxG.switchState(new MainMenuState()); } } diff --git a/source/funkin/menus/MainMenuState.hx b/source/funkin/menus/MainMenuState.hx index 5d7b1713..1a68f4a2 100644 --- a/source/funkin/menus/MainMenuState.hx +++ b/source/funkin/menus/MainMenuState.hx @@ -29,6 +29,7 @@ class MainMenuState extends MusicBeatState override function create() { + super.create(); DiscordUtil.call("onMenuLoaded", ["Main Menu"]); @@ -97,7 +98,7 @@ class MainMenuState extends MusicBeatState if (!selectedSomethin) { if (canAccessDebugMenus) { - if (FlxG.keys.justPressed.SEVEN) { + if (controls.DEV_ACCESS) { persistentUpdate = false; persistentDraw = true; openSubState(new funkin.editors.EditorPicker()); diff --git a/source/funkin/menus/ModSwitchMenu.hx b/source/funkin/menus/ModSwitchMenu.hx index db9ac798..977f74fc 100644 --- a/source/funkin/menus/ModSwitchMenu.hx +++ b/source/funkin/menus/ModSwitchMenu.hx @@ -12,9 +12,15 @@ class ModSwitchMenu extends MusicBeatSubstate { var alphabets:FlxTypedGroup<Alphabet>; var curSelected:Int = 0; + var subCam:FlxCamera; + public override function create() { super.create(); + camera = subCam = new FlxCamera(); + subCam.bgColor = 0; + FlxG.cameras.add(subCam, false); + var bg = new FlxSprite(0, 0).makeSolid(FlxG.width, FlxG.height, 0xFF000000); bg.updateHitbox(); bg.scrollFactor.set(); @@ -66,5 +72,12 @@ class ModSwitchMenu extends MusicBeatSubstate { } alphabets.members[curSelected].alpha = 1; } + + override function destroy() { + super.destroy(); + + if (FlxG.cameras.list.contains(subCam)) + FlxG.cameras.remove(subCam); + } } #end diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx index 1d2db997..f652b36c 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -66,7 +66,7 @@ class PauseSubState extends MusicBeatSubstate menuItems = event.options; - pauseMusic = FlxG.sound.load(Paths.music(event.music), 0, true); + pauseMusic = FlxG.sound.load(Assets.getMusic(Paths.music(event.music)), 0, true); pauseMusic.persist = false; pauseMusic.group = FlxG.sound.defaultMusicGroup; pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); @@ -174,7 +174,6 @@ class PauseSubState extends MusicBeatSubstate if (PlayState.chartingMode && Charter.undos.unsaved) game.saveWarn(false); else { - PlayState.resetSongInfos(); if (Charter.instance != null) Charter.instance.__clearStatics(); // prevents certain notes to disappear early when exiting - Nex diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx index e38fdb9f..1a4c9de3 100644 --- a/source/funkin/menus/StoryMenuState.hx +++ b/source/funkin/menus/StoryMenuState.hx @@ -5,17 +5,20 @@ import flixel.text.FlxText; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxTimer; +import flixel.util.typeLimit.OneOfTwo; import funkin.backend.FunkinText; +import funkin.backend.scripting.events.CancellableEvent; import funkin.backend.scripting.events.menu.MenuChangeEvent; import funkin.backend.scripting.events.menu.storymenu.*; -import funkin.backend.scripting.events.CancellableEvent; +import funkin.backend.week.*; import funkin.savedata.FunkinSave; import haxe.io.Path; import haxe.xml.Access; class StoryMenuState extends MusicBeatState { - public var characters:Map<String, Access> = []; + public var characters:Map<String, WeekData.WeekCharacter> = []; public var weeks:Array<WeekData> = []; + public var weekList:StoryWeeklist; // yes it supports parameters - Nex // To be removed later, when translation branch is merged into public - Neo @@ -34,7 +37,6 @@ class StoryMenuState extends MusicBeatState { public var blackBar:FlxSprite; public var weekBG:FlxSprite; - public var defColor:String = "#F9CF51"; public var interpColor:FlxInterpolateColor; public var lerpScore:Float = 0; @@ -65,7 +67,7 @@ class StoryMenuState extends MusicBeatState { weekTitle.alpha = 0.7; weekBG = new FlxSprite(0, 56).makeSolid(FlxG.width, 400, 0xFFFFFFFF); - weekBG.color = weeks.length > 0 ? weeks[0].bgColor : FlxColor.fromString(defColor); + weekBG.color = weeks.length > 0 ? weeks[0].bgColor : Flags.DEFAULT_WEEK_COLOR; weekBG.updateHitbox(); weekSprites = new FlxTypedGroup<MenuItem>(); @@ -93,19 +95,18 @@ class StoryMenuState extends MusicBeatState { tracklist.color = 0xFFE55777; add(weekSprites); - for(e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) { - e.antialiasing = true; + for (e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) { e.scrollFactor.set(); add(e); } add(characterSprites = new FlxTypedGroup<FunkinSprite>()); - for(i=>week in weeks) { + for (i=>week in weeks) { var spr:MenuItem = new MenuItem(0, (i * 120) + 480, 'menus/storymenu/weeks/${week.sprite}'); weekSprites.add(spr); - for(e in week.difficulties) { + for (e in week.difficulties) { var le = e.toLowerCase(); if (difficultySprites[le] == null) { var diffSprite = new FlxSprite(leftArrow.x + leftArrow.width, leftArrow.y); @@ -164,6 +165,12 @@ class StoryMenuState extends MusicBeatState { weekBG.color = interpColor.color; } + public override function beatHit(curBeat:Int) { + super.beatHit(curBeat); + if (characterSprites != null) // reason why I wanted to use a MusicBeatGroup, but eh, whatever - Nex + characterSprites.forEachAlive(function(spr) spr.beatHit(curBeat)); + } + public function goBack() { var event = event("onGoBack", new CancellableEvent()); if (!event.cancelled) @@ -182,24 +189,16 @@ class StoryMenuState extends MusicBeatState { e.targetY = k - curWeek; e.alpha = k == curWeek ? 1.0 : 0.6; } - var str = new StringBuf(); - str.add(TU.translate("story.tracks") + "\n\n"); - var len = weeks[curWeek].songs.length; - for(i=>e in weeks[curWeek].songs) { - if (!e.hide) { - str.add(e.name.toUpperCase()); - if (i != len-1) - str.add("\n"); - } - } - - tracklist.text = str.toString(); + tracklist.text = '${TU.translate("story.tracks")}\n\n${[for(e in weeks[curWeek].songs) if (!e.hide) e.displayName.getDefault(e.name).toUpperCase()].join('\n')}'; weekTitle.text = weeks[curWeek].name.getDefault(""); - if(characterSprites != null) for(i in 0...3) { - var curChar:FunkinSprite; var newChar:Access = characters[weeks[curWeek].chars[i]]; - if(newChar == null) modifyCharacterAt(i); - else if((curChar = characterSprites.members[i]) == null || newChar.getAtt("name") != curChar.name) modifyCharacterAt(i, newChar); + if (characterSprites != null) for (i in 0...3) { + var char = weeks[curWeek].chars[i]; + var curChar:FunkinSprite = null; + var newChar = null; + + if (char == null || (newChar = characters[char.name]) == null) modifyCharacterAt(i); + else if ((curChar = cast characterSprites.members[i]) == null || newChar.name != curChar.name) modifyCharacterAt(i, newChar); // forcing the sprites to be FunkinSprite basically - Nex } changeDifficulty(0, true); @@ -207,27 +206,6 @@ class StoryMenuState extends MusicBeatState { MemoryUtil.clearMinor(); } - public function modifyCharacterAt(i:Int, ?node:Access):FunkinSprite { - var old = characterSprites.members[i]; - if(old != null) { - characterSprites.remove(old); - old.destroy(); - } - - if(node == null) return null; - var curChar:FunkinSprite = XMLUtil.createSpriteFromXML(node, "", BEAT); - curChar.offset.x += curChar.x; curChar.offset.y += curChar.y; - curChar.setPosition((FlxG.width * 0.25) * (1 + i) - 150, 70); - if(characterSprites != null) characterSprites.insert(i, curChar); // Making so many null checks abt this group just in case if mods destroy it - Nex - curChar.playAnim("idle", true, DANCE); - return curChar; - } - - public override function beatHit(curBeat:Int) { - super.beatHit(curBeat); - if(characterSprites != null) characterSprites.forEachAlive(function(spr) spr.beatHit(curBeat)); - } - var __oldDiffName = null; public function changeDifficulty(change:Int, force:Bool = false) { if (change == 0 && !force) return; @@ -256,122 +234,44 @@ class StoryMenuState extends MusicBeatState { } public function loadXMLs() { - // CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); - var weeks:Array<String> = []; - - switch(Flags.WEEKS_LIST_MOD_MODE) { - case 'prepend': - getWeeksFromSource(weeks, MODS); - getWeeksFromSource(weeks, SOURCE); - case 'append': - getWeeksFromSource(weeks, SOURCE); - getWeeksFromSource(weeks, MODS); - default /*case 'override'*/: - if (getWeeksFromSource(weeks, MODS)) - getWeeksFromSource(weeks, SOURCE); - } - - for(k=>weekName in weeks) { - var week:Access = null; - try { - week = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/weeks/$weekName'))).firstElement()); - } catch(e) { - Logs.error('Cannot parse week "$weekName.xml": ${Std.string(e)}'); - } - - if (week == null) continue; - - if (!week.has.name) { - Logs.warn('Story Menu: Week at index ${k} has no name. Skipping...'); - continue; - } - var weekObj:WeekData = { - name: week.att.name, - id: weekName, - sprite: week.getAtt('sprite').getDefault(weekName), - chars: [null, null, null], - songs: [], - difficulties: ['easy', 'normal', 'hard'], - bgColor: FlxColor.fromString(week.getAtt("bgColor").getDefault(defColor)) - }; - - var diffNodes = week.nodes.difficulty; - if (diffNodes.length > 0) { - var diffs:Array<String> = []; - for(e in diffNodes) { - if (e.has.name) diffs.push(e.att.name); - } - if (diffs.length > 0) - weekObj.difficulties = diffs; - } - - if (week.has.chars) { - for(k=>e in week.att.chars.split(",")) { - if (e.trim() == "" || e == "none" || e == "null") - weekObj.chars[k] = null; - else { - addCharacter(weekObj.chars[k] = e.trim()); - } - } - } - for(k2=>song in week.nodes.song) { - if (song == null) continue; - try { - var name = song.innerData.trim(); - if (name == "") { - Logs.warn('Story Menu: Song at index ${k2} in week ${weekObj.name} has no name. Skipping...'); - continue; - } - weekObj.songs.push({ - name: name, - hide: song.getAtt('hide').getDefault('false') == "true" - }); - } catch(e) { - Logs.warn('Story Menu: Song at index ${k2} in week ${weekObj.name} cannot contain any other XML nodes in its name.'); - continue; - } - } - if (weekObj.songs.length <= 0) { - Logs.warn('Story Menu: Week ${weekObj.name} has no songs. Skipping...'); - continue; - } - this.weeks.push(weekObj); - } + weekList = StoryWeeklist.get(true, false); // will only load week files AND NOT characters too (we will load them later only if needed)!! - Nex + weeks = weekList.weeks; + for (week in weeks) for (char in week.chars) if (char != null) + addCharacter(char.name); } - public function addCharacter(charName:String) { - var char:Access = null; - try { - char = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/characters/$charName'))).firstElement()); - } catch(e) { - Logs.error('Story Menu: Cannot parse character "$charName.xml": ${Std.string(e)}'); - } + public function addCharacter(char:OneOfTwo<String, WeekData.WeekCharacter>) { + // better to use unsafe casts for going fast - Nex + var charObj:WeekData.WeekCharacter = null; + var charName:String; - if(char != null && characters[charName] == null) { - if(!char.x.exists("name")) char.x.set("name", charName); - if(!char.x.exists("sprite")) char.x.set("sprite", 'menus/storymenu/characters/${charName}'); - if(!char.x.exists("beatInterval")) char.x.set("beatInterval", "1"); - if(!char.x.exists("updateHitbox")) char.x.set("updateHitbox", "true"); - characters[charName] = char; - } + charName = char is String ? cast char : (charObj = cast char).name; + if (characters[charName] != null) return; // will load only if it can be saved inside the map - Nex + characters[charName] = charObj == null ? Week.loadWeekCharacter(charName) : charObj; } - public function getWeeksFromSource(weeks:Array<String>, source:funkin.backend.assets.AssetSource) { - var path:String = Paths.txt('freeplaySonglist'); - var weeksFound:Array<String> = []; - if (Paths.assetsTree.existsSpecific(path, "TEXT", source)) { - var trim = ""; - weeksFound = CoolUtil.coolTextFile(Paths.txt('weeks/weeks')); - } else { - weeksFound = [for(c in Paths.getFolderContent('data/weeks/weeks/', false, source)) if (Path.extension(c).toLowerCase() == "xml") Path.withoutExtension(c)]; - } + public function modifyCharacterAt(i:Int, ?data:WeekData.WeekCharacter):FunkinSprite { + var curChar:FunkinSprite = null; - if (weeksFound.length > 0) { - for(s in weeksFound) - weeks.push(s); - return false; + if (characterSprites != null) { + var old = characterSprites.members[i]; + if (old != null) { + characterSprites.remove(old); + old.destroy(); + } + + if (data != null) { + curChar = XMLUtil.createSpriteFromXML(data.xml, "", BEAT); + curChar.offset.x += curChar.x; curChar.offset.y += curChar.y; + curChar.setPosition((FlxG.width * 0.25) * (1 + i) - 150, 70); + curChar.playAnim("idle", true, DANCE); + characterSprites.insert(i, curChar); + } else { + //characterSprites.members[i] = null; // cant add nulls to flxgroups, so - Nex + characterSprites.insert(i, new FunkinSprite()).visible = false; + } } - return true; + return curChar; } public function selectWeek() { @@ -381,7 +281,7 @@ class StoryMenuState extends MusicBeatState { canSelect = false; CoolUtil.playMenuSFX(CONFIRM); - if(characterSprites != null) + if (characterSprites != null) characterSprites.forEachAlive(function(spr) spr.playAnim("confirm", true, LOCK)); PlayState.loadWeek(event.week, event.difficulty); @@ -394,21 +294,6 @@ class StoryMenuState extends MusicBeatState { } } -typedef WeekData = { - var name:String; // name SHOULD NOT be used for loading week highscores, its just the name on the right side of the week, remember that next time!! - Nex - var id:String; // id IS instead for saving and loading!! - Nex - var sprite:String; - var chars:Array<String>; - var songs:Array<WeekSong>; - var difficulties:Array<String>; - var bgColor:FlxColor; -} - -typedef WeekSong = { - var name:String; - var hide:Bool; -} - class MenuItem extends FlxSprite { public var targetY:Float = 0; @@ -450,3 +335,42 @@ class MenuItem extends FlxSprite color = (time % 0.1 > 0.05) ? FlxColor.WHITE : 0xFF33ffff; } } + +class StoryWeeklist { + public var weeks:Array<WeekData> = []; + + public function new() {} + + public function getWeeksFromSource(source:funkin.backend.assets.AssetSource, useTxt:Bool = true, loadCharactersData:Bool = true) { + var path:String = Paths.txt('weeks/weeks'); + var weeksFound:Array<String> = useTxt && Paths.assetsTree.existsSpecific(path, "TEXT", source) ? CoolUtil.coolTextFile(path) : + [for (c in Paths.getFolderContent('data/weeks/weeks/', false, source)) if (Path.extension(c).toLowerCase() == "xml") Path.withoutExtension(c)]; + + if (weeksFound.length > 0) { + for (w in weeksFound) { + var week = Week.loadWeek(w, loadCharactersData); + if (week != null) weeks.push(week); + } + return false; + } + return true; + } + + public static function get(useTxt:Bool = true, loadCharactersData:Bool = true) { + var weekList = new StoryWeeklist(); + + switch(Flags.WEEKS_LIST_MOD_MODE) { + case 'prepend': + weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData); + weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData); + case 'append': + weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData); + weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData); + default /*case 'override'*/: + if (weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData)) + weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData); + } + + return weekList; + } +} \ No newline at end of file diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx index 3f059fc4..76485cbf 100644 --- a/source/funkin/menus/credits/CreditsMain.hx +++ b/source/funkin/menus/credits/CreditsMain.hx @@ -1,12 +1,12 @@ package funkin.menus.credits; +import flixel.util.FlxColor; import funkin.backend.assets.AssetSource; +import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor; import funkin.options.OptionsScreen; import funkin.options.TreeMenu; -import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor; import funkin.options.type.*; import haxe.xml.Access; -import flixel.util.FlxColor; class CreditsMain extends TreeMenu { var bg:FlxSprite; @@ -22,19 +22,14 @@ class CreditsMain extends TreeMenu { bg.antialiasing = true; add(bg); - var xmlPath = Paths.xml('config/credits'); - for(source in [funkin.backend.assets.AssetSource.SOURCE, funkin.backend.assets.AssetSource.MODS]) { - if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { - var access:Access = null; - try { - access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source))); - } catch(e) { - Logs.error('Error while parsing credits.xml: ${Std.string(e)}', "CreditsMain"); - } + for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { + var xmlPath = Paths.xml('config/credits/LIB_$i'); - if (access != null) - for(c in parseCreditsFromXML(access, source)) - items.push(c); + if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) { + var access:Access = null; + try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT"))) + catch(e) Logs.trace('[CreditsMain] Error while parsing credits.xml: ${Std.string(e)}', ERROR); + if (access != null) for (c in parseCreditsFromXML(access)) items.push(c); } } @@ -54,7 +49,7 @@ class CreditsMain extends TreeMenu { /** * XML STUFF */ - public function parseCreditsFromXML(xml:Access, source:AssetSource):Array<OptionType> { + public function parseCreditsFromXML(xml:Access, source:AssetSource = BOTH):Array<OptionType> { var credsMenus:Array<OptionType> = []; for(node in xml.elements) { diff --git a/source/funkin/menus/ui/ClassicAlphabet.hx b/source/funkin/menus/ui/ClassicAlphabet.hx index 4ece8633..f9a29ecf 100644 --- a/source/funkin/menus/ui/ClassicAlphabet.hx +++ b/source/funkin/menus/ui/ClassicAlphabet.hx @@ -3,7 +3,6 @@ package funkin.menus.ui; import flixel.group.FlxSpriteGroup; import flixel.util.FlxColor; import flixel.util.FlxTimer; -import funkin.backend.assets.AssetsLibraryList; import funkin.backend.assets.IModsAssetLibrary; import funkin.backend.assets.LimeLibrarySymbol; import haxe.xml.Access; @@ -53,7 +52,9 @@ class ClassicAlphabet extends FlxSpriteGroup public function refreshAlphabetXML(path:String) { AlphaCharacter.__alphaPath = Paths.getAssetsRoot() + path; try { - var xml = new Access(Xml.parse(Assets.getText(path)).firstElement()); + var file = Assets.getText(path); + if(file == null) return; + var xml = new Access(Xml.parse(file).firstElement()); AlphaCharacter.boldAnims = []; AlphaCharacter.letterAnims = []; AlphaCharacter.boldAlphabetPath = AlphaCharacter.letterAlphabetPath = 'ui/alphabet'; @@ -92,7 +93,12 @@ class ClassicAlphabet extends FlxSpriteGroup #if MOD_SUPPORT else { var libThing = new LimeLibrarySymbol(alphabetPath); if (libThing.library is AssetLibrary) { - var library = AssetsLibraryList.getCleanLibrary(libThing.library); + var library = cast(libThing.library, AssetLibrary); + @:privateAccess + if (library.__proxy != null && library.__proxy is AssetLibrary) { + @:privateAccess + library = cast(library.__proxy, AssetLibrary); + } if (library is IModsAssetLibrary) { var modLib = cast(library, IModsAssetLibrary); @:privateAccess @@ -158,7 +164,7 @@ class ClassicAlphabet extends FlxSpriteGroup splitWords = _finalText.split(""); } - //public var personTalking:String = 'gf'; + //public var personTalking:String = Flags.DEFAULT_GIRLFRIEND; public function startTypedText():Void { @@ -209,10 +215,6 @@ class ClassicAlphabet extends FlxSpriteGroup addText(); } } - - override function draw() { - super.draw(); - } } class AlphaCharacter extends FlxSprite @@ -281,4 +283,4 @@ class AlphaCharacter extends FlxSprite y = (110 - height); y += row * 60; } -} +} \ No newline at end of file diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx index 02717fe2..200e6207 100644 --- a/source/funkin/options/Options.hx +++ b/source/funkin/options/Options.hx @@ -33,6 +33,7 @@ class Options public static var gameplayShaders:Bool = true; public static var colorHealthBar:Bool = true; public static var lowMemoryMode:Bool = false; + public static var devMode:Bool = false; public static var betaUpdates:Bool = false; public static var splashesEnabled:Bool = true; public static var hitWindow:Float = 250; @@ -40,6 +41,11 @@ class Options public static var framerate:Int = 120; public static var gpuOnlyBitmaps:Bool = #if (mac || web) false #else true #end; // causes issues on mac and web public static var language = "en"; // default to english, Flags.DEFAULT_LANGUAGE should not modify this + public static var streamedMusic:Bool = true; + public static var streamedVocals:Bool = true; + #if MODCHARTING_FEATURES + public static var modchartingHoldSubdivisions:Int = 4; + #end public static var lastLoadedMod:String = null; @@ -49,6 +55,7 @@ class Options public static var intensiveBlur:Bool = true; public static var editorSFX:Bool = true; public static var editorPrettyPrint:Bool = false; + public static var editorsResizable:Bool = true; public static var maxUndos:Int = 120; /** @@ -67,12 +74,23 @@ class Options public static var charterShowSections:Bool = true; public static var charterShowBeats:Bool = true; public static var charterEnablePlaytestScripts:Bool = true; + public static var charterRainbowWaveforms:Bool = false; public static var charterLowDetailWaveforms:Bool = false; public static var charterAutoSaves:Bool = true; public static var charterAutoSaveTime:Float = 60*5; public static var charterAutoSaveWarningTime:Float = 5; public static var charterAutoSavesSeparateFolder:Bool = false; + /** + * CHARACTER EDITOR + */ + public static var stageSelected:String = null; + public static var characterHitbox:Bool = true; + public static var characterCamera:Bool = true; + public static var characterAxis:Bool = true; + public static var characterDragging:Bool = true; + public static var playAnimOnOffset:Bool = false; + /** * PLAYER 1 CONTROLS */ @@ -80,6 +98,8 @@ class Options public static var P1_NOTE_DOWN:Array<FlxKey> = [S]; public static var P1_NOTE_UP:Array<FlxKey> = [W]; public static var P1_NOTE_RIGHT:Array<FlxKey> = [D]; + + // Menus public static var P1_LEFT:Array<FlxKey> = [A]; public static var P1_DOWN:Array<FlxKey> = [S]; public static var P1_UP:Array<FlxKey> = [W]; @@ -87,16 +107,30 @@ class Options public static var P1_ACCEPT:Array<FlxKey> = [ENTER]; public static var P1_BACK:Array<FlxKey> = [BACKSPACE]; public static var P1_PAUSE:Array<FlxKey> = [ENTER]; + + // Misc public static var P1_RESET:Array<FlxKey> = [R]; public static var P1_SWITCHMOD:Array<FlxKey> = [TAB]; + public static var P1_VOLUME_UP:Array<FlxKey> = []; + public static var P1_VOLUME_DOWN:Array<FlxKey> = []; + public static var P1_VOLUME_MUTE:Array<FlxKey> = []; + + // Debugs + public static var P1_DEV_ACCESS:Array<FlxKey> = [SEVEN]; + public static var P1_DEV_CONSOLE:Array<FlxKey> = [F2]; + public static var P1_DEV_RELOAD:Array<FlxKey> = [F5]; /** - * PLAYER 2 CONTROLS (ALT) - */ + * PLAYER 2 CONTROLS (ALT) + */ + + // Notes public static var P2_NOTE_LEFT:Array<FlxKey> = [LEFT]; public static var P2_NOTE_DOWN:Array<FlxKey> = [DOWN]; public static var P2_NOTE_UP:Array<FlxKey> = [UP]; public static var P2_NOTE_RIGHT:Array<FlxKey> = [RIGHT]; + + // Menus public static var P2_LEFT:Array<FlxKey> = [LEFT]; public static var P2_DOWN:Array<FlxKey> = [DOWN]; public static var P2_UP:Array<FlxKey> = [UP]; @@ -104,16 +138,30 @@ class Options public static var P2_ACCEPT:Array<FlxKey> = [SPACE]; public static var P2_BACK:Array<FlxKey> = [ESCAPE]; public static var P2_PAUSE:Array<FlxKey> = [ESCAPE]; + + // Misc public static var P2_RESET:Array<FlxKey> = []; public static var P2_SWITCHMOD:Array<FlxKey> = []; + public static var P2_VOLUME_UP:Array<FlxKey> = [PLUS]; + public static var P2_VOLUME_DOWN:Array<FlxKey> = [MINUS]; + public static var P2_VOLUME_MUTE:Array<FlxKey> = [ZERO]; + + // Debugs + public static var P2_DEV_ACCESS:Array<FlxKey> = []; + public static var P2_DEV_CONSOLE:Array<FlxKey> = []; + public static var P2_DEV_RELOAD:Array<FlxKey> = []; /** - * SOLO GETTERS - */ + * SOLO GETTERS + */ + + // Notes public static var SOLO_NOTE_LEFT(get, null):Array<FlxKey>; public static var SOLO_NOTE_DOWN(get, null):Array<FlxKey>; public static var SOLO_NOTE_UP(get, null):Array<FlxKey>; public static var SOLO_NOTE_RIGHT(get, null):Array<FlxKey>; + + // Menus public static var SOLO_LEFT(get, null):Array<FlxKey>; public static var SOLO_DOWN(get, null):Array<FlxKey>; public static var SOLO_UP(get, null):Array<FlxKey>; @@ -121,8 +169,18 @@ class Options public static var SOLO_ACCEPT(get, null):Array<FlxKey>; public static var SOLO_BACK(get, null):Array<FlxKey>; public static var SOLO_PAUSE(get, null):Array<FlxKey>; + + // Misc public static var SOLO_RESET(get, null):Array<FlxKey>; public static var SOLO_SWITCHMOD(get, null):Array<FlxKey>; + public static var SOLO_VOLUME_UP(get, null):Array<FlxKey>; + public static var SOLO_VOLUME_DOWN(get, null):Array<FlxKey>; + public static var SOLO_VOLUME_MUTE(get, null):Array<FlxKey>; + + // Debugs + public static var SOLO_DEV_ACCESS(get, null):Array<FlxKey>; + public static var SOLO_DEV_CONSOLE(get, null):Array<FlxKey>; + public static var SOLO_DEV_RELOAD(get, null):Array<FlxKey>; public static function load() { if (__save == null) __save = new FlxSave(); @@ -156,6 +214,10 @@ class Options PlayerSettings.solo.setKeyboardScheme(Solo); PlayerSettings.player1.setKeyboardScheme(Duo(true)); PlayerSettings.player2.setKeyboardScheme(Duo(false)); + + FlxG.sound.volumeUpKeys = SOLO_VOLUME_UP; + FlxG.sound.volumeDownKeys = SOLO_VOLUME_DOWN; + FlxG.sound.muteKeys = SOLO_VOLUME_MUTE; } public static function save() { diff --git a/source/funkin/options/OptionsMenu.hx b/source/funkin/options/OptionsMenu.hx index ca4535de..ace34aad 100644 --- a/source/funkin/options/OptionsMenu.hx +++ b/source/funkin/options/OptionsMenu.hx @@ -89,19 +89,14 @@ class OptionsMenu extends TreeMenu { }); }]); - var xmlPath = Paths.xml("config/options"); - for(source in [funkin.backend.assets.AssetSource.SOURCE, funkin.backend.assets.AssetSource.MODS]) { - if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { - var access:Access = null; - try { - access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source))); - } catch(e) { - Logs.error('Error while parsing options.xml: ${Std.string(e)}'); - } + for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { + var xmlPath = Paths.xml('config/options/LIB_$i'); - if (access != null) - for(o in parseOptionsFromXML(access)) - main.add(o); + if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) { + var access:Access = null; + try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT"))) + catch(e) Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR); + if (access != null) for (o in parseOptionsFromXML(access)) main.add(o); } } } diff --git a/source/funkin/options/OptionsScreen.hx b/source/funkin/options/OptionsScreen.hx index 8451e9c1..53dd5b06 100644 --- a/source/funkin/options/OptionsScreen.hx +++ b/source/funkin/options/OptionsScreen.hx @@ -82,7 +82,7 @@ class OptionsScreen extends FlxTypedSpriteGroup<OptionType> { option.selected = false; option.y = __firstFrame ? y : CoolUtil.fpsLerp(option.y, y, 0.25); - option.x = x + (-50 + (Math.abs(Math.cos((option.y + (optionHeight / 2) - (FlxG.camera.scroll.y + (FlxG.height / 2))) / (FlxG.height * 1.25) * Math.PI)) * 150)); + option.x = x + (-50 + (Math.abs(Math.cos((option.y + (optionHeight / 2) - (FlxG.camera.scroll.y + (FlxG.camera.height / 2))) / (FlxG.camera.height * 1.25) * Math.PI)) * 150)); } if (__firstFrame) { __firstFrame = false; diff --git a/source/funkin/options/TreeMenu.hx b/source/funkin/options/TreeMenu.hx index 6a47fd6d..41717e88 100644 --- a/source/funkin/options/TreeMenu.hx +++ b/source/funkin/options/TreeMenu.hx @@ -18,17 +18,23 @@ class TreeMenu extends UIState { public var pathDesc:FunkinText; public var pathBG:FlxSprite; + public var screenScroll(default, set):Float; + private inline function set_screenScroll(val:Float) { + FlxG.camera.scroll.x = val * FlxG.camera.width; + return screenScroll = val; + } + public static var lastState:Class<FlxState> = null; // Static for fixing the softlock bugs when resetting the state - Nex public function new(scriptsAllowed:Bool = true, ?scriptName:String) { - if(lastState == null) lastState = Type.getClass(FlxG.state); + if (lastState == null) lastState = Type.getClass(FlxG.state); super(scriptsAllowed, scriptName); } public override function createPost() { if (main == null) main = new OptionsScreen("Fallback Treemenu", "Please set the \"main\" variable in your extended class before createPost", [new TextOption("Oops! No Options", "This doesn't look like it was supposed to happen...", () -> FlxG.resetState() ) ]); - FlxG.camera.scroll.set(-FlxG.width, 0); + screenScroll = -1; pathLabel = new FunkinText(4, 4, FlxG.width - 8, "> Tree Menu", 32, true); pathLabel.borderSize = 1.25; @@ -66,28 +72,10 @@ class TreeMenu extends UIState { if (menuChangeTween != null) menuChangeTween.cancel(); - // i hate this code - Nex - // we need to rewrite OptionsTree to be more sane - var current = null; - var last = null; - var wasClosing = optionsTree.wasClosing; - - current = optionsTree.members.last(); - if (wasClosing) { - last = optionsTree.lastMenu; - } else if (optionsTree.members.length > 1) { - last = optionsTree.members[optionsTree.members.length - 2]; // previous - } - - if (current != null) - current.visible = true; - - menuChangeTween = FlxTween.tween(FlxG.camera.scroll, {x: FlxG.width * Math.max(0, (optionsTree.members.length-1))}, 1.5, {ease: menuTransitionEase, onComplete: function(t) { + menuChangeTween = FlxTween.num(screenScroll, Math.max(0, (optionsTree.members.length-1)), 1.5, {ease: menuTransitionEase, onComplete: function(t) { optionsTree.clearLastMenu(); - if (!wasClosing && last != null) // only hide if they are opening a menu - last.visible = false; menuChangeTween = null; - }}); + }}, (val:Float) -> screenScroll = val); reloadLabels(); } @@ -128,6 +116,20 @@ class TreeMenu extends UIState { pathLabel.x = lerp(pathLabel.x, Math.max(0, FlxG.width - 4 - pathLabel.width), 0.125); } + public override function onResize(width:Int, height:Int) { + super.onResize(width, height); + if (!UIState.resolutionAware) return; + + if (width < FlxG.initialWidth || height < FlxG.initialHeight) { + width = FlxG.initialWidth; height = FlxG.initialHeight; + } + + screenScroll = screenScroll; // Updating the cam position - Nex + + if (pathDesc != null && pathLabel != null) + pathDesc.width = pathLabel.width = width - 8; + } + public static inline function menuTransitionEase(e:Float) return FlxEase.quintInOut(FlxEase.cubeOut(e)); } diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx index 358e5410..b49e151b 100644 --- a/source/funkin/options/categories/AppearanceOptions.hx +++ b/source/funkin/options/categories/AppearanceOptions.hx @@ -43,6 +43,14 @@ class AppearanceOptions extends OptionsScreen { "gpuOnlyBitmaps")); } #end + add(new Checkbox( + "Streamed Music", + "If checked, only musics will have streamed audio, ALSO freeing a LOT of memory with the downside of higher cpu usage if more audio are being streamed at once (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on longer songs. If you aren't sure, leave this on.", + "streamedMusic")); + add(new Checkbox( + "Streamed Vocals", + "If checked, vocals will also be streamed if Streamed Music is checked, will impact the performance if the song uses alot of vocals (EXPERIMENTAL). If you aren't sure, leave this on.", + "streamedVocals")); add(new Checkbox( getName("autoPause"), getDesc("autoPause"), diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 11b0992a..4d6dc72c 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -53,7 +53,7 @@ class GameplayOptions extends OptionsScreen { __lastBeat = Conductor.curBeat; } - var beat = Math.floor(Conductor.getStepForTime(FlxG.sound.music.time) / Conductor.stepsPerBeat); + var beat = Math.floor(Conductor.getTimeInBeats(FlxG.sound.music.time)); if (__lastSongBeat != beat) { __metronome.replay(); __lastSongBeat = beat; diff --git a/source/funkin/options/categories/MiscOptions.hx b/source/funkin/options/categories/MiscOptions.hx index 8529d907..eedaf897 100644 --- a/source/funkin/options/categories/MiscOptions.hx +++ b/source/funkin/options/categories/MiscOptions.hx @@ -1,15 +1,12 @@ package funkin.options.categories; - class MiscOptions extends OptionsScreen { public override function new(title:String, desc:String) { super(title, desc, "MiscOptions."); - - add(new TextOption( - getName("forceCrash"), - getDesc("forceCrash"), - () -> { throw new haxe.Exception("Forced crash."); } - )); + add(new Checkbox( + "Developer Mode", + "If checked, you will be able to access developer features like certain keybinds, editors, developer options, state reloads, console and more.", + "devMode")); #if UPDATE_CHECKING add(new Checkbox( getName("betaUpdates"), diff --git a/source/funkin/options/keybinds/KeybindSetting.hx b/source/funkin/options/keybinds/KeybindSetting.hx index c793e35a..c075fc8d 100644 --- a/source/funkin/options/keybinds/KeybindSetting.hx +++ b/source/funkin/options/keybinds/KeybindSetting.hx @@ -15,16 +15,19 @@ class KeybindSetting extends FlxTypedSpriteGroup<FlxSprite> { public var p2Selected:Bool = false; public var value:String; + public var custom:Bool = false; + public var option1:Null<FlxKey>; public var option2:Null<FlxKey>; - public function new(x:Float, y:Float, name:String, value:String, ?sparrowIcon:String, ?sparrowAnim:String) { + public function new(x:Float, y:Float, name:String, value:String, ?sparrowIcon:String, ?sparrowAnim:String, ?custom:Bool = false) { super(); this.value = value; - title = new Alphabet(0, 0, name, "bold"); + this.custom = custom; + title = new Alphabet(0, 0, name, true); add(title); - var controlArrayP1:Array<FlxKey> = Reflect.field(Options, 'P1_${value}'); - var controlArrayP2:Array<FlxKey> = Reflect.field(Options, 'P2_${value}'); + var controlArrayP1:Array<FlxKey> = custom ? [Reflect.getProperty(FlxG.save.data, 'P1_${value}')] : Reflect.field(Options, 'P1_${value}'); + var controlArrayP2:Array<FlxKey> = custom ? [Reflect.getProperty(FlxG.save.data, 'P2_${value}')] : Reflect.field(Options, 'P2_${value}'); option1 = controlArrayP1[0]; option2 = controlArrayP2[0]; @@ -77,19 +80,35 @@ class KeybindSetting extends FlxTypedSpriteGroup<FlxSprite> { KeybindsOptions.instance.openSubState(new ChangeKeybindSubState(function(key:FlxKey) { flicker.stop(); flicker.destroy(); - if (p2) - Reflect.setField(Options, 'P2_$value', [option2 = key]); - else - Reflect.setField(Options, 'P1_$value', [option1 = key]); + if (!custom) { + if (p2) + Reflect.setField(Options, 'P2_$value', [option2 = key]); + else + Reflect.setField(Options, 'P1_$value', [option1 = key]); + } else { + if (p2) + Reflect.setProperty(FlxG.save.data, 'P2_$value', option2 = key); + else + Reflect.setProperty(FlxG.save.data, 'P1_$value', option1 = key); + } + updateText(); callback(); }, function() { flicker.stop(); flicker.destroy(); - if (p2) - Reflect.setField(Options, 'P2_$value', [option2 = 0]); - else - Reflect.setField(Options, 'P1_$value', [option1 = 0]); + if (!custom) { + if (p2) + Reflect.setField(Options, 'P2_$value', [option2 = 0]); + else + Reflect.setField(Options, 'P1_$value', [option1 = 0]); + } else { + if (p2) + Reflect.setProperty(FlxG.save.data, 'P2_$value', option2 = 0); + else + Reflect.setProperty(FlxG.save.data, 'P1_$value', option1 = 0); + } + updateText(); cancelCallback(); })); } diff --git a/source/funkin/options/keybinds/KeybindsOptions.hx b/source/funkin/options/keybinds/KeybindsOptions.hx index 56e8f0f5..283706b3 100644 --- a/source/funkin/options/keybinds/KeybindsOptions.hx +++ b/source/funkin/options/keybinds/KeybindsOptions.hx @@ -1,26 +1,18 @@ package funkin.options.keybinds; import flixel.util.FlxColor; - +import haxe.xml.Access; using StringTools; -typedef KeybindCategory = { - var name:String; - var settings:Array<{ - var name:String; - var control:String; - var ?icon:Array<String>; - }>; -} class KeybindsOptions extends MusicBeatSubstate { public static var instance:KeybindsOptions; - public var categories:Array<KeybindCategory> = []; - public function translate(id:String, ?args:Array<Dynamic>) return TU.translate("KeybindsOptions." + id, args); + public var categories:Array<ControlsCategory> = []; + public var settingCam:FlxCamera; public var p2Selected:Bool = false; @@ -103,6 +95,23 @@ class KeybindsOptions extends MusicBeatSubstate { }, ] }, + { + name: 'Volume', + settings: [ + { + name: 'Up', + control: 'VOLUME_UP' + }, + { + name: 'Down', + control: 'VOLUME_DOWN' + }, + { + name: 'Mute', + control: 'VOLUME_MUTE' + }, + ] + }, { name: translate("category.engine"), settings: [ @@ -111,6 +120,24 @@ class KeybindsOptions extends MusicBeatSubstate { control: 'SWITCHMOD' }, ] + }, + { + name: 'Developer', + devModeOnly: true, + settings: [ + { + name: 'Developer Menus', + control: 'DEV_ACCESS' + }, + { + name: 'Open Console', + control: 'DEV_CONSOLE' + }, + { + name: 'Reload State', + control: 'DEV_RELOAD' + }, + ] } ]; @@ -145,8 +172,13 @@ class KeybindsOptions extends MusicBeatSubstate { FlxG.camera.follow(camFollow, LOCKON, 0.125); } + var customCategories = loadCustomCategories(); + for (i in customCategories) categories.push(i); + var k:Int = 0; - for(category in categories) { + for (category in categories) { + if (category.devModeOnly && !Options.devMode) continue; + k++; var title = new Alphabet(0, k * 75, category.name, "bold"); title.screenCenter(X); @@ -160,8 +192,10 @@ class KeybindsOptions extends MusicBeatSubstate { sparrowIcon = e.icon[0]; sparrowAnim = e.icon[1]; } + if (e.sparrowIcon != null) sparrowIcon = e.sparrowIcon; + if (e.sparrowAnim != null) sparrowAnim = e.sparrowAnim; - var text = new KeybindSetting(100, k * 75, e.name, e.control, sparrowIcon, sparrowAnim); + var text = new KeybindSetting(100, k * 75, e.name, e.control, sparrowIcon, sparrowAnim, e.custom == null ? false : e.custom); if (!isSubState) text.bind1.color = text.bind2.color = FlxColor.BLACK; alphabets.add(text); @@ -170,6 +204,10 @@ class KeybindsOptions extends MusicBeatSubstate { } add(alphabets); add(camFollow); + + FlxG.sound.volumeUpKeys = []; + FlxG.sound.volumeDownKeys = []; + FlxG.sound.muteKeys = []; } public override function destroy() { @@ -183,10 +221,8 @@ class KeybindsOptions extends MusicBeatSubstate { public override function update(elapsed:Float) { super.update(elapsed); - - if (isSubState) { - bg.alpha = lerp(bg.alpha, 0.1, 0.125); - } else { + if (isSubState) bg.alpha = lerp(bg.alpha, 0.1, 0.125); + else { if (curSelected < 4) { if (coloredBG.alpha == 0) coloredBG.color = noteColors[curSelected]; @@ -202,12 +238,14 @@ class KeybindsOptions extends MusicBeatSubstate { changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0)); if (controls.BACK) { - MusicBeatState.skipTransIn = true; - if (isSubState) - close(); - else + if (isSubState) close(); + else { + MusicBeatState.skipTransIn = true; FlxG.switchState(new OptionsMenu()); + } + ControlsUtil.resetCustomControls(); Options.applyKeybinds(); + ControlsUtil.loadCustomControls(); Options.save(); return; } @@ -251,9 +289,65 @@ class KeybindsOptions extends MusicBeatSubstate { var minH = FlxG.height / 2; var maxH = alphabets.members[alphabets.length-1].y + alphabets.members[alphabets.length-1].height - (FlxG.height / 2); if (minH < maxH) - camFollow.setPosition(FlxG.width / 2, FlxMath.bound(alphabet.y + (alphabet.height / 2) - (35), minH, maxH)); + camFollow.setPosition(FlxG.width / 2, CoolUtil.bound(alphabet.y + (alphabet.height / 2) - 35, minH, maxH)); else camFollow.setPosition(FlxG.width / 2, FlxG.height / 2); } } + + public function loadCustomCategories() { + var customCategories:Array<ControlsCategory> = []; + + var xmlPath = Paths.xml("config/controls"); + for(source in [funkin.backend.assets.AssetSource.SOURCE, funkin.backend.assets.AssetSource.MODS]) { + if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { + var access:Access = null; + try { + access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source)).firstElement()); + } catch(e) { + Logs.trace('Error while parsing controls.xml: ${Std.string(e)}', ERROR); + } + + if (access != null) { + for (category in access.elements) { + if (!category.has.name) continue; + + var cat:ControlsCategory = { + name: category.getAtt("name"), + settings: [] + }; + + for (control in category.elements) { + if (control.has.menuName && control.has.saveName) { + cat.settings.push({ + name: control.getAtt("menuName"), + control: control.getAtt("saveName"), + custom: true, + sparrowIcon: control.getAtt("menuIcon").getDefault(null), + sparrowAnim: control.getAtt("menuAnim").getDefault(null) + }); + } + } + + customCategories.push(cat); + } + } + } + } + return customCategories; + } +} + +typedef KeybindSettingData = { + var name:String; + var control:String; + var ?custom:Bool; + var ?sparrowIcon:String; + var ?sparrowAnim:String; +} + +typedef ControlsCategory = { + var name:String; + var settings:Array<KeybindSettingData>; + var ?devModeOnly:Bool; } \ No newline at end of file diff --git a/source/funkin/options/type/ArrayOption.hx b/source/funkin/options/type/ArrayOption.hx index cf9af189..885d0335 100644 --- a/source/funkin/options/type/ArrayOption.hx +++ b/source/funkin/options/type/ArrayOption.hx @@ -36,7 +36,7 @@ class ArrayOption extends OptionType { var fieldValue = Reflect.field(parent, optionName); if(fieldValue != null) - this.currentSelection = Std.int(Math.max(0, options.indexOf(fieldValue))); + this.currentSelection = CoolUtil.maxInt(0, options.indexOf(fieldValue)); this.optionName = optionName; diff --git a/source/funkin/options/type/GithubIconOption.hx b/source/funkin/options/type/GithubIconOption.hx index d57430f3..be3567a8 100644 --- a/source/funkin/options/type/GithubIconOption.hx +++ b/source/funkin/options/type/GithubIconOption.hx @@ -49,7 +49,21 @@ class GithubUserIcon extends FlxSprite super.update(elapsed); } + #if target.threaded final mutex = new sys.thread.Mutex(); + #end + + inline function acquireMutex() { + #if target.threaded + mutex.acquire(); + #end + } + inline function releaseMutex() { + #if target.threaded + mutex.release(); + #end + } + override function drawComplex(camera:FlxCamera):Void { // Making the image download only if the player actually sees it on the screen - Nex if(waitUntilLoad <= 0) { waitUntilLoad = null; @@ -82,19 +96,19 @@ class GithubUserIcon extends FlxSprite } if(bmap != null) try { - mutex.acquire(); // Avoiding critical section - Nex + acquireMutex(); // Avoiding critical section - Nex var leGraphic:FlxGraphic = FlxG.bitmap.add(bmap, false, key); leGraphic.persist = true; updateDaFunni(leGraphic); bmap = null; - mutex.release(); + releaseMutex(); } catch(e) { Logs.error('Failed to update the pfp for ${user.login}: ${e.message}'); } } else { - mutex.acquire(); + acquireMutex(); updateDaFunni(bmap); - mutex.release(); + releaseMutex(); } }); } diff --git a/source/funkin/options/type/IconOption.hx b/source/funkin/options/type/IconOption.hx index aa4ad3c2..b61c5c67 100644 --- a/source/funkin/options/type/IconOption.hx +++ b/source/funkin/options/type/IconOption.hx @@ -14,6 +14,10 @@ class IconOption extends TextOption { iconSpr = new HealthIcon(icon, false); iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2); + iconSpr.setUnstretchedGraphicSize(150, 150, true); + iconSpr.updateHitbox(); + iconSpr.sprTracker = __text; + iconSpr.sprTrackerAlignment = LEFT; add(iconSpr); } } \ No newline at end of file diff --git a/source/funkin/options/type/NewOption.hx b/source/funkin/options/type/NewOption.hx index 6804608b..28255b8c 100644 --- a/source/funkin/options/type/NewOption.hx +++ b/source/funkin/options/type/NewOption.hx @@ -1,7 +1,6 @@ package funkin.options.type; import flixel.util.FlxColor; -import funkin.game.HealthIcon; /** * Option type that has a new button, and is green. @@ -18,7 +17,7 @@ class NewOption extends TextOption { iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2); iconSpr.scale.set(1.4, 1.4); iconSpr.updateHitbox(); - iconSpr.offset.set(20, -15); + iconSpr.offset.set(15, -15); add(iconSpr); } } \ No newline at end of file diff --git a/source/haxe/Timer.hx b/source/haxe/Timer.hx new file mode 100644 index 00000000..5092af8c --- /dev/null +++ b/source/haxe/Timer.hx @@ -0,0 +1,297 @@ +package haxe; + +#if !lime_cffi +// Original haxe.Timer class + +/* + * Copyright (C)2005-2018 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + The Timer class allows you to create asynchronous timers on platforms that + support events. + + The intended usage is to create an instance of the Timer class with a given + interval, set its run() method to a custom function to be invoked and + eventually call stop() to stop the Timer. + + Note that a running Timer may or may not prevent the program to exit + automatically when main() returns. + + It is also possible to extend this class and override its run() method in + the child class. +**/ +class Timer +{ + #if (flash || js) + private var id:Null<Int>; + #elseif java + private var timer:java.util.Timer; + private var task:java.util.TimerTask; + #elseif (haxe_ver >= "3.4.0") + private var event:MainLoop.MainEvent; + #end + + /** + Creates a new timer that will run every `time_ms` milliseconds. + + After creating the Timer instance, it calls `this.run` repeatedly, + with delays of `time_ms` milliseconds, until `this.stop` is called. + + The first invocation occurs after `time_ms` milliseconds, not + immediately. + + The accuracy of this may be platform-dependent. + **/ + public function new(time_ms:Int) + { + #if flash + var me = this; + id = untyped __global__["flash.utils.setInterval"](function() + { + me.run(); + }, time_ms); + #elseif js + var me = this; + id = untyped setInterval(function() me.run(), time_ms); + #elseif java + timer = new java.util.Timer(); + timer.scheduleAtFixedRate(task = new TimerTask(this), haxe.Int64.ofInt(time_ms), haxe.Int64.ofInt(time_ms)); + #elseif (haxe_ver >= "3.4.0") + var dt = time_ms / 1000; + event = MainLoop.add(function() + { + @:privateAccess event.nextRun += dt; + run(); + }); + event.delay(dt); + #end + } + + /** + Stops `this` Timer. + + After calling this method, no additional invocations of `this.run` + will occur. + + It is not possible to restart `this` Timer once stopped. + **/ + public function stop() + { + #if (flash || js) + if (id == null) return; + #if flash + untyped __global__["flash.utils.clearInterval"](id); + #elseif js + untyped clearInterval(id); + #end + id = null; + #elseif java + if (timer != null) + { + timer.cancel(); + timer = null; + } + task = null; + #elseif (haxe_ver >= "3.4.0") + if (event != null) + { + event.stop(); + event = null; + } + #end + } + + /** + This method is invoked repeatedly on `this` Timer. + + It can be overridden in a subclass, or rebound directly to a custom + function: + var timer = new haxe.Timer(1000); // 1000ms delay + timer.run = function() { ... } + + Once bound, it can still be rebound to different functions until `this` + Timer is stopped through a call to `this.stop`. + **/ + public dynamic function run() {} + + /** + Invokes `f` after `time_ms` milliseconds. + + This is a convenience function for creating a new Timer instance with + `time_ms` as argument, binding its run() method to `f` and then stopping + `this` Timer upon the first invocation. + + If `f` is null, the result is unspecified. + **/ + public static function delay(f:Void->Void, time_ms:Int) + { + var t = new haxe.Timer(time_ms); + t.run = function() + { + t.stop(); + f(); + }; + return t; + } + + /** + Measures the time it takes to execute `f`, in seconds with fractions. + + This is a convenience function for calculating the difference between + Timer.stamp() before and after the invocation of `f`. + + The difference is passed as argument to Log.trace(), with "s" appended + to denote the unit. The optional `pos` argument is passed through. + + If `f` is null, the result is unspecified. + **/ + public static function measure<T>(f:Void->T, ?pos:PosInfos):T + { + var t0 = stamp(); + var r = f(); + Log.trace((stamp() - t0) + "s", pos); + return r; + } + + /** + Returns a timestamp, in seconds with fractions. + + The value itself might differ depending on platforms, only differences + between two values make sense. + **/ + public static inline function stamp():Float + { + #if flash + return flash.Lib.getTimer() / 1000; + #elseif (neko || php) + return Sys.time(); + #elseif js + return Date.now().getTime() / 1000; + #elseif cpp + return untyped __global__.__time_stamp(); + #elseif python + return Sys.cpuTime(); + #elseif sys + return Sys.time(); + #else + return 0; + #end + } +} + +#if java +@:nativeGen +private class TimerTask extends java.util.TimerTask +{ + var timer:Timer; + + public function new(timer:Timer):Void + { + super(); + this.timer = timer; + } + + @:overload override public function run():Void + { + timer.run(); + } +} +#end +#else +import lime.system.System; + +class Timer +{ + private static var sRunningTimers:Array<Timer> = []; + + private var mTime:Float; + private var mFireAt:Float; + private var mRunning:Bool; + + public function new(time:Float) + { + mTime = time; + sRunningTimers.push(this); + mFireAt = getMS() + mTime; + mRunning = true; + } + + public static function delay(f:Void->Void, time:Int) + { + var t = new Timer(time); + + t.run = function() + { + t.stop(); + f(); + }; + + return t; + } + + private static function getMS():Float + { + return System.getTimer(); + } + + public static function measure<T>(f:Void->T, ?pos:PosInfos):T + { + var t0 = stamp(); + var r = f(); + Log.trace((stamp() - t0) + "s", pos); + return r; + } + + dynamic public function run() {} + + public static inline function stamp():Float + { + var timer = System.getTimer(); + return (timer > 0 ? timer / 1000 : 0); + } + + public function stop():Void + { + /*if (mRunning) + { + + for (i in 0...sRunningTimers.length) + { + if (sRunningTimers[i] == this) + { + sRunningTimers[i] = null; + break; + } + } + }*/ + mRunning = false; + } + + @:noCompletion private function __check(inTime:Float) + { + if (inTime >= mFireAt) + { + mFireAt += mTime; + run(); + } + } +} +#end diff --git a/source/lime/_internal/backend/html5/HTML5AudioSource.hx b/source/lime/_internal/backend/html5/HTML5AudioSource.hx index bfca4198..7fd31c4e 100644 --- a/source/lime/_internal/backend/html5/HTML5AudioSource.hx +++ b/source/lime/_internal/backend/html5/HTML5AudioSource.hx @@ -2,6 +2,7 @@ package lime._internal.backend.html5; import lime.math.Vector4; import lime.media.AudioSource; +import lime.media.AudioManager; @:access(lime.media.AudioBuffer) class HTML5AudioSource @@ -9,7 +10,7 @@ class HTML5AudioSource private var completed:Bool; private var gain:Float; private var id:Int; - private var length:Float; + private var length:Null<Float>; private var loops:Int; private var parent:AudioSource; private var playing:Bool; @@ -24,7 +25,9 @@ class HTML5AudioSource position = new Vector4(); } - public function dispose():Void {} + public function dispose():Void { + stop(); + } public function init():Void {} @@ -98,8 +101,9 @@ class HTML5AudioSource { loops--; stop(); - // currentTime = 0; + if (loopTime != null && loopTime > 0) setCurrentTime(loopTime); play(); + parent.onLoop.dispatch(); return; } else if (parent.buffer != null && parent.buffer.__srcHowl != null) @@ -128,7 +132,7 @@ class HTML5AudioSource } else if (parent.buffer != null && parent.buffer.__srcHowl != null) { - var time = (parent.buffer.__srcHowl.seek(id) * 1000) - parent.offset; + var time = parent.buffer.__srcHowl.seek(id) * 1000.0 - parent.offset; if (time < 0) return 0; return time; } @@ -137,6 +141,20 @@ class HTML5AudioSource return 0; } + public function getLatency():Float + { + var ctx = AudioManager.context.web; + if (ctx != null) + { + var baseLatency:Float = untyped ctx.baseLatency != null ? untyped ctx.baseLatency : 0; + var outputLatency:Float = untyped ctx.outputLatency != null ? untyped ctx.outputLatency : 0; + + return (baseLatency + outputLatency) * 1000; + } + + return 0; + } + public function setCurrentTime(value:Float):Float { #if lime_howlerjs @@ -172,7 +190,7 @@ class HTML5AudioSource return gain = value; } - public function getLength():Float + public function getLength():Null<Float> { if (length != 0) { @@ -182,14 +200,14 @@ class HTML5AudioSource #if lime_howlerjs if (parent.buffer != null && parent.buffer.__srcHowl != null) { - return parent.buffer.__srcHowl.duration() * 1000; + return parent.buffer.__srcHowl.duration() * 1000.0; } #end return 0; } - public function setLength(value:Float):Float + public function setLength(value:Null<Float>):Null<Float> { return length = value; } @@ -204,6 +222,14 @@ class HTML5AudioSource return loops = value; } + public function getLoopTime():Float { + return loopTime; + } + + public function setLoopTime(value:Float):Float { + return loopTime = value; + } + public function getPitch():Float { #if lime_howlerjs @@ -218,9 +244,11 @@ class HTML5AudioSource #if lime_howlerjs parent.buffer.__srcHowl.rate(value); #end + return getPitch(); } + public function getPosition():Vector4 { #if lime_howlerjs @@ -250,4 +278,4 @@ class HTML5AudioSource return position; } -} \ No newline at end of file +} diff --git a/source/lime/_internal/backend/native/NativeApplication.hx b/source/lime/_internal/backend/native/NativeApplication.hx new file mode 100644 index 00000000..ac8ab467 --- /dev/null +++ b/source/lime/_internal/backend/native/NativeApplication.hx @@ -0,0 +1,981 @@ +package lime._internal.backend.native; + +import haxe.Timer; +import lime._internal.backend.native.NativeCFFI; +import lime.app.Application; +import lime.graphics.opengl.GL; +import lime.graphics.OpenGLRenderContext; +import lime.graphics.RenderContext; +import lime.math.Rectangle; +import lime.media.AudioManager; +import lime.system.Clipboard; +import lime.system.Display; +import lime.system.DisplayMode; +import lime.system.JNI; +import lime.system.Sensor; +import lime.system.SensorType; +import lime.system.System; +import lime.ui.Gamepad; +import lime.ui.Joystick; +import lime.ui.JoystickHatPosition; +import lime.ui.KeyCode; +import lime.ui.KeyModifier; +import lime.ui.Touch; +import lime.ui.Window; + +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@:access(haxe.Timer) +@:access(lime._internal.backend.native.NativeCFFI) +@:access(lime._internal.backend.native.NativeOpenGLRenderContext) +@:access(lime._internal.backend.native.NativeWindow) +@:access(lime.app.Application) +@:access(lime.graphics.opengl.GL) +@:access(lime.graphics.OpenGLRenderContext) +@:access(lime.graphics.Renderer) +@:access(lime.system.Clipboard) +@:access(lime.system.Sensor) +@:access(lime.ui.Gamepad) +@:access(lime.ui.Joystick) +@:access(lime.ui.Window) +class NativeApplication +{ + private var applicationEventInfo = new ApplicationEventInfo(UPDATE); + private var clipboardEventInfo = new ClipboardEventInfo(); + private var currentTouches = new Map<Int, Touch>(); + private var dropEventInfo = new DropEventInfo(); + private var gamepadEventInfo = new GamepadEventInfo(); + private var joystickEventInfo = new JoystickEventInfo(); + private var keyEventInfo = new KeyEventInfo(); + private var mouseEventInfo = new MouseEventInfo(); + private var renderEventInfo = new RenderEventInfo(RENDER); + private var sensorEventInfo = new SensorEventInfo(); + private var textEventInfo = new TextEventInfo(); + private var touchEventInfo = new TouchEventInfo(); + private var unusedTouchesPool = new List<Touch>(); + private var windowEventInfo = new WindowEventInfo(); + + public var handle:Dynamic; + + private var pauseTimer:Int; + private var parent:Application; + private var toggleFullscreen:Bool; + + private static function __init__() + { + #if (lime_cffi && !macro) + var init = NativeCFFI; + #end + } + + public function new(parent:Application):Void + { + this.parent = parent; + pauseTimer = -1; + toggleFullscreen = true; + + AudioManager.init(); + + #if (ios || android || tvos) + Sensor.registerSensor(SensorType.ACCELEROMETER, 0); + #end + + #if (!macro && lime_cffi) + handle = NativeCFFI.lime_application_create(); + #end + } + + private function advanceTimer():Void + { + #if lime_cffi + if (pauseTimer > -1) + { + var offset = System.getTimer() - pauseTimer; + for(timer in Timer.sRunningTimers) { + if(timer != null && timer.mRunning) timer.mFireAt += offset; + } + pauseTimer = -1; + } + #end + } + + public function exec():Int + { + #if !macro + #if lime_cffi + NativeCFFI.lime_application_event_manager_register(handleApplicationEvent, applicationEventInfo); + NativeCFFI.lime_clipboard_event_manager_register(handleClipboardEvent, clipboardEventInfo); + NativeCFFI.lime_drop_event_manager_register(handleDropEvent, dropEventInfo); + NativeCFFI.lime_gamepad_event_manager_register(handleGamepadEvent, gamepadEventInfo); + NativeCFFI.lime_joystick_event_manager_register(handleJoystickEvent, joystickEventInfo); + NativeCFFI.lime_key_event_manager_register(handleKeyEvent, keyEventInfo); + NativeCFFI.lime_mouse_event_manager_register(handleMouseEvent, mouseEventInfo); + NativeCFFI.lime_render_event_manager_register(handleRenderEvent, renderEventInfo); + NativeCFFI.lime_text_event_manager_register(handleTextEvent, textEventInfo); + NativeCFFI.lime_touch_event_manager_register(handleTouchEvent, touchEventInfo); + NativeCFFI.lime_window_event_manager_register(handleWindowEvent, windowEventInfo); + #if (ios || android || tvos) + NativeCFFI.lime_sensor_event_manager_register(handleSensorEvent, sensorEventInfo); + #end + #end + + #if (nodejs && lime_cffi) + NativeCFFI.lime_application_init(handle); + + var eventLoop = function() + { + var active = NativeCFFI.lime_application_update(handle); + + if (!active) + { + untyped process.exitCode = NativeCFFI.lime_application_quit(handle); + parent.onExit.dispatch(untyped process.exitCode); + } + else + { + untyped setImmediate(eventLoop); + } + } + + untyped setImmediate(eventLoop); + return 0; + #elseif lime_cffi + var result = NativeCFFI.lime_application_exec(handle); + + #if (!webassembly && !ios && !nodejs) + parent.onExit.dispatch(result); + #end + + return result; + #end + #end + + return 0; + } + + public function exit():Void + { + AudioManager.shutdown(); + + #if (!macro && lime_cffi) + NativeCFFI.lime_application_quit(handle); + #end + } + + private function handleApplicationEvent():Void + { + switch (applicationEventInfo.type) + { + case UPDATE: + updateTimer(); + + parent.onUpdate.dispatch(applicationEventInfo.deltaTime); + + default: + } + } + + private function handleClipboardEvent():Void + { + Clipboard.__update(); + } + + private function handleDropEvent():Void + { + for (window in parent.windows) + { + window.onDropFile.dispatch(#if hl @:privateAccess String.fromUTF8(dropEventInfo.file) #else dropEventInfo.file #end); + } + } + + private function handleGamepadEvent():Void + { + switch (gamepadEventInfo.type) + { + case AXIS_MOVE: + var gamepad = Gamepad.devices.get(gamepadEventInfo.id); + if (gamepad != null) gamepad.onAxisMove.dispatch(gamepadEventInfo.axis, gamepadEventInfo.axisValue); + + case BUTTON_DOWN: + var gamepad = Gamepad.devices.get(gamepadEventInfo.id); + if (gamepad != null) gamepad.onButtonDown.dispatch(gamepadEventInfo.button); + + case BUTTON_UP: + var gamepad = Gamepad.devices.get(gamepadEventInfo.id); + if (gamepad != null) gamepad.onButtonUp.dispatch(gamepadEventInfo.button); + + case CONNECT: + Gamepad.__connect(gamepadEventInfo.id); + + case DISCONNECT: + Gamepad.__disconnect(gamepadEventInfo.id); + } + } + + private function handleJoystickEvent():Void + { + switch (joystickEventInfo.type) + { + case AXIS_MOVE: + var joystick = Joystick.devices.get(joystickEventInfo.id); + if (joystick != null) joystick.onAxisMove.dispatch(joystickEventInfo.index, joystickEventInfo.x); + + case HAT_MOVE: + var joystick = Joystick.devices.get(joystickEventInfo.id); + if (joystick != null) joystick.onHatMove.dispatch(joystickEventInfo.index, joystickEventInfo.eventValue); + + case TRACKBALL_MOVE: // I guess this was just removed ?? + + case BUTTON_DOWN: + var joystick = Joystick.devices.get(joystickEventInfo.id); + if (joystick != null) joystick.onButtonDown.dispatch(joystickEventInfo.index); + + case BUTTON_UP: + var joystick = Joystick.devices.get(joystickEventInfo.id); + if (joystick != null) joystick.onButtonUp.dispatch(joystickEventInfo.index); + + case CONNECT: + Joystick.__connect(joystickEventInfo.id); + + case DISCONNECT: + Joystick.__disconnect(joystickEventInfo.id); + } + } + + private function handleKeyEvent():Void + { + var window = parent.__windowByID.get(keyEventInfo.windowID); + + if (window != null) + { + var type:KeyEventType = keyEventInfo.type; + var int32:Float = keyEventInfo.keyCode; + var keyCode:KeyCode = Std.int(int32); + var modifier:KeyModifier = keyEventInfo.modifier; + + switch (type) + { + case KEY_DOWN: + window.onKeyDown.dispatch(keyCode, modifier); + + case KEY_UP: + window.onKeyUp.dispatch(keyCode, modifier); + } + + #if (windows || linux) + if (keyCode == RETURN) + { + if (type == KEY_DOWN) + { + if (toggleFullscreen && modifier.altKey && (!modifier.ctrlKey && !modifier.shiftKey && !modifier.metaKey)) + { + toggleFullscreen = false; + + if (!window.onKeyDown.canceled) + { + window.fullscreen = !window.fullscreen; + } + } + } + else + { + toggleFullscreen = true; + } + } + + #if rpi + if (keyCode == ESCAPE && modifier == KeyModifier.NONE && type == KEY_UP && !window.onKeyUp.canceled) + { + System.exit(0); + } + #end + #elseif mac + if (keyCode == F) + { + if (type == KEY_DOWN) + { + if (toggleFullscreen && (modifier.ctrlKey && modifier.metaKey) && (!modifier.altKey && !modifier.shiftKey)) + { + toggleFullscreen = false; + + if (!window.onKeyDown.canceled) + { + window.fullscreen = !window.fullscreen; + } + } + } + else + { + toggleFullscreen = true; + } + } + #elseif android + if (keyCode == APP_CONTROL_BACK && modifier == KeyModifier.NONE && type == KEY_UP && !window.onKeyUp.canceled) + { + var mainActivity = JNI.createStaticField("org/haxe/extension/Extension", "mainActivity", "Landroid/app/Activity;"); + var moveTaskToBack = JNI.createMemberMethod("android/app/Activity", "moveTaskToBack", "(Z)Z"); + + moveTaskToBack(mainActivity.get(), true); + } + #end + } + } + + private function handleMouseEvent():Void + { + var window = parent.__windowByID.get(mouseEventInfo.windowID); + + if (window != null) + { + switch (mouseEventInfo.type) + { + case MOUSE_DOWN: + window.clickCount = mouseEventInfo.clickCount; + window.onMouseDown.dispatch(mouseEventInfo.x, mouseEventInfo.y, mouseEventInfo.button); + window.clickCount = 0; + + case MOUSE_UP: + window.clickCount = mouseEventInfo.clickCount; + window.onMouseUp.dispatch(mouseEventInfo.x, mouseEventInfo.y, mouseEventInfo.button); + window.clickCount = 0; + + case MOUSE_MOVE: + window.onMouseMove.dispatch(mouseEventInfo.x, mouseEventInfo.y); + window.onMouseMoveRelative.dispatch(mouseEventInfo.movementX, mouseEventInfo.movementY); + + case MOUSE_WHEEL: + window.onMouseWheel.dispatch(mouseEventInfo.x, mouseEventInfo.y, UNKNOWN); + + default: + } + } + } + + private function handleRenderEvent():Void + { + // TODO: Allow windows to render independently + + for (window in parent.__windows) + { + if (window == null) continue; + + // parent.renderer = renderer; + + switch (renderEventInfo.type) + { + case RENDER: + if (window.context != null) + { + window.__backend.render(); + window.onRender.dispatch(window.context); + + if (!window.onRender.canceled) + { + window.__backend.contextFlip(); + } + } + + case RENDER_CONTEXT_LOST: + if (window.__backend.useHardware && window.context != null) + { + switch (window.context.type) + { + case OPENGL, OPENGLES, WEBGL: + #if (lime_cffi && (lime_opengl || lime_opengles) && !display) + var gl = window.context.gl; + (gl : NativeOpenGLRenderContext).__contextLost(); + if (GL.context == gl) GL.context = null; + #end + + default: + } + + window.context = null; + window.onRenderContextLost.dispatch(); + } + + case RENDER_CONTEXT_RESTORED: + if (window.__backend.useHardware) + { + // GL.context = new OpenGLRenderContext (); + // window.context.gl = GL.context; + + window.onRenderContextRestored.dispatch(window.context); + } + } + } + } + + private function handleSensorEvent():Void + { + var sensor = Sensor.sensorByID.get(sensorEventInfo.id); + + if (sensor != null) + { + sensor.onUpdate.dispatch(sensorEventInfo.x, sensorEventInfo.y, sensorEventInfo.z); + } + } + + private function handleTextEvent():Void + { + var window = parent.__windowByID.get(textEventInfo.windowID); + + if (window != null) + { + switch (textEventInfo.type) + { + case TEXT_INPUT: + window.onTextInput.dispatch(#if hl @:privateAccess String.fromUTF8(textEventInfo.text) #else textEventInfo.text #end); + + case TEXT_EDIT: + window.onTextEdit.dispatch(#if hl @:privateAccess String.fromUTF8(textEventInfo.text) #else textEventInfo.text #end, textEventInfo.start, + textEventInfo.length); + + default: + } + } + } + + private function handleTouchEvent():Void + { + switch (touchEventInfo.type) + { + case TOUCH_START: + var touch = unusedTouchesPool.pop(); + + if (touch == null) + { + touch = new Touch(touchEventInfo.x, touchEventInfo.y, touchEventInfo.id, touchEventInfo.dx, touchEventInfo.dy, touchEventInfo.pressure, + touchEventInfo.device); + } + else + { + touch.x = touchEventInfo.x; + touch.y = touchEventInfo.y; + touch.id = touchEventInfo.id; + touch.dx = touchEventInfo.dx; + touch.dy = touchEventInfo.dy; + touch.pressure = touchEventInfo.pressure; + touch.device = touchEventInfo.device; + } + + currentTouches.set(touch.id, touch); + + Touch.onStart.dispatch(touch); + + case TOUCH_END: + var touch = currentTouches.get(touchEventInfo.id); + + if (touch != null) + { + touch.x = touchEventInfo.x; + touch.y = touchEventInfo.y; + touch.dx = touchEventInfo.dx; + touch.dy = touchEventInfo.dy; + touch.pressure = touchEventInfo.pressure; + + Touch.onEnd.dispatch(touch); + + currentTouches.remove(touchEventInfo.id); + unusedTouchesPool.add(touch); + } + + case TOUCH_MOVE: + var touch = currentTouches.get(touchEventInfo.id); + + if (touch != null) + { + touch.x = touchEventInfo.x; + touch.y = touchEventInfo.y; + touch.dx = touchEventInfo.dx; + touch.dy = touchEventInfo.dy; + touch.pressure = touchEventInfo.pressure; + + Touch.onMove.dispatch(touch); + } + + default: + } + } + + private function handleWindowEvent():Void + { + var window = parent.__windowByID.get(windowEventInfo.windowID); + + if (window != null) + { + switch (windowEventInfo.type) + { + case WINDOW_ACTIVATE: + advanceTimer(); + window.onActivate.dispatch(); + AudioManager.resume(); + + case WINDOW_CLOSE: + window.close(); + + case WINDOW_DEACTIVATE: + window.onDeactivate.dispatch(); + AudioManager.suspend(); + pauseTimer = System.getTimer(); + + case WINDOW_ENTER: + window.onEnter.dispatch(); + + case WINDOW_EXPOSE: + window.onExpose.dispatch(); + + case WINDOW_FOCUS_IN: + window.onFocusIn.dispatch(); + + case WINDOW_FOCUS_OUT: + window.onFocusOut.dispatch(); + + case WINDOW_LEAVE: + window.onLeave.dispatch(); + + case WINDOW_MAXIMIZE: + window.__maximized = true; + window.__fullscreen = false; + window.__minimized = false; + window.onMaximize.dispatch(); + + case WINDOW_MINIMIZE: + window.__minimized = true; + window.__maximized = false; + window.__fullscreen = false; + window.onMinimize.dispatch(); + + case WINDOW_MOVE: + window.__x = windowEventInfo.x; + window.__y = windowEventInfo.y; + window.onMove.dispatch(windowEventInfo.x, windowEventInfo.y); + + case WINDOW_RESIZE: + window.__width = windowEventInfo.width; + window.__height = windowEventInfo.height; + window.onResize.dispatch(windowEventInfo.width, windowEventInfo.height); + + case WINDOW_RESTORE: + window.__fullscreen = false; + window.__minimized = false; + window.onRestore.dispatch(); + + case WINDOW_SHOW: + window.onShow.dispatch(); + + case WINDOW_HIDE: + window.onHide.dispatch(); + } + } + } + + private function updateTimer():Void + { + #if lime_cffi + if (Timer.sRunningTimers.length > 0) + { + var currentTime = System.getTimer(); + var foundNull = false; + var timer; + + for (i in 0...Timer.sRunningTimers.length) + { + timer = Timer.sRunningTimers[i]; + + if (timer != null && timer.mRunning) + { + if (currentTime >= timer.mFireAt) + { + timer.mFireAt += timer.mTime; + timer.run(); + } + } + else + { + foundNull = true; + } + } + + if (foundNull) + { + Timer.sRunningTimers = Timer.sRunningTimers.filter(function(val) + { + return val != null && val.mRunning; + }); + } + } + + #if (haxe_ver >= 4.2) + #if target.threaded + sys.thread.Thread.current().events.progress(); + #else + // Duplicate code required because Haxe 3 can't handle + // #if (haxe_ver >= 4.2 && target.threaded) + @:privateAccess haxe.EntryPoint.processEvents(); + #end + #else + @:privateAccess haxe.EntryPoint.processEvents(); + #end + #end + } +} + +@:keep /*private*/ class ApplicationEventInfo +{ + public var deltaTime:Int; + public var type:ApplicationEventType; + + public function new(type:ApplicationEventType = null, deltaTime:Int = 0) + { + this.type = type; + this.deltaTime = deltaTime; + } + + public function clone():ApplicationEventInfo + { + return new ApplicationEventInfo(type, deltaTime); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract ApplicationEventType(Int) +{ + var UPDATE = 0; + var EXIT = 1; +} + +@:keep /*private*/ class ClipboardEventInfo +{ + public var type:ClipboardEventType; + + public function new(type:ClipboardEventType = null) + { + this.type = type; + } + + public function clone():ClipboardEventInfo + { + return new ClipboardEventInfo(type); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract ClipboardEventType(Int) +{ + var UPDATE = 0; +} + +@:keep /*private*/ class DropEventInfo +{ + public var file:#if hl hl.Bytes #else String #end; + public var type:DropEventType; + + public function new(type:DropEventType = null, file = null) + { + this.type = type; + this.file = file; + } + + public function clone():DropEventInfo + { + return new DropEventInfo(type, file); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract DropEventType(Int) +{ + var DROP_FILE = 0; +} + +@:keep /*private*/ class GamepadEventInfo +{ + public var axis:Int; + public var button:Int; + public var id:Int; + public var type:GamepadEventType; + public var axisValue:Float; + + public function new(type:GamepadEventType = null, id:Int = 0, button:Int = 0, axis:Int = 0, value:Float = 0) + { + this.type = type; + this.id = id; + this.button = button; + this.axis = axis; + this.axisValue = value; + } + + public function clone():GamepadEventInfo + { + return new GamepadEventInfo(type, id, button, axis, axisValue); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract GamepadEventType(Int) +{ + var AXIS_MOVE = 0; + var BUTTON_DOWN = 1; + var BUTTON_UP = 2; + var CONNECT = 3; + var DISCONNECT = 4; +} + +@:keep /*private*/ class JoystickEventInfo +{ + public var id:Int; + public var index:Int; + public var type:JoystickEventType; + public var eventValue:Int; + public var x:Float; + public var y:Float; + + public function new(type:JoystickEventType = null, id:Int = 0, index:Int = 0, value:Int = 0, x:Float = 0, y:Float = 0) + { + this.type = type; + this.id = id; + this.index = index; + this.eventValue = value; + this.x = x; + this.y = y; + } + + public function clone():JoystickEventInfo + { + return new JoystickEventInfo(type, id, index, eventValue, x, y); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract JoystickEventType(Int) +{ + var AXIS_MOVE = 0; + var HAT_MOVE = 1; + var TRACKBALL_MOVE = 2; + var BUTTON_DOWN = 3; + var BUTTON_UP = 4; + var CONNECT = 5; + var DISCONNECT = 6; +} + +@:keep /*private*/ class KeyEventInfo +{ + public var keyCode: Float; + public var modifier:Int; + public var type:KeyEventType; + public var windowID:Int; + + public function new(type:KeyEventType = null, windowID:Int = 0, keyCode: Float = 0, modifier:Int = 0) + { + this.type = type; + this.windowID = windowID; + this.keyCode = keyCode; + this.modifier = modifier; + } + + public function clone():KeyEventInfo + { + return new KeyEventInfo(type, windowID, keyCode, modifier); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract KeyEventType(Int) +{ + var KEY_DOWN = 0; + var KEY_UP = 1; +} + +@:keep /*private*/ class MouseEventInfo +{ + public var button:Int; + public var movementX:Float; + public var movementY:Float; + public var type:MouseEventType; + public var windowID:Int; + public var x:Float; + public var y:Float; + public var clickCount:Int; + + public function new(type:MouseEventType = null, windowID:Int = 0, x:Float = 0, y:Float = 0, button:Int = 0, movementX:Float = 0, movementY:Float = 0, clickCount:Int = 0) + { + this.type = type; + this.windowID = 0; + this.x = x; + this.y = y; + this.button = button; + this.movementX = movementX; + this.movementY = movementY; + this.clickCount = clickCount; + } + + public function clone():MouseEventInfo + { + return new MouseEventInfo(type, windowID, x, y, button, movementX, movementY, clickCount); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract MouseEventType(Int) +{ + var MOUSE_DOWN = 0; + var MOUSE_UP = 1; + var MOUSE_MOVE = 2; + var MOUSE_WHEEL = 3; +} + +@:keep /*private*/ class RenderEventInfo +{ + public var type:RenderEventType; + + public function new(type:RenderEventType = null) + { + this.type = type; + } + + public function clone():RenderEventInfo + { + return new RenderEventInfo(type); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract RenderEventType(Int) +{ + var RENDER = 0; + var RENDER_CONTEXT_LOST = 1; + var RENDER_CONTEXT_RESTORED = 2; +} + +@:keep /*private*/ class SensorEventInfo +{ + public var id:Int; + public var x:Float; + public var y:Float; + public var z:Float; + public var type:SensorEventType; + + public function new(type:SensorEventType = null, id:Int = 0, x:Float = 0, y:Float = 0, z:Float = 0) + { + this.type = type; + this.id = id; + this.x = x; + this.y = y; + this.z = z; + } + + public function clone():SensorEventInfo + { + return new SensorEventInfo(type, id, x, y, z); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract SensorEventType(Int) +{ + var ACCELEROMETER = 0; +} + +@:keep /*private*/ class TextEventInfo +{ + public var id:Int; + public var length:Int; + public var start:Int; + public var text:#if hl hl.Bytes #else String #end; + public var type:TextEventType; + public var windowID:Int; + + public function new(type:TextEventType = null, windowID:Int = 0, text = null, start:Int = 0, length:Int = 0) + { + this.type = type; + this.windowID = windowID; + this.text = text; + this.start = start; + this.length = length; + } + + public function clone():TextEventInfo + { + return new TextEventInfo(type, windowID, text, start, length); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract TextEventType(Int) +{ + var TEXT_INPUT = 0; + var TEXT_EDIT = 1; +} + +@:keep /*private*/ class TouchEventInfo +{ + public var device:Int; + public var dx:Float; + public var dy:Float; + public var id:Int; + public var pressure:Float; + public var type:TouchEventType; + public var x:Float; + public var y:Float; + + public function new(type:TouchEventType = null, x:Float = 0, y:Float = 0, id:Int = 0, dx:Float = 0, dy:Float = 0, pressure:Float = 0, device:Int = 0) + { + this.type = type; + this.x = x; + this.y = y; + this.id = id; + this.dx = dx; + this.dy = dy; + this.pressure = pressure; + this.device = device; + } + + public function clone():TouchEventInfo + { + return new TouchEventInfo(type, x, y, id, dx, dy, pressure, device); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract TouchEventType(Int) +{ + var TOUCH_START = 0; + var TOUCH_END = 1; + var TOUCH_MOVE = 2; +} + +@:keep /*private*/ class WindowEventInfo +{ + public var height:Int; + public var type:WindowEventType; + public var width:Int; + public var windowID:Int; + public var x:Int; + public var y:Int; + + public function new(type:WindowEventType = null, windowID:Int = 0, width:Int = 0, height:Int = 0, x:Int = 0, y:Int = 0) + { + this.type = type; + this.windowID = windowID; + this.width = width; + this.height = height; + this.x = x; + this.y = y; + } + + public function clone():WindowEventInfo + { + return new WindowEventInfo(type, windowID, width, height, x, y); + } +} + +#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract WindowEventType(Int) +{ + var WINDOW_ACTIVATE = 0; + var WINDOW_CLOSE = 1; + var WINDOW_DEACTIVATE = 2; + var WINDOW_ENTER = 3; + var WINDOW_EXPOSE = 4; + var WINDOW_FOCUS_IN = 5; + var WINDOW_FOCUS_OUT = 6; + var WINDOW_LEAVE = 7; + var WINDOW_MAXIMIZE = 8; + var WINDOW_MINIMIZE = 9; + var WINDOW_MOVE = 10; + var WINDOW_RESIZE = 11; + var WINDOW_RESTORE = 12; + var WINDOW_SHOW = 13; + var WINDOW_HIDE = 14; +} diff --git a/source/lime/_internal/backend/native/NativeAudioSource.hx b/source/lime/_internal/backend/native/NativeAudioSource.hx index e7cbd821..478ed150 100644 --- a/source/lime/_internal/backend/native/NativeAudioSource.hx +++ b/source/lime/_internal/backend/native/NativeAudioSource.hx @@ -1,584 +1,492 @@ +// ALL REWRITTEN FROM SCRATCH!!!! -raltyro package lime._internal.backend.native; -import haxe.Int64; import haxe.Timer; -import lime.math.Vector4; +import haxe.Int64; + import lime.media.openal.AL; import lime.media.openal.ALBuffer; import lime.media.openal.ALSource; + +#if lime_vorbis +import lime.media.vorbis.Vorbis; import lime.media.vorbis.VorbisFile; -import lime.media.AudioManager; +#end + +import lime.math.Vector4; +import lime.media.AudioBuffer; import lime.media.AudioSource; -import lime.utils.UInt8Array; +import lime.utils.ArrayBufferView; #if !lime_debug @:fileXml('tags="haxe,release"') @:noDebug #end @:access(lime.media.AudioBuffer) -class NativeAudioSource -{ - public static var initBuffers:Array<NativeAudioSource> = []; +@:access(lime.utils.ArrayBufferView) +class NativeAudioSource { + private static var STREAM_BUFFER_SAMPLES:Int = 0x1000; + private static var STREAM_MAX_BUFFERS:Int = 32; + private static var STREAM_TIMER_FREQUENCY:Int = 100; + private static var STREAM_BUFFER_FREQUENCY:Int = 6; - private static var STREAM_BUFFER_SIZE = 48000; - #if (native_audio_buffers && !macro) - private static var STREAM_NUM_BUFFERS = Std.parseInt(haxe.macro.Compiler.getDefine("native_audio_buffers")); - #else - private static var STREAM_NUM_BUFFERS = 3; - #end - private static var STREAM_TIMER_FREQUENCY = 100; - - private var buffers:Array<ALBuffer>; - private var bufferTimeBlocks:Array<Float>; - private var completed:Bool; - private var dataLength:Int; - private var format:Int; - private var handle:ALSource; - private var length:Null<Float>; - private var loops:Int; private var parent:AudioSource; + private var disposed:Bool; + private var streamed:Bool; private var playing:Bool; - private var position:Vector4; - private var samples:Float; - private var stream:Bool; - private var streamTimer:Timer; + private var completed:Bool; + + private var handle:ALSource; + private var buffers:Array<ALBuffer>; + private var unusedBuffers:Array<ALBuffer>; private var timer:Timer; + private var format:Int; + private var samples:Float; - private var pcmTotal:Int64; - private var sampleRate:Int; + private var streamTimer:Timer; + private var bufferDatas:Array<ArrayBufferView>; + private var bufferTimes:Array<Float>; + private var bufferSizes:Array<Int>; + private var bufferSize:Int; + private var requestBuffers:Int; + private var queuedBuffers:Int; + private var toLoop:Int; + private var streamEnded:Bool; + private var dataLength:Float; - var disposed:Bool = false; + private var position:Vector4 = new Vector4(); + private var length:Null<Float>; + private var loopTime:Null<Float>; + private var loops:Int; - public function new(parent:AudioSource) - { - this.parent = parent; + public function new(parent:AudioSource) this.parent = parent; - position = new Vector4(); - } - - public function dispose():Void - { - if (handle != null) - { - stop(); - AL.sourcei(handle, AL.BUFFER, null); - AL.deleteSource(handle); - if (buffers != null) - { - for (buffer in buffers) - { - AL.deleteBuffer(buffer); - } - buffers = null; - } - handle = null; - } + public function dispose() { disposed = true; - initBuffers.remove(this); + stop(); + + if (handle != null) { + AL.sourcei(handle, AL.BUFFER, AL.NONE); + AL.deleteSource(handle); + } + handle = null; + + if (buffers != null) AL.deleteBuffers(buffers); + buffers = null; + bufferDatas = null; + bufferTimes = null; + bufferSizes = null; + unusedBuffers = null; } - public function init():Void - { - dataLength = 0; - format = 0; + public function init() { + if (handle != null) return; - if (parent.buffer.channels == 1) - { - if (parent.buffer.bitsPerSample == 8) - { - format = AL.FORMAT_MONO8; - } - else if (parent.buffer.bitsPerSample == 16) - { - format = AL.FORMAT_MONO16; - } + if (disposed = (handle = AL.createSource()) == null) return; + AL.sourcef(handle, AL.MAX_GAIN, 10); + + var buffer = parent.buffer; + var channels = buffer.channels, bitsPerSample = buffer.bitsPerSample; + + // Default is just AL.FORMAT_MONO8 if it doesn't match any to avoid yo ears getting blasted + var isCreativeXFi = AL.getString(AL.RENDERER) == "X-Fi"; + if (channels > 2 && (isCreativeXFi || AL.isExtensionPresent("AL_EXT_MCFORMATS"))) { // https://github.com/openalext/openalext/blob/master/AL_EXT_MCFORMATS.txt + if (channels == 3) format = bitsPerSample == 32 ? 0x1209 : (bitsPerSample == 16 ? 0x1208 : 0x1207); + else if (channels == 4) format = bitsPerSample == 32 ? 0x1206 : (bitsPerSample == 16 ? 0x1205 : 0x1204); + else if (channels == 6) format = bitsPerSample == 32 ? 0x120C : (bitsPerSample == 16 ? 0x120B : 0x120A); + else if (channels == 7) format = bitsPerSample == 32 ? 0x120F : (bitsPerSample == 16 ? 0x120E : 0x120D); + else if (channels == 8) format = bitsPerSample == 32 ? 0x1212 : (bitsPerSample == 16 ? 0x1211 : 0x1210); } - else if (parent.buffer.channels == 2) - { - if (parent.buffer.bitsPerSample == 8) - { - format = AL.FORMAT_STEREO8; - } - else if (parent.buffer.bitsPerSample == 16) - { - format = AL.FORMAT_STEREO16; - } + // The 32 bit for STEREO and MONO are undocumented? https://github.com/kcat/openal-soft/issues/934, though it only works in X-Fi OpenAL?? + else if (bitsPerSample == 32 && isCreativeXFi) format = channels == 2 ? 0x1203 : 0x1202; + else if (channels == 2) format = bitsPerSample == 16 ? AL.FORMAT_STEREO16 : AL.FORMAT_STEREO8; + else format = bitsPerSample == 16 ? AL.FORMAT_MONO16 : AL.FORMAT_MONO8; + + if (buffer.data != null) { + streamed = false; + samples = (dataLength = (buffer.data.length >> 0)) / buffer.channels / (buffer.bitsPerSample >> 3); + + if ((buffer.__srcBuffer == null || (AL.getBufferi(buffer.__srcBuffer, AL.SIZE) >> 0) != dataLength) && (buffer.__srcBuffer = AL.createBuffer()) != null) + AL.bufferData(buffer.__srcBuffer, format, buffer.data, buffer.data.length, buffer.sampleRate); + + AL.sourcei(handle, AL.BUFFER, buffer.__srcBuffer); } + #if lime_vorbis + else if (buffer.__srcVorbisFile != null) { + streamed = true; + dataLength = (samples = getFloat(buffer.__srcVorbisFile.pcmTotal())) * buffer.channels * (buffer.bitsPerSample >> 3); - if (parent.buffer.__srcVorbisFile != null) - { - stream = true; - - var vorbisFile = parent.buffer.__srcVorbisFile; - pcmTotal = vorbisFile.pcmTotal(); - var info = vorbisFile.info(); - sampleRate = info.rate; - var _dataLength = pcmTotal * Int64.ofInt(parent.buffer.channels) * (Int64.ofInt(parent.buffer.bitsPerSample) / Int64.ofInt(8)); - dataLength = Int64.toInt(_dataLength); - - buffers = new Array(); - bufferTimeBlocks = new Array(); - - for (i in 0...STREAM_NUM_BUFFERS) - { - buffers.push(AL.createBuffer()); - bufferTimeBlocks.push(0); - } - - handle = AL.createSource(); - - samples = Int64.toInt(pcmTotal); + var constructor = buffer.bitsPerSample == 32 ? Int32 : buffer.bitsPerSample == 16 ? Int16 : Int8; + bufferSize = STREAM_BUFFER_SAMPLES * buffer.channels * (constructor == Int8 ? buffer.bitsPerSample >> 3 : 1); + buffers = AL.genBuffers(STREAM_MAX_BUFFERS); + bufferDatas = [for (i in 0...STREAM_MAX_BUFFERS) new ArrayBufferView(bufferSize, constructor)]; + bufferTimes = [for (i in 0...STREAM_MAX_BUFFERS) 0]; + bufferSizes = [for (i in 0...STREAM_MAX_BUFFERS) 0]; + unusedBuffers = []; + bufferSize *= constructor == Int8 ? 1 : buffer.bitsPerSample >> 3; } - else - { - if (parent.buffer.__srcBuffer == null) - { - parent.buffer.__srcBuffer = AL.createBuffer(); + #end - if (parent.buffer.__srcBuffer != null) - { - AL.bufferData(parent.buffer.__srcBuffer, format, parent.buffer.data, parent.buffer.data.length, parent.buffer.sampleRate); - } - } - - dataLength = parent.buffer.data.length; - - handle = AL.createSource(); - - if (handle != null) - { - AL.sourcei(handle, AL.BUFFER, parent.buffer.__srcBuffer); - } - - samples = Int64.toInt((Int64.make(0, dataLength) * Int64.ofInt(8)) / Int64.ofInt(parent.buffer.channels * parent.buffer.bitsPerSample)); + if (dataLength == 0) { + trace('NativeAudioSource Bug! dataLength is 0'); + dispose(); } - - initBuffers.push(this); } - public function play():Void - { - if (disposed) { - trace("TRIED TO PLAY DISPOSED?? WTFFF"); - trace(haxe.CallStack.toString(haxe.CallStack.callStack())); - } - if (playing || handle == null) - { - return; - } + public function play() { + if (playing || disposed) return; playing = true; - - if (stream) - { - setCurrentTime(getCurrentTime()); - - streamTimer = new Timer(STREAM_TIMER_FREQUENCY); - streamTimer.run = streamTimer_onRun; - } - else - { - var time = completed ? 0 : getCurrentTime(); - - setCurrentTime(time); - } + if (completed) setCurrentTime(0); + else setCurrentTime(getCurrentTime()); } - public function pause():Void - { + public function pause() { + if (!disposed) AL.sourcePause(handle); + playing = false; - - if (handle == null) return; - AL.sourcePause(handle); - - if (streamTimer != null) - { - streamTimer.stop(); - } - - if (timer != null) - { - timer.stop(); - } + stopStreamTimer(); + stopTimer(); } - private function readVorbisFileBuffer(vorbisFile:VorbisFile, length:Int):UInt8Array - { - #if lime_vorbis - var buffer = new UInt8Array(length); - var read = 0, total = 0, readMax; - - for (i in 0...STREAM_NUM_BUFFERS-1) - { - bufferTimeBlocks[i] = bufferTimeBlocks[i + 1]; - } - bufferTimeBlocks[STREAM_NUM_BUFFERS-1] = vorbisFile.timeTell(); - - while (total < length) - { - readMax = 4096; - - if (readMax > length - total) - { - readMax = length - total; - } - - read = vorbisFile.read(buffer.buffer, total, readMax); - - if (read > 0) - { - total += read; - } - else - { - break; - } - } - - return buffer; - #else - return null; - #end - } - - private function refillBuffers(buffers:Array<ALBuffer> = null):Void - { - #if lime_vorbis - var vorbisFile = null; - var position = 0; - - if (buffers == null) - { - var buffersProcessed:Int = AL.getSourcei(handle, AL.BUFFERS_PROCESSED); - - if (buffersProcessed > 0) - { - vorbisFile = parent.buffer.__srcVorbisFile; - position = Int64.toInt(vorbisFile.pcmTell()); - - if (position < dataLength) - { - buffers = AL.sourceUnqueueBuffers(handle, buffersProcessed); - } - } - } - - if (buffers != null) - { - if (vorbisFile == null) - { - vorbisFile = parent.buffer.__srcVorbisFile; - position = Int64.toInt(vorbisFile.pcmTell()); - } - - var numBuffers = 0; - var data; - - for (buffer in buffers) - { - if (dataLength - position >= STREAM_BUFFER_SIZE) - { - data = readVorbisFileBuffer(vorbisFile, STREAM_BUFFER_SIZE); - AL.bufferData(buffer, format, data, data.length, parent.buffer.sampleRate); - position += STREAM_BUFFER_SIZE; - numBuffers++; - } - else if (position < dataLength) - { - data = readVorbisFileBuffer(vorbisFile, dataLength - position); - AL.bufferData(buffer, format, data, data.length, parent.buffer.sampleRate); - numBuffers++; - break; - } - } - - AL.sourceQueueBuffers(handle, numBuffers, buffers); - - // OpenAL can unexpectedly stop playback if the buffers run out - // of data, which typically happens if an operation (such as - // resizing a window) freezes the main thread. - // If AL is supposed to be playing but isn't, restart it here. - if (playing && handle != null && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) - { - AL.sourcePlay(handle); - } - } - #end - } - - public function stop():Void - { - if (playing && handle != null && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING) - { + public function stop() { + if (!disposed) { AL.sourceStop(handle); + toLoop = 0; } playing = false; - - if (streamTimer != null) - { - streamTimer.stop(); - } - - if (timer != null) - { - timer.stop(); - } - - setCurrentTime(0); + stopStreamTimer(); + stopTimer(); } - // Event Handlers - private function streamTimer_onRun():Void - { - refillBuffers(); - } - - private function timer_onRun():Void - { - if (loops > 0) - { - playing = false; - loops--; - setCurrentTime(0); - play(); - return; - } - else - { - stop(); - } - + private function complete() { completed = true; + stop(); parent.onComplete.dispatch(); } - // Get & Set Methods - public function getCurrentTime():Float - { - if (completed) - { - return getLength(); - } - else if (handle != null) - { - if (stream) - { - var time = (Std.int(bufferTimeBlocks[0] * 1000) + Std.int(AL.getSourcef(handle, AL.SEC_OFFSET) * 1000)) - parent.offset; - if (time < 0) return 0; - return time; - } - else - { - var offset:Int = AL.getSourcei(handle, AL.BYTE_OFFSET); - var ratio = (offset / dataLength); - var totalSeconds = samples / parent.buffer.sampleRate; - - var time = totalSeconds * ratio * 1000 - parent.offset; - - // var time = Std.int (AL.getSourcef (handle, AL.SEC_OFFSET) * 1000) - parent.offset; - if (time < 0) return 0; - return time; - } - } - + // Streaming, atleast for vorbis for now. + // just incase if we support more than 1 in the future?? which i doubt + private function streamTell():Float { + #if lime_vorbis + return parent.buffer.__srcVorbisFile.timeTell(); + #else return 0; + #end } - public function setCurrentTime(value:Float):Float - { - // `setCurrentTime()` has side effects and is never safe to skip. - /* if (value == getCurrentTime()) - { - return value; - } */ + private function streamSeek(samples:Int64) { + #if lime_vorbis + // There's apparently a bug in libvorbis <= 1.3.4, hhttps://github.com/xiph/vorbis/blob/master/CHANGES#L39 + // But it seems to also happen in this lime libvorbis version which is 1.3.7...? -ralty + if (samples < 4) parent.buffer.__srcVorbisFile.rawSeek(0); else parent.buffer.__srcVorbisFile.pcmSeek(samples); + #end + } - if (handle != null) - { - if (stream) - { - AL.sourceStop(handle); + private function readToBufferData(data:ArrayBufferView):Int { + #if lime_vorbis + var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN, wordSize = parent.buffer.bitsPerSample >> 3; + var size = dataLength - (streamTell() * parent.buffer.sampleRate * parent.buffer.channels * wordSize); + var n:Int = size < bufferSize ? Math.floor(size) : bufferSize; + n -= n % (parent.buffer.channels * wordSize); - parent.buffer.__srcVorbisFile.timeSeek((value + parent.offset) / 1000); - AL.sourceUnqueueBuffers(handle, STREAM_NUM_BUFFERS); - refillBuffers(buffers); + var total = 0, result = 0, wasEOF = false; + while (total < bufferSize) { + result = n > 0 ? parent.buffer.__srcVorbisFile.read(data.buffer, total, n, isBigEndian, wordSize, true) : 0; - if (playing) AL.sourcePlay(handle); + if (result == Vorbis.HOLE) continue; + else if (result <= Vorbis.EREAD) break; + else if (result == 0) { + if (wasEOF || (streamEnded = loops <= toLoop)) break; + else { + wasEOF = true; + toLoop++; + + var samples = getSamples(loopTime != null ? loopTime + parent.offset : parent.offset); + streamSeek(samples); + if ((size = dataLength - (getFloat(samples) * parent.buffer.channels * wordSize)) < (n = bufferSize - total)) n = Math.floor(size); + n -= n % (parent.buffer.channels * wordSize); + } } - else if (parent.buffer != null) - { - AL.sourceRewind(handle); - - // AL.sourcef (handle, AL.SEC_OFFSET, (value + parent.offset) / 1000); - - var secondOffset = (value + parent.offset) / 1000; - var totalSeconds = samples / parent.buffer.sampleRate; - - if (secondOffset < 0) secondOffset = 0; - else if (secondOffset > totalSeconds) secondOffset = totalSeconds; - - var ratio = (secondOffset / totalSeconds); - var totalOffset = Std.int(dataLength * ratio); - - AL.sourcei(handle, AL.BYTE_OFFSET, totalOffset); - if (playing) AL.sourcePlay(handle); + else { + total += result; + n -= result; + wasEOF = false; } } - if (playing) - { - if (timer != null) - { - timer.stop(); + if (total < bufferSize) data.buffer.fill(total, n, 0); + if (result < 0) { + trace('NativeAudioSource Streaming Bug! reading result is $result, streamEnded: $streamEnded, total: $total, n: $n'); + return result; + } + return total; + #else + return 0; + #end + } + + private function fillBuffer(buffer:ALBuffer):Int { + var i = STREAM_MAX_BUFFERS - requestBuffers; + var data = bufferDatas[i], time = streamTell(); + var decoded = readToBufferData(data); + + if (decoded > 0) { + AL.bufferData(buffer, format, data, decoded, parent.buffer.sampleRate); + + var n = STREAM_MAX_BUFFERS - 1; + while (i < n) { + bufferDatas[i] = bufferDatas[i + 1]; + bufferTimes[i] = bufferTimes[i + 1]; + bufferSizes[i] = bufferSizes[++i]; + } + queuedBuffers = requestBuffers; + bufferDatas[n] = data; + bufferTimes[n] = time; + bufferSizes[n] = decoded; + } + + return decoded; + } + + private function streamRun() { + if (parent == null || parent.buffer == null || handle == null) return dispose(); + if (!playing || disposed) return stopStreamTimer(); + + try { + var processed = AL.getSourcei(handle, AL.BUFFERS_PROCESSED), n = STREAM_BUFFER_FREQUENCY, buffer; + while (processed-- > 0) { + buffer = AL.sourceUnqueueBuffer(handle); + if (!streamEnded && --n > 0 && fillBuffer(buffer) > 0) AL.sourceQueueBuffer(handle, buffer); + else { + queuedBuffers = --requestBuffers; + unusedBuffers.push(buffer); + } } - var timeRemaining = (getLength() - value) / getPitch(); + if (!streamEnded) { + if (unusedBuffers.length != 0) { + requestBuffers++; + if (fillBuffer(buffer = unusedBuffers.pop()) > 0) { + queuedBuffers = requestBuffers; + AL.sourceQueueBuffer(handle, buffer); + } + else { + requestBuffers--; + unusedBuffers.push(buffer); + } + } + else if (queuedBuffers < STREAM_MAX_BUFFERS) { + if (fillBuffer(buffer = buffers[requestBuffers++]) > 0) AL.sourceQueueBuffer(handle, buffer); + else requestBuffers--; + } + } + } + catch(e) trace('NativeAudioSource Bug! in streamRun $e'); - if (timeRemaining > 0) - { - completed = false; - timer = new Timer(timeRemaining); - timer.run = timer_onRun; + if (AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) { + AL.sourcePlay(handle); + resetTimer((getLength() - getCurrentTime()) / getPitch()); + } + } + + // Timers + inline function stopStreamTimer() { + if (streamTimer != null) streamTimer.stop(); + streamTimer = null; + } + + private function resetStreamTimer() { + stopStreamTimer(); + if (streamed) (streamTimer = new Timer(STREAM_TIMER_FREQUENCY)).run = streamRun; + } + + inline function stopTimer() { + if (timer != null) timer.stop(); + timer = null; + } + + private function resetTimer(timeRemaining:Float) { + stopTimer(); + (timer = new Timer(timeRemaining)).run = timer_onRun; + } + + private function timer_onRun() { + var timeRemaining = (getLength() - getCurrentTime()) / getPitch(); + if (timeRemaining > 100 && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING && (!streamed || !streamEnded)) { + resetTimer(timeRemaining); + return; + } + + if (loops == 0) { + complete(); + return; + } + + var start = loopTime != null ? loopTime : 0; + if (toLoop <= 0) { + setLoops(--loops); + if (start > 0 || AL.getSourcei(handle, AL.SOURCE_STATE) != AL.PLAYING) setCurrentTime(start); + else resetTimer((getLength() - start) / getPitch()); + } + else { + if ((loops -= toLoop) < 0) loops = 0; + toLoop = 0; + resetTimer((getLength() - start) / getPitch()); + } + + parent.onLoop.dispatch(); + } + + // Get & Set Methods + public function getCurrentTime():Float { + if (disposed) return 0; + else if (completed) return getLength(); + + var time = AL.getSourcef(handle, AL.SAMPLE_OFFSET) / parent.buffer.sampleRate; + if (streamed) { + if (playing && streamEnded && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) { + complete(); + return getLength(); } else - { - playing = false; - completed = true; + time += bufferTimes[STREAM_MAX_BUFFERS - queuedBuffers]; + } + time *= 1000; + + var length = if (length == null) getRealLength(); else length; + if (loops > 0 && time > length) { + var start = loopTime != null ? Math.max(0, loopTime) : 0; + return ((time - start) % (length - start)) + start - parent.offset; + } + + return time - parent.offset; + } + + public function setCurrentTime(value:Float):Float { + if (disposed) return value; + + if (streamed) AL.sourceStop(handle); + else AL.sourcei(handle, AL.SAMPLE_OFFSET, getSamples(value + parent.offset)); + + if (playing) { + var timeRemaining = (getLength() - value) / getPitch(); + if (timeRemaining < 8) complete(); + else { + completed = streamEnded = false; + if (streamed) { + AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED)); + + unusedBuffers.resize(0); + streamSeek(getSamples(value + parent.offset)); + + requestBuffers = queuedBuffers = STREAM_BUFFER_FREQUENCY; + for (i in 0...queuedBuffers) { + if (!streamEnded && fillBuffer(buffers[i]) > 0) AL.sourceQueueBuffer(handle, buffers[i]); + else queuedBuffers = --requestBuffers; + } + + if (!streamEnded) resetStreamTimer(); + } + + resetTimer(timeRemaining); + AL.sourcePlay(handle); } } + if (!playing && streamed) bufferTimes[STREAM_MAX_BUFFERS - (requestBuffers = queuedBuffers = 1)] = value / 1000; + return value; } - public function getGain():Float - { - if (handle != null) - { - return AL.getSourcef(handle, AL.GAIN); - } - else - { - return 1; - } - } + inline private function getRealLength():Float return samples / parent.buffer.sampleRate * 1000; + public function getLength():Null<Float> + return if (disposed) 0; else if (length == null) getRealLength() - parent.offset; else length - parent.offset; - public function setGain(value:Float):Float - { - if (handle != null) - { - AL.sourcef(handle, AL.GAIN, value); - } + public function setLength(value:Null<Float>):Null<Float> { + if (value == length || disposed) return length = value; + var buffer = parent.buffer, wordSize = buffer.bitsPerSample >> 3; + if ((length = value) == null) dataLength = streamed ? samples * buffer.channels * wordSize : getFloat(Int64.make(0, buffer.data.length)); + else dataLength = Math.ffloor(Math.max(0, Math.min(value, getRealLength())) / 1000 * buffer.sampleRate) * buffer.channels * wordSize; + + if (playing) { + if (streamed && (streamEnded || toLoop > 0)) setCurrentTime(getCurrentTime()); + else { + var timeRemaining = ((getLength() - parent.offset) - getCurrentTime()) / getPitch(); + if (timeRemaining > 0) resetTimer(timeRemaining); + } + } return value; } - public function getLength():Float - { - if (length != null) - { - return length; + public function getPitch():Float { + return if (disposed) 1; + else AL.getSourcef(handle, AL.PITCH); + } + + public function setPitch(value:Float):Float { + if (disposed || value == AL.getSourcef(handle, AL.PITCH)) return value; + if (playing) { + var timeRemaining = (getLength() - getCurrentTime()) / value; + if (timeRemaining > 0) resetTimer(timeRemaining); } - - return (samples / parent.buffer.sampleRate * 1000) - parent.offset; + AL.sourcef(handle, AL.PITCH, value); + return value; } - public function setLength(value:Float):Float - { - if (playing && length != value) - { - if (timer != null) - { - timer.stop(); - } - - var timeRemaining = (value - getCurrentTime()) / getPitch(); - - if (timeRemaining > 0) - { - timer = new Timer(timeRemaining); - timer.run = timer_onRun; - } - } - - return length = value; + public function getGain():Float { + if (disposed) return 1; + return AL.getSourcef(handle, AL.GAIN); } - public function getLoops():Int - { - return loops; + public function setGain(value:Float):Float { + if (!disposed) AL.sourcef(handle, AL.GAIN, value); + return value; } - public function setLoops(value:Int):Int - { + inline public function getLoops():Int return loops; + + inline public function setLoops(value:Int):Int { + if (!streamed && !disposed) AL.sourcei(handle, AL.LOOPING, (loopTime <= 0 && value > 0) ? AL.TRUE : AL.FALSE); + else if (loops == 0 && value > 0) setCurrentTime(getCurrentTime()); + if (value < 0) return loops = 0; return loops = value; } - public function getPitch():Float - { - if (handle != null) - { - return AL.getSourcef(handle, AL.PITCH); - } - else - { - return 1; - } + inline public function getLoopTime():Float return loopTime; + + inline public function setLoopTime(value:Float):Float { + if (!streamed && !disposed) AL.sourcei(handle, AL.LOOPING, (value <= 0 && loops > 0) ? AL.TRUE : AL.FALSE); + return loopTime = value; } - public function setPitch(value:Float):Float - { - if (playing && value != getPitch()) - { - if (timer != null) - { - timer.stop(); - } - - var timeRemaining = (getLength() - getCurrentTime()) / value; - - if (timeRemaining > 0) - { - timer = new Timer(timeRemaining); - timer.run = timer_onRun; - } + public function getLatency():Float { + /*#if (lime >= "8.3.0") + if (AL.isExtensionPresent("AL_SOFT_source_latency")) { + final offsets = AL.getSourcedvSOFT(handle, AL.SEC_OFFSET_LATENCY_SOFT, 2); + if (offsets != null) return offsets[1] * 1000; } - - if (handle != null) - { - AL.sourcef(handle, AL.PITCH, value); - } - - return value; + #end*/ + return 0; } - public function getPosition():Vector4 - { - if (handle != null) - { - #if !emscripten - var value = AL.getSource3f(handle, AL.POSITION); - position.x = value[0]; - position.y = value[1]; - position.z = value[2]; - #end - } + public function getPosition():Vector4 return position; - return position; - } - - public function setPosition(value:Vector4):Vector4 - { + public function setPosition(value:Vector4):Vector4 { position.x = value.x; position.y = value.y; position.z = value.z; position.w = value.w; - if (handle != null) - { + if (!disposed) { AL.distanceModel(AL.NONE); AL.source3f(handle, AL.POSITION, position.x, position.y, position.z); } - return position; } -} + + inline private function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >> 0); + inline private function getSamples(ms:Float):Int64 return Int64.fromFloat(Math.max(0, Math.min(ms / 1000 * parent.buffer.sampleRate, samples))); +} \ No newline at end of file diff --git a/source/lime/media/AudioBuffer.hx b/source/lime/media/AudioBuffer.hx new file mode 100644 index 00000000..40ebfbec --- /dev/null +++ b/source/lime/media/AudioBuffer.hx @@ -0,0 +1,491 @@ +package lime.media; + +import haxe.io.Bytes; +import haxe.io.Path; +import lime._internal.backend.native.NativeCFFI; +import lime._internal.format.Base64; +import lime.app.Future; +import lime.app.Promise; +import lime.media.openal.AL; +import lime.media.openal.ALBuffer; +import lime.media.vorbis.VorbisFile; +import lime.net.HTTPRequest; +import lime.utils.Log; +import lime.utils.UInt8Array; +#if lime_howlerjs +import lime.media.howlerjs.Howl; +#end +#if (js && html5) +import js.html.Audio; +#elseif flash +import flash.media.Sound; +import flash.net.URLRequest; +#end + +@:access(lime._internal.backend.native.NativeCFFI) +@:access(lime.utils.Assets) +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + The `AudioBuffer` class represents a buffer of audio data that can be played back using an `AudioSource`. + It supports a variety of audio formats and platforms, providing a consistent API for loading and managing audio data. + + Depending on the platform, the audio backend may differ, but the class provides a unified interface for accessing + audio data, whether it's stored in memory, loaded from a file, or streamed. + + @see lime.media.AudioSource +**/ +class AudioBuffer +{ + /** + The number of bits per sample in the audio data. + **/ + public var bitsPerSample:Int; + + /** + The number of audio channels (e.g., 1 for mono, 2 for stereo). + **/ + public var channels:Int; + + /** + The raw audio data stored as a `UInt8Array`. + **/ + public var data:UInt8Array; + + /** + The sample rate of the audio data, in Hz. + **/ + public var sampleRate:Int; + + /** + The source of the audio data. This can be an `Audio`, `Sound`, `Howl`, or other platform-specific object. + **/ + public var src(get, set):Dynamic; + + @:noCompletion private var __srcAudio:#if (js && html5) Audio #else Dynamic #end; + @:noCompletion private var __srcBuffer:#if lime_cffi ALBuffer #else Dynamic #end; + @:noCompletion private var __srcCustom:Dynamic; + @:noCompletion private var __srcHowl:#if lime_howlerjs Howl #else Dynamic #end; + @:noCompletion private var __srcSound:#if flash Sound #else Dynamic #end; + @:noCompletion private var __srcVorbisFile:#if lime_vorbis VorbisFile #else Dynamic #end; + + #if commonjs + private static function __init__() + { + var p = untyped AudioBuffer.prototype; + untyped Object.defineProperties(p, + { + "src": {get: p.get_src, set: p.set_src} + }); + } + #end + + /** + Creates a new, empty `AudioBuffer` instance. + **/ + public function new() {} + + /** + Disposes of the resources used by this `AudioBuffer`, such as unloading any associated audio data. + **/ + public function dispose():Void + { + #if (js && html5 && lime_howlerjs) + if (__srcHowl != null) __srcHowl.unload(); + __srcHowl = null; + #end + #if lime_cffi + if (__srcBuffer != null) AL.deleteBuffer(__srcBuffer); + __srcBuffer = null; + #end + #if lime_vorbis + if (__srcVorbisFile != null) __srcVorbisFile.clear(); + __srcVorbisFile = null; + #end + } + + /** + Creates an `AudioBuffer` from a Base64-encoded string. + + @param base64String The Base64-encoded audio data. + @return An `AudioBuffer` instance with the decoded audio data. + **/ + public static function fromBase64(base64String:String):AudioBuffer + { + if (base64String == null) return null; + + #if (js && html5 && lime_howlerjs) + // if base64String doesn't contain codec data, add it. + if (base64String.indexOf(",") == -1) + { + base64String = "data:" + __getCodec(Base64.decode(base64String)) + ";base64," + base64String; + } + + var audioBuffer = new AudioBuffer(); + audioBuffer.src = new Howl({src: [base64String], preload: false}); + return audioBuffer; + #elseif (lime_cffi && !macro) + #if !cs + // if base64String contains codec data, strip it then decode it. + var base64StringSplit = base64String.split(","); + var base64StringNoEncoding = base64StringSplit[base64StringSplit.length - 1]; + var bytes:Bytes = Base64.decode(base64StringNoEncoding); + var audioBuffer = new AudioBuffer(); + audioBuffer.data = new UInt8Array(Bytes.alloc(0)); + + return NativeCFFI.lime_audio_load_bytes(bytes, audioBuffer); + #else + // if base64String contains codec data, strip it then decode it. + var base64StringSplit = base64String.split(","); + var base64StringNoEncoding = base64StringSplit[base64StringSplit.length - 1]; + var bytes:Bytes = Base64.decode(base64StringNoEncoding); + var data:Dynamic = NativeCFFI.lime_audio_load_bytes(bytes, null); + + if (data != null) + { + var audioBuffer = new AudioBuffer(); + audioBuffer.bitsPerSample = data.bitsPerSample; + audioBuffer.channels = data.channels; + audioBuffer.data = new UInt8Array(@:privateAccess new Bytes(data.data.length, data.data.b)); + audioBuffer.sampleRate = data.sampleRate; + return audioBuffer; + } + #end + #end + + return null; + } + + /** + Creates an `AudioBuffer` from a `Bytes` object. + + @param bytes The `Bytes` object containing the audio data. + @return An `AudioBuffer` instance with the decoded audio data. + **/ + public static function fromBytes(bytes:Bytes):AudioBuffer + { + if (bytes == null) return null; + + #if (js && html5 && lime_howlerjs) + var audioBuffer = new AudioBuffer(); + audioBuffer.src = new Howl({src: ["data:" + __getCodec(bytes) + ";base64," + Base64.encode(bytes)], preload: false}); + + return audioBuffer; + #elseif (lime_cffi && !macro) + #if !cs + var audioBuffer = new AudioBuffer(); + audioBuffer.data = new UInt8Array(Bytes.alloc(0)); + + return NativeCFFI.lime_audio_load_bytes(bytes, audioBuffer); + #else + var data:Dynamic = NativeCFFI.lime_audio_load_bytes(bytes, null); + + if (data != null) + { + var audioBuffer = new AudioBuffer(); + audioBuffer.bitsPerSample = data.bitsPerSample; + audioBuffer.channels = data.channels; + audioBuffer.data = new UInt8Array(@:privateAccess new Bytes(data.data.length, data.data.b)); + audioBuffer.sampleRate = data.sampleRate; + return audioBuffer; + } + #end + #end + + return null; + } + + /** + Creates an `AudioBuffer` from a file. + + @param path The file path to the audio data. + @return An `AudioBuffer` instance with the audio data loaded from the file. + **/ + public static function fromFile(path:String #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer + { + if (path == null) return null; + + #if (js && html5 && lime_howlerjs) + var audioBuffer = new AudioBuffer(); + + #if force_html5_audio + audioBuffer.__srcHowl = new Howl({src: [path], html5: true, preload: false}); + #else + audioBuffer.__srcHowl = new Howl({src: [path], html5: howlHtml5, preload: false}); + #end + + return audioBuffer; + #elseif flash + switch (Path.extension(path)) + { + case "ogg", "wav": + return null; + default: + } + + var audioBuffer = new AudioBuffer(); + audioBuffer.__srcSound = new Sound(new URLRequest(path)); + return audioBuffer; + #elseif (lime_cffi && !macro) + #if !cs + var audioBuffer = new AudioBuffer(); + audioBuffer.data = new UInt8Array(Bytes.alloc(0)); + + //audioBuffer = NativeCFFI.lime_audio_load_file(path, audioBuffer); + //if (audioBuffer != null) audioBuffer.initBuffer(); + //return audioBuffer; + return NativeCFFI.lime_audio_load_file(path, audioBuffer); + #else + var data:Dynamic = NativeCFFI.lime_audio_load_file(path, null); + + if (data != null) + { + var audioBuffer = new AudioBuffer(); + audioBuffer.bitsPerSample = data.bitsPerSample; + audioBuffer.channels = data.channels; + audioBuffer.data = new UInt8Array(@:privateAccess new Bytes(data.data.length, data.data.b)); + audioBuffer.sampleRate = data.sampleRate; + //audioBuffer.initBuffer(); + return audioBuffer; + } + + return null; + #end + #else + return null; + #end + } + + /** + Creates an `AudioBuffer` from an array of file paths. + + @param paths An array of file paths to search for audio data. + @return An `AudioBuffer` instance with the audio data loaded from the first valid file found. + **/ + public static function fromFiles(paths:Array<String> #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer + { + #if (js && html5 && lime_howlerjs) + var audioBuffer = new AudioBuffer(); + + #if force_html5_audio + audioBuffer.__srcHowl = new Howl({src: paths, html5: true, preload: false}); + #else + audioBuffer.__srcHowl = new Howl({src: paths, html5: howlHtml5, preload: false}); + #end + + return audioBuffer; + #else + var buffer = null; + + for (path in paths) + { + buffer = AudioBuffer.fromFile(path); + if (buffer != null) break; + } + + return buffer; + #end + } + + /** + Creates an `AudioBuffer` from a `VorbisFile`. + + @param vorbisFile The `VorbisFile` object containing the audio data. + @return An `AudioBuffer` instance with the decoded audio data. + **/ + #if lime_vorbis + + public static function fromVorbisFile(vorbisFile:VorbisFile):AudioBuffer + { + if (vorbisFile == null) return null; + + var info = vorbisFile.info(); + + var audioBuffer = new AudioBuffer(); + audioBuffer.channels = info.channels; + audioBuffer.sampleRate = info.rate; + audioBuffer.bitsPerSample = 16; + audioBuffer.__srcVorbisFile = vorbisFile; + + return audioBuffer; + } + #else + public static function fromVorbisFile(vorbisFile:Dynamic):AudioBuffer + { + return null; + } + #end + + /** + Asynchronously loads an `AudioBuffer` from a file. + + @param path The file path to the audio data. + @return A `Future` that resolves to the loaded `AudioBuffer`. + **/ + public static function loadFromFile(path:String):Future<AudioBuffer> + { + #if (flash || (js && html5)) + var promise = new Promise<AudioBuffer>(); + + var audioBuffer = AudioBuffer.fromFile(path); + + if (audioBuffer != null) + { + #if flash + audioBuffer.__srcSound.addEventListener(flash.events.Event.COMPLETE, function(event) + { + promise.complete(audioBuffer); + }); + + audioBuffer.__srcSound.addEventListener(flash.events.ProgressEvent.PROGRESS, function(event) + { + promise.progress(Std.int(event.bytesLoaded), Std.int(event.bytesTotal)); + }); + + audioBuffer.__srcSound.addEventListener(flash.events.IOErrorEvent.IO_ERROR, promise.error); + #elseif (js && html5 && lime_howlerjs) + if (audioBuffer != null) + { + audioBuffer.__srcHowl.on("load", function() + { + promise.complete(audioBuffer); + }); + + audioBuffer.__srcHowl.on("loaderror", function(id, msg) + { + promise.error(msg); + }); + + audioBuffer.__srcHowl.load(); + } + #else + promise.complete(audioBuffer); + #end + } + else + { + promise.error(null); + } + + return promise.future; + #else + // TODO: Streaming + + var request = new HTTPRequest<AudioBuffer>(); + return request.load(path).then(function(buffer) + { + if (buffer != null) + { + return Future.withValue(buffer); + } + else + { + return cast Future.withError(""); + } + }); + #end + } + + /** + Asynchronously loads an `AudioBuffer` from multiple files. + + @param paths An array of file paths to search for audio data. + @return A `Future` that resolves to the loaded `AudioBuffer`. + **/ + public static function loadFromFiles(paths:Array<String>):Future<AudioBuffer> + { + #if (js && html5 && lime_howlerjs) + var promise = new Promise<AudioBuffer>(); + + var audioBuffer = AudioBuffer.fromFiles(paths); + + if (audioBuffer != null) + { + audioBuffer.__srcHowl.on("load", function() + { + promise.complete(audioBuffer); + }); + + audioBuffer.__srcHowl.on("loaderror", function() + { + promise.error(null); + }); + + audioBuffer.__srcHowl.load(); + } + else + { + promise.error(null); + } + + return promise.future; + #else + return new Future(fromFiles.bind(paths), true); + #end + } + + private static function __getCodec(bytes:Bytes):String + { + var signature = bytes.getString(0, 4); + + switch (signature) + { + case "OggS": + return "audio/ogg"; + case "fLaC": + return "audio/flac"; + case "RIFF" if (bytes.getString(8, 4) == "WAVE"): + return "audio/wav"; + default: + switch ([bytes.get(0), bytes.get(1), bytes.get(2)]) + { + case [73, 68, 51] | [255, 251, _] | [255, 250, _] | [255, 243, _]: return "audio/mp3"; + default: + } + } + + Log.error("Unsupported sound format"); + return null; + } + + // Get & Set Methods + @:noCompletion private function get_src():Dynamic + { + #if (js && html5) + #if lime_howlerjs + return __srcHowl; + #else + return __srcAudio; + #end + #elseif flash + return __srcSound; + #elseif lime_vorbis + return __srcVorbisFile; + #else + return __srcCustom; + #end + } + + @:noCompletion private function set_src(value:Dynamic):Dynamic + { + #if (js && html5) + #if lime_howlerjs + return __srcHowl = value; + #else + return __srcAudio = value; + #end + #elseif flash + return __srcSound = value; + #elseif lime_vorbis + return __srcVorbisFile = value; + #else + return __srcCustom = value; + #end + } +} diff --git a/source/lime/media/AudioSource.hx b/source/lime/media/AudioSource.hx index bc5948df..87f9e327 100644 --- a/source/lime/media/AudioSource.hx +++ b/source/lime/media/AudioSource.hx @@ -1,29 +1,100 @@ package lime.media; import lime.app.Event; -import lime.media.openal.AL; -import lime.media.openal.ALSource; import lime.math.Vector4; #if !lime_debug @:fileXml('tags="haxe,release"') @:noDebug #end +/** + The `AudioSource` class provides a way to control audio playback in a Lime application. + It allows for playing, pausing, and stopping audio, as well as controlling various + audio properties such as gain, pitch, and looping. + + Depending on the platform, the audio backend may vary, but the API remains consistent. + + @see lime.media.AudioBuffer +**/ class AudioSource { + private static var activeSources:Array<AudioSource> = []; + + /** + An event that is dispatched when the audio playback is complete. + **/ public var onComplete = new Event<Void->Void>(); + + /** + An event that is dispatched when the audio playback looped. + **/ + public var onLoop = new Event<Void->Void>(); + + /** + The `AudioBuffer` associated with this `AudioSource`. + **/ public var buffer:AudioBuffer; + + /** + An property if this 'AudioSource' is playing. + **/ + public var playing(get, null):Bool; + + /** + The current playback position of the audio, in milliseconds. + **/ public var currentTime(get, set):Float; + + /** + The gain (volume) of the audio. A value of `1.0` represents the default volume. + **/ public var gain(get, set):Float; - public var length(get, set):Float; + + /** + The length of the audio, in milliseconds. + **/ + public var length(get, set):Null<Float>; + + /** + The number of times the audio will loop. A value of `0` means the audio will not loop. + **/ public var loops(get, set):Int; + + /** + In which audio playback time the audio will loop. + **/ + public var loopTime(get, set):Float; + + /** + The pitch of the audio. A value of `1.0` represents the default pitch. + **/ public var pitch(get, set):Float; - public var offset:Int; + + /** + The offset within the audio buffer to start playback, in samples. + **/ + public var offset:Float; + + /** + The 3D position of the audio source, represented as a `Vector4`. + **/ public var position(get, set):Vector4; + /** + The latency of the audio source. + **/ + public var latency(get, never):Float; + @:noCompletion private var __backend:AudioSourceBackend; - public function new(buffer:AudioBuffer = null, offset:Int = 0, length:Null<Int> = null, loops:Int = 0) + /** + Creates a new `AudioSource` instance. + @param buffer The `AudioBuffer` to associate with this `AudioSource`. + @param offset The starting offset within the audio buffer, in samples. + @param length The length of the audio to play, in milliseconds. If `null`, the full buffer is used. + @param loops The number of times to loop the audio. `0` means no looping. + **/ + public function new(buffer:AudioBuffer = null, offset:Float = 0, length:Null<Float> = null, loops:Int = 0) { this.buffer = buffer; this.offset = offset; @@ -35,104 +106,136 @@ class AudioSource this.length = length; } - this.loops = loops; - if (buffer != null) { init(); } + + this.loops = loops; } - public function dispose():Void + /** + Releases any resources used by this `AudioSource`. + **/ + inline public function dispose():Void { __backend.dispose(); + activeSources.remove(this); } - @:noCompletion private function init():Void + @:noCompletion inline private function init():Void { __backend.init(); + activeSources.push(this); } - public function play():Void + /** + Starts or resumes audio playback. + **/ + inline public function play():Void { __backend.play(); } - public function pause():Void + /** + Pauses audio playback. + **/ + inline public function pause():Void { __backend.pause(); } - public function stop():Void + /** + Stops audio playback and resets the playback position to the beginning. + **/ + inline public function stop():Void { __backend.stop(); } // Get & Set Methods - @:noCompletion private function get_currentTime():Float + @:noCompletion inline private function get_playing():Bool + { + @:privateAccess return __backend.playing; + } + + @:noCompletion inline private function get_currentTime():Float { return __backend.getCurrentTime(); } - @:noCompletion private function set_currentTime(value:Float):Float + @:noCompletion inline private function set_currentTime(value:Float):Float { return __backend.setCurrentTime(value); } - @:noCompletion private function get_gain():Float + @:noCompletion inline private function get_gain():Float { return __backend.getGain(); } - @:noCompletion private function set_gain(value:Float):Float + @:noCompletion inline private function set_gain(value:Float):Float { return __backend.setGain(value); } - @:noCompletion private function get_length():Float + @:noCompletion inline private function get_length():Null<Float> { return __backend.getLength(); } - @:noCompletion private function set_length(value:Float):Float + @:noCompletion inline private function set_length(value:Null<Float>):Null<Float> { return __backend.setLength(value); } - @:noCompletion private function get_loops():Int + @:noCompletion inline private function get_loops():Int { return __backend.getLoops(); } - @:noCompletion private function set_loops(value:Int):Int + @:noCompletion inline private function set_loops(value:Int):Int { return __backend.setLoops(value); } - @:noCompletion private function get_pitch():Float + @:noCompletion inline private function get_loopTime():Float + { + return __backend.getLoopTime(); + } + + @:noCompletion inline private function set_loopTime(value:Float):Float + { + return __backend.setLoopTime(value); + } + + @:noCompletion inline private function get_pitch():Float { return __backend.getPitch(); } - @:noCompletion private function set_pitch(value:Float):Float + @:noCompletion inline private function set_pitch(value:Float):Float { return __backend.setPitch(value); } - @:noCompletion private function get_position():Vector4 + @:noCompletion inline private function get_position():Vector4 { return __backend.getPosition(); } - @:noCompletion private function set_position(value:Vector4):Vector4 + @:noCompletion inline private function set_position(value:Vector4):Vector4 { return __backend.setPosition(value); } + + @:noCompletion inline private function get_latency():Float + { + return __backend.getLatency(); + } } -#if flash -@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.flash.FlashAudioSource; -#elseif (js && html5) +#if (js && html5) @:noCompletion private typedef AudioSourceBackend = lime._internal.backend.html5.HTML5AudioSource; #else @:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeAudioSource; diff --git a/source/lime/media/vorbis/VorbisFile.hx b/source/lime/media/vorbis/VorbisFile.hx new file mode 100644 index 00000000..bd29a7a7 --- /dev/null +++ b/source/lime/media/vorbis/VorbisFile.hx @@ -0,0 +1,356 @@ +package lime.media.vorbis; + +#if (!lime_doc_gen || lime_vorbis) +import haxe.Int64; +import haxe.io.Bytes; +import lime._internal.backend.native.NativeCFFI; + +#if hl +@:keep +#end +@:access(lime._internal.backend.native.NativeCFFI) +class VorbisFile +{ + public var bitstream(default, null):Int; + + @:noCompletion private var handle:Dynamic; + + @:noCompletion var _filePath:String; + @:noCompletion var _bytes:Bytes; + + @:noCompletion private function new(handle:Dynamic) + { + this.handle = handle; + } + + public function bitrate(bitstream:Int = -1):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_bitrate(handle, bitstream); + #else + return 0; + #end + } + + public function bitrateInstant():Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_bitrate_instant(handle); + #else + return 0; + #end + } + + public function clear():Void + { + #if (lime_cffi && lime_vorbis && !macro) + NativeCFFI.lime_vorbis_file_clear(handle); + #end + handle = null; + _filePath = null; + _bytes = null; + } + + public function clone():VorbisFile + { + if (_filePath != null) return fromFile(_filePath); + if (_bytes != null) return fromBytes(_bytes); + return null; + } + + public function comment(bitstream:Int = -1):VorbisComment + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_comment(handle, bitstream); + + if (data != null) + { + var comment = new VorbisComment(); + comment.userComments = data.userComments; + comment.vendor = data.vendor; + return comment; + } + #end + + return null; + } + + public function crosslap(other:VorbisFile):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_crosslap(handle, other.handle); + #else + return 0; + #end + } + + public static function fromBytes(bytes:Bytes):VorbisFile + { + #if (lime_cffi && lime_vorbis && !macro) + var handle = NativeCFFI.lime_vorbis_file_from_bytes(bytes); + + if (handle != null) + { + var vorbisFile = new VorbisFile(handle); + vorbisFile._bytes = bytes; + return vorbisFile; + } + #end + + return null; + } + + public static function fromFile(path:String):VorbisFile + { + #if (lime_cffi && lime_vorbis && !macro) + var handle = NativeCFFI.lime_vorbis_file_from_file(path); + + if (handle != null) + { + var vorbisFile = new VorbisFile(handle); + vorbisFile._filePath = path; + return vorbisFile; + } + #end + + return null; + } + + public function info(bitstream:Int = -1):VorbisInfo + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_info(handle, bitstream); + + if (data != null) + { + var info = new VorbisInfo(); + info.bitrateLower = data.bitrateLower; + info.bitrateNominal = data.bitrateNominal; + info.bitrateUpper = data.bitrateUpper; + info.channels = data.channels; + info.rate = data.rate; + info.version = data.version; + return info; + } + #end + + return null; + } + + public function pcmSeek(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmSeekLap(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek_lap(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmSeekPage(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek_page(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmSeekPageLap(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_pcm_seek_page_lap(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function pcmTell():Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_pcm_tell(handle); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function pcmTotal(bitstream:Int = -1):Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_pcm_total(handle, bitstream); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function rawSeek(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_raw_seek(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function rawSeekLap(pos:Int64):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_raw_seek_lap(handle, pos.low, pos.high); + #else + return 0; + #end + } + + public function rawTell():Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_raw_tell(handle); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function rawTotal(bitstream:Int = -1):Int64 + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_raw_total(handle, bitstream); + + if (data != null) + { + return Int64.make(data.high, data.low); + } + #end + + return Int64.ofInt(0); + } + + public function read(buffer:Bytes, position:Int, length:Int = 4096, bigEndianPacking:Bool = false, wordSize:Int = 2, signed:Bool = true):Int + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_read(handle, buffer, position, length, bigEndianPacking, wordSize, signed); + if (data == null) return 0; + bitstream = data.bitstream; + return data.returnValue; + #else + return 0; + #end + } + + // public function readFilter (buffer:Bytes, length:Int = 4096, endianness:Endian = LITTLE_ENDIAN, wordSize:Int = 2, signed:Bool = true, bitstream:Int = 0, filter, filter_param + public function readFloat(pcmChannels:Bytes, samples:Int):Int + { + #if (lime_cffi && lime_vorbis && !macro) + var data = NativeCFFI.lime_vorbis_file_read_float(handle, pcmChannels, samples); + if (data == null) return 0; + bitstream = data.bitstream; + return data.returnValue; + #else + return 0; + #end + } + + public function seekable():Bool + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_seekable(handle); + #else + return false; + #end + } + + public function serialNumber(bitstream:Int = -1):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_serial_number(handle, bitstream); + #else + return 0; + #end + } + + public function streams():Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_streams(handle); + #else + return 0; + #end + } + + public function timeSeek(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek(handle, s); + #else + return 0; + #end + } + + public function timeSeekLap(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek_lap(handle, s); + #else + return 0; + #end + } + + public function timeSeekPage(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek_page(handle, s); + #else + return 0; + #end + } + + public function timeSeekPageLap(s:Float):Int + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_seek_page_lap(handle, s); + #else + return 0; + #end + } + + public function timeTell():Float + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_tell(handle); + #else + return 0; + #end + } + + public function timeTotal(bitstream:Int = -1):Float + { + #if (lime_cffi && lime_vorbis && !macro) + return NativeCFFI.lime_vorbis_file_time_total(handle, bitstream); + #else + return 0; + #end + } +} +#end diff --git a/source/lime/ui/FileDialog.hx b/source/lime/ui/FileDialog.hx new file mode 100644 index 00000000..418d7119 --- /dev/null +++ b/source/lime/ui/FileDialog.hx @@ -0,0 +1,411 @@ +package lime.ui; + +import haxe.io.Bytes; +import haxe.io.Path; +import lime._internal.backend.native.NativeCFFI; +import lime.app.Event; +import lime.graphics.Image; +import lime.system.BackgroundWorker; +import lime.utils.ArrayBuffer; +import lime.utils.Resource; +#if hl +import hl.Bytes as HLBytes; +import hl.NativeArray; +#end +#if sys +import sys.io.File; +#end +#if (js && html5) +import js.html.Blob; +#end + +/** + Simple file dialog used for asking user where to save a file, or select files to open. + + Example usage: + ```haxe + var fileDialog = new FileDialog(); + + fileDialog.onCancel.add( () -> trace("Canceled.") ); + + fileDialog.onSave.add( path -> trace("File saved in " + path) ); + + fileDialog.onOpen.add( res -> trace("Size of the file = " + (res:haxe.io.Bytes).length) ); + + if ( fileDialog.open("jpg", null, "Load file") ) + trace("File dialog opened, waiting for selection..."); + else + trace("This dialog is unsupported."); + ``` + + Availability note: most file dialog operations are only available on desktop targets, though + `save()` is also available in HTML5. +**/ +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@:access(lime._internal.backend.native.NativeCFFI) +@:access(lime.graphics.Image) +class FileDialog +{ + /** + Triggers when the user clicks "Cancel" during any operation, or when a function is unsupported + (such as `open()` on HTML5). + **/ + public var onCancel = new Event<Void->Void>(); + + /** + Triggers when `open()` is successful. The `lime.utils.Resource` contains the file's data, and can + be implicitly cast to `haxe.io.Bytes`. + **/ + public var onOpen = new Event<Resource->Void>(); + + /** + Triggers when `open()` is successful. The `lime.utils.Resource` contains the file's data, and can + be implicitly cast to `haxe.io.Bytes`, the String is the path to the file. + **/ + public var onOpenFile = new Event<(Resource, String)->Void>(); // Added by @NeeEoo + + /** + Triggers when `save()` is successful. The `String` is the path to the saved file. + **/ + public var onSave = new Event<String->Void>(); + + /** + Triggers when `browse()` is successful and `type` is anything other than + `FileDialogType.OPEN_MULTIPLE`. The `String` is the path to the selected file. + **/ + public var onSelect = new Event<String->Void>(); + + /** + Triggers when `browse()` is successful and `type` is `FileDialogType.OPEN_MULTIPLE`. The + `Array<String>` contains all selected file paths. + **/ + public var onSelectMultiple = new Event<Array<String>->Void>(); + + public function new() {} + + /** + Opens a file selection dialog. If successful, either `onSelect` or `onSelectMultiple` will trigger + with the result(s). + + This function only works on desktop targets, and will return `false` otherwise. + @param type Type of the file dialog: `OPEN`, `SAVE`, `OPEN_DIRECTORY` or `OPEN_MULTIPLE`. + @param filter A filter to use when browsing. Asterisks are treated as wildcards. For example, + `"*.jpg"` will match any file ending in `.jpg`. + @param defaultPath The directory in which to start browsing and/or the default filename to + suggest. Defaults to `Sys.getCwd()`, with no default filename. + @param title The title to give the dialog window. + @return Whether `browse()` is supported on this target. + **/ + public function browse(type:FileDialogType = null, filter:String = null, defaultPath:String = null, title:String = null):Bool + { + if (type == null) type = FileDialogType.OPEN; + + #if desktop + var worker = new BackgroundWorker(); + + worker.doWork.add(function(_) + { + switch (type) + { + case OPEN: + #if linux + if (title == null) title = "Open File"; + #end + + var path = null; + #if (!macro && lime_cffi) + #if hl + var bytes = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath); + if (bytes != null) + { + path = @:privateAccess String.fromUTF8(cast bytes); + } + #else + path = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath); + #end + #end + + worker.sendComplete(path); + + case OPEN_MULTIPLE: + #if linux + if (title == null) title = "Open Files"; + #end + + var paths = null; + #if (!macro && lime_cffi) + #if hl + var bytes:NativeArray<HLBytes> = cast NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath); + if (bytes != null) + { + paths = []; + for (i in 0...bytes.length) + { + paths[i] = @:privateAccess String.fromUTF8(bytes[i]); + } + } + #else + paths = NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath); + #end + #end + + worker.sendComplete(paths); + + case OPEN_DIRECTORY: + #if linux + if (title == null) title = "Open Directory"; + #end + + var path = null; + #if (!macro && lime_cffi) + #if hl + var bytes = NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath); + if (bytes != null) + { + path = @:privateAccess String.fromUTF8(cast bytes); + } + #else + path = NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath); + #end + #end + + worker.sendComplete(path); + + case SAVE: + #if linux + if (title == null) title = "Save File"; + #end + + var path = null; + #if (!macro && lime_cffi) + #if hl + var bytes = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath); + if (bytes != null) + { + path = @:privateAccess String.fromUTF8(cast bytes); + } + #else + path = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath); + #end + #end + + worker.sendComplete(path); + } + }); + + worker.onComplete.add(function(result) + { + switch (type) + { + case OPEN, OPEN_DIRECTORY, SAVE: + var path:String = cast result; + + if (path != null) + { + // Makes sure the filename ends with extension + if (type == SAVE && filter != null && path.indexOf(".") == -1) + { + path += "." + filter; + } + + onSelect.dispatch(path); + } + else + { + onCancel.dispatch(); + } + + case OPEN_MULTIPLE: + var paths:Array<String> = cast result; + + if (paths != null && paths.length > 0) + { + onSelectMultiple.dispatch(paths); + } + else + { + onCancel.dispatch(); + } + } + }); + + worker.run(); + + return true; + #else + onCancel.dispatch(); + return false; + #end + } + + /** + Shows an open file dialog. If successful, `onOpen` will trigger with the file contents. + + This function only works on desktop targets, and will return `false` otherwise. + @param filter A filter to use when browsing. Asterisks are treated as wildcards. For example, + `"*.jpg"` will match any file ending in `.jpg`. + @param defaultPath The directory in which to start browsing and/or the default filename to + suggest. Defaults to `Sys.getCwd()`, with no default filename. + @param title The title to give the dialog window. + @return Whether `open()` is supported on this target. + **/ + public function open(filter:String = null, defaultPath:String = null, title:String = null):Bool + { + #if (desktop && sys) + var worker = new BackgroundWorker(); + + worker.doWork.add(function(_) + { + #if linux + if (title == null) title = "Open File"; + #end + + var path = null; + #if (!macro && lime_cffi) + #if hl + var bytes = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath); + if (bytes != null) path = @:privateAccess String.fromUTF8(cast bytes); + #else + path = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath); + #end + #end + + worker.sendComplete(path); + }); + + worker.onComplete.add(function(path:String) + { + if (path != null) + { + try + { + var data = File.getBytes(path); + onOpen.dispatch(data); + onOpenFile.dispatch(data, path); // Added by @NeeEoo + return; + } + catch (e:Dynamic) {} + } + + onCancel.dispatch(); + }); + + worker.run(); + + return true; + #else + onCancel.dispatch(); + return false; + #end + } + + /** + Shows an open file dialog. If successful, `onSave` will trigger with the selected path. + + This function only works on desktop and HMTL5 targets, and will return `false` otherwise. + @param data The file contents, in `haxe.io.Bytes` format. (Implicit casting possible.) + @param filter A filter to use when browsing. Asterisks are treated as wildcards. For example, + `"*.jpg"` will match any file ending in `.jpg`. Used only if targeting deskop. + @param defaultPath The directory in which to start browsing and/or the default filename to + suggest. When targeting destkop, this defaults to `Sys.getCwd()` with no default filename. When targeting + HTML5, this defaults to the browser's download directory, with a default filename based on the MIME type. + @param title The title to give the dialog window. + @param type The default MIME type of the file, in case the type can't be determined from the + file data. Used only if targeting HTML5. + @return Whether `save()` is supported on this target. + **/ + public function save(data:Resource, filter:String = null, defaultPath:String = null, title:String = null, type:String = "application/octet-stream"):Bool + { + if (data == null) + { + onCancel.dispatch(); + return false; + } + + #if (desktop && sys) + var worker = new BackgroundWorker(); + + worker.doWork.add(function(_) + { + #if linux + if (title == null) title = "Save File"; + #end + + var path = null; + #if (!macro && lime_cffi) + #if hl + var bytes = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath); + path = @:privateAccess String.fromUTF8(cast bytes); + #else + path = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath); + #end + #end + + worker.sendComplete(path); + }); + + worker.onComplete.add(function(path:String) + { + if (path != null) + { + try + { + File.saveBytes(path, data); + onSave.dispatch(path); + return; + } + catch (e:Dynamic) {} + } + + onCancel.dispatch(); + }); + + worker.run(); + + return true; + #elseif (js && html5) + // TODO: Cleaner API for mimeType detection + + var defaultExtension = ""; + + if (Image.__isPNG(data)) + { + type = "image/png"; + defaultExtension = ".png"; + } + else if (Image.__isJPG(data)) + { + type = "image/jpeg"; + defaultExtension = ".jpg"; + } + else if (Image.__isGIF(data)) + { + type = "image/gif"; + defaultExtension = ".gif"; + } + else if (Image.__isWebP(data)) + { + type = "image/webp"; + defaultExtension = ".webp"; + } + + var path = defaultPath != null ? Path.withoutDirectory(defaultPath) : "download" + defaultExtension; + var buffer = (data : Bytes).getData(); + buffer = buffer.slice(0, (data : Bytes).length); + + #if commonjs + untyped #if haxe4 js.Syntax.code #else __js__ #end ("require ('file-saver')")(new Blob([buffer], {type: type}), path, true); + #else + untyped window.saveAs(new Blob([buffer], {type: type}), path, true); + #end + onSave.dispatch(path); + return true; + #else + onCancel.dispatch(); + return false; + #end + } +} diff --git a/source/lime/utils/ObjectPool.hx b/source/lime/utils/ObjectPool.hx new file mode 100644 index 00000000..f05715c3 --- /dev/null +++ b/source/lime/utils/ObjectPool.hx @@ -0,0 +1,347 @@ +package lime.utils; + +import haxe.ds.ObjectMap; + + +/** + A generic object pool for reusing objects. + **/ +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +#if !js @:generic #end class ObjectPool<T> +{ + /** + The number of active objects in the pool. + **/ + public var activeObjects(default, null):Int; + + /** + The number of inactive objects in the pool. + **/ + public var inactiveObjects(default, null):Int; + + /** + The total size of the object pool (both active and inactive objects). + **/ + public var size(get, set):Null<Int>; + + @:noCompletion private var __inactiveObject0:T; + @:noCompletion private var __inactiveObject1:T; + @:noCompletion private var __inactiveObjectList:List<T>; + @:noCompletion private var __pool:Map<T, Bool>; + @:noCompletion private var __size:Null<Int>; + + /** + Creates a new ObjectPool instance. + + @param create A function that creates a new instance of type T. + @param clean A function that cleans up an instance of type T before it is reused. + @param size The maximum size of the object pool. + **/ + public function new(create:Void->T = null, clean:T->Void = null, size:Null<Int> = null) + { + __pool = cast new ObjectMap(); + + activeObjects = 0; + inactiveObjects = 0; + + __inactiveObject0 = null; + __inactiveObject1 = null; + __inactiveObjectList = new List<T>(); + + if (create != null) + { + this.create = create; + } + if (clean != null) + { + this.clean = clean; + } + if (size != null) + { + this.size = size; + } + } + /** + Adds an object to the object pool. + + @param object The object to add to the pool. + **/ + public function add(object:T):Void + { + if (object != null && !__pool.exists(object)) + { + __pool.set(object, false); + clean(object); + __addInactive(object); + } + } + + /** + Dynamic function. + + Cleans up an object before returning it to the pool. + + @param object The object to clean up. + **/ + public dynamic function clean(object:T):Void {} + + /** + Clears the object pool, removing all objects. + **/ + public function clear():Void + { + __pool = cast new ObjectMap(); + + activeObjects = 0; + inactiveObjects = 0; + + __inactiveObject0 = null; + __inactiveObject1 = null; + __inactiveObjectList.clear(); + } + + /** + Dynamic function. + + Creates a new Object. + **/ + public dynamic function create():T + { + return null; + } + + /** + Creates a new object and adds it to the pool, or returns an existing inactive object from the pool. + + @return The object retrieved from the pool, or null if the pool is full and no new objects can be created. + **/ + public function get():T + { + var object = null; + + if (inactiveObjects > 0) + { + object = __getInactive(); + } + else if (__size == null || activeObjects < __size) + { + object = create(); + + if (object != null) + { + __pool.set(object, true); + activeObjects++; + } + } + + return object; + } + + /** + Releases an active object back into the pool. + + @param object The object to release. + **/ + public function release(object:T):Void + { + #if lime_pool_debug + if (object == null || !__pool.exists(object)) + { + Log.error("Object is not a member of the pool"); + } + else if (!__pool.get(object)) + { + Log.error("Object has already been released"); + } + #end + + activeObjects--; + + if (__size == null || activeObjects + inactiveObjects < __size) + { + clean(object); + __addInactive(object); + } + else + { + __pool.remove(object); + } + } + + /** + Removes an object from the pool. + + @param object The object to remove from the pool. + **/ + public function remove(object:T):Void + { + if (object != null && __pool.exists(object)) + { + __pool.remove(object); + + if (__inactiveObject0 == object) + { + __inactiveObject0 = null; + inactiveObjects--; + } + else if (__inactiveObject1 == object) + { + __inactiveObject1 = null; + inactiveObjects--; + } + else if (__inactiveObjectList.remove(object)) + { + inactiveObjects--; + } + else + { + activeObjects--; + } + } + } + + @:noCompletion private inline function __addInactive(object:T):Void + { + #if lime_pool_debug + __pool.set(object, false); + #end + + if (__inactiveObject0 == null) + { + __inactiveObject0 = object; + } + else if (__inactiveObject1 == null) + { + __inactiveObject1 = object; + } + else + { + __inactiveObjectList.add(object); + } + + inactiveObjects++; + } + + @:noCompletion private inline function __getInactive():T + { + var object = null; + + if (__inactiveObject0 != null) + { + object = __inactiveObject0; + __inactiveObject0 = null; + } + else if (__inactiveObject1 != null) + { + object = __inactiveObject1; + __inactiveObject1 = null; + } + else + { + object = __inactiveObjectList.pop(); + + if (__inactiveObjectList.length > 0) + { + __inactiveObject0 = __inactiveObjectList.pop(); + } + + if (__inactiveObjectList.length > 0) + { + __inactiveObject1 = __inactiveObjectList.pop(); + } + } + + #if lime_pool_debug + __pool.set(object, true); + #end + + inactiveObjects--; + activeObjects++; + + return object; + } + + @:noCompletion private function __removeInactive(count:Int):Void + { + if (count <= 0 || inactiveObjects == 0) return; + + if (__inactiveObject0 != null) + { + __pool.remove(__inactiveObject0); + __inactiveObject0 = null; + inactiveObjects--; + count--; + } + + if (count == 0 || inactiveObjects == 0) return; + + if (__inactiveObject1 != null) + { + __pool.remove(__inactiveObject1); + __inactiveObject1 = null; + inactiveObjects--; + count--; + } + + if (count == 0 || inactiveObjects == 0) return; + + for (object in __inactiveObjectList) + { + __pool.remove(object); + __inactiveObjectList.remove(object); + inactiveObjects--; + count--; + + if (count == 0 || inactiveObjects == 0) return; + } + } + + // Get & Set Methods + @:noCompletion private function get_size():Null<Int> + { + return __size; + } + + @:noCompletion private function set_size(value:Null<Int>):Null<Int> + { + if (value == null) + { + __size = null; + } + else + { + var current = inactiveObjects + activeObjects; + __size = value; + + if (current > value) + { + __removeInactive(current - value); + } + else if (value > current) + { + var object; + + for (i in 0...(value - current)) + { + object = create(); + + if (object != null) + { + __pool.set(object, false); + __inactiveObjectList.add(object); + inactiveObjects++; + } + else + { + break; + } + } + } + } + + return value; + } +} diff --git a/source/openfl/media/Sound.hx b/source/openfl/media/Sound.hx index b0632e78..808399c3 100644 --- a/source/openfl/media/Sound.hx +++ b/source/openfl/media/Sound.hx @@ -599,17 +599,6 @@ class Sound extends EventDispatcher #end } - var changeID:Int = 0; - public function regen() { - var audioBuffer = new AudioBuffer(); - audioBuffer.bitsPerSample = __buffer.bitsPerSample; - audioBuffer.channels = __buffer.channels; - audioBuffer.data = __buffer.data; - audioBuffer.sampleRate = __buffer.sampleRate; - - __buffer = audioBuffer; - } - /** Generates a new SoundChannel object to play back the sound. This method returns a SoundChannel object, which you access to stop the sound and to @@ -636,13 +625,6 @@ class Sound extends EventDispatcher return null; } - #if !macro - if (changeID < funkin.backend.system.Main.changeID) { - changeID = funkin.backend.system.Main.changeID; - regen(); - } - #end - if (sndTransform == null) { sndTransform = new SoundTransform(); @@ -690,25 +672,10 @@ class Sound extends EventDispatcher #if (js && html5 && howlerjs) return __buffer.src.duration() * 1000; #else - if (__buffer.data != null) - { - // var samples = (__buffer.data.length * 8) / (__buffer.channels * __buffer.bitsPerSample); - // return Std.int(samples / __buffer.sampleRate * 1000); - var samples = (Int64.make(0, __buffer.data.length) * Int64.ofInt(8)) / Int64.ofInt(__buffer.channels * __buffer.bitsPerSample); - var value = samples / Int64.ofInt(__buffer.sampleRate) * Int64.ofInt(1000); - return Int64.toInt(value); - } - else if (__buffer.__srcVorbisFile != null) - { - //var samples = Int64.toInt(__buffer.__srcVorbisFile.pcmTotal()); - //return Std.int(samples / __buffer.sampleRate * 1000); - var samples = __buffer.__srcVorbisFile.pcmTotal(); - var value = Int64.fromFloat(__buffer.__srcVorbisFile.timeTotal()) * 1000; - return Int64.toInt(value); - } - else - { - return 0; + if (__buffer.data != null) return (__buffer.data.length >> 0) / __buffer.channels / (__buffer.bitsPerSample >> 3) / __buffer.sampleRate * 1000; + else if (__buffer.__srcVorbisFile != null) { + var x = __buffer.__srcVorbisFile.pcmTotal(); + return (x.high * 4294967296. + (x.low >> 0)) / __buffer.sampleRate * 1000; } #end } diff --git a/source/openfl/media/SoundChannel.hx b/source/openfl/media/SoundChannel.hx index 0838f391..6ccbf8ac 100644 --- a/source/openfl/media/SoundChannel.hx +++ b/source/openfl/media/SoundChannel.hx @@ -1,10 +1,13 @@ package openfl.media; #if !flash +import haxe.Int64; + import openfl.events.Event; import openfl.events.EventDispatcher; #if lime import lime.media.AudioSource; +import lime.media.openal.AL; #end /** @@ -16,11 +19,19 @@ import lime.media.AudioSource; object to the channel. @event soundComplete Dispatched when a sound has finished playing. + + @see [Playing sounds](https://books.openfl.org/openfl-developers-guide/working-with-sound/playing-sounds.html) + @see `openfl.media.Sound` **/ #if !openfl_debug @:fileXml('tags="haxe,release"') @:noDebug #end +#if (lime && lime_cffi) +@:access(lime._internal.backend.native.NativeAudioSource) +@:access(lime.media.AudioSource) +#end +@:access(openfl.media.Sound) @:access(openfl.media.SoundMixer) @:final @:keep class SoundChannel extends EventDispatcher { @@ -28,7 +39,13 @@ import lime.media.AudioSource; The current amplitude(volume) of the left channel, from 0(silent) to 1 (full amplitude). **/ - public var leftPeak(default, null):Float; + public var leftPeak(get, null):Float; + + /** + The current amplitude(volume) of the right channel, from 0(silent) to 1 + (full amplitude). + **/ + public var rightPeak(get, null):Float; /** When the sound is playing, the `position` property indicates in @@ -45,12 +62,6 @@ import lime.media.AudioSource; **/ public var position(get, set):Float; - /** - The current amplitude(volume) of the right channel, from 0(silent) to 1 - (full amplitude). - **/ - public var rightPeak(default, null):Float; - /** The SoundTransform object assigned to the sound channel. A SoundTransform object includes properties for setting volume, panning, left speaker @@ -58,13 +69,23 @@ import lime.media.AudioSource; **/ public var soundTransform(get, set):SoundTransform; + /** + self explanatory + */ + public var loopTime(get, set):Float; + public var endTime(get, set):Null<Float>; public var pitch(get, set):Float; + public var loops(get, set):Int; + @:noCompletion private var __sound:Sound; @:noCompletion private var __isValid:Bool; @:noCompletion private var __soundTransform:SoundTransform; + @:noCompletion private var __lastPeakTime:Float; + @:noCompletion private var __leftPeak:Float; + @:noCompletion private var __rightPeak:Float; #if lime @:noCompletion private var __source:AudioSource; - @:noCompletion private var __audioSource(get, set):AudioSource; + @:noCompletion private var __audioSource(get, set):AudioSource; // forward??? compatibility?? #end #if openfljs @@ -87,28 +108,10 @@ import lime.media.AudioSource; { super(this); - leftPeak = 1; - rightPeak = 1; + if (soundTransform != null) __soundTransform = soundTransform; + else __soundTransform = new SoundTransform(); - if (soundTransform != null) - { - __soundTransform = soundTransform; - } - else - { - __soundTransform = new SoundTransform(); - } - - #if lime - if (source != null) - { - __source = source; - __source.onComplete.add(source_onComplete); - __isValid = true; - - __source.play(); - } - #end + __initAudioSource(source); SoundMixer.__registerSoundChannel(this); } @@ -134,6 +137,7 @@ import lime.media.AudioSource; #if lime __source.onComplete.remove(source_onComplete); + __source.onLoop.remove(source_onLoop); __source.dispose(); __source = null; #end @@ -145,6 +149,90 @@ import lime.media.AudioSource; this.soundTransform = soundTransform; } + @:noCompletion private function __updatePeaks(time:Float):Bool + { + if (Math.abs(time - __lastPeakTime) < 8) return false; + + __lastPeakTime = time; + __leftPeak = __rightPeak = 0; + + #if !macro + if (!__isValid) return false; + + var buffer = __source.buffer; + var wordSize = buffer.bitsPerSample >> 3, bitUnsignedSize = 1 << buffer.bitsPerSample, bitSize = 1 << (buffer.bitsPerSample - 1); + var pos = Math.floor(time * buffer.sampleRate / 1000 * buffer.channels * wordSize), size = 0, buf; + + #if lime_cffi + var backend = __source.__backend, i = 0; + if (backend.streamed) { + size = backend.bufferSizes[i = backend.bufferSizes.length - backend.queuedBuffers]; + buf = backend.bufferDatas[i].buffer; + pos -= Math.floor(backend.bufferTimes[i] * buffer.sampleRate * buffer.channels * wordSize); + } + else + #end { + buf = buffer.data #if !js .buffer #end; + size = #if js buf.byteLength #else buf.length #end; + } + + var s = Math.floor(Math.min(buffer.sampleRate / 80, 512)), c = Math.floor((pos % (wordSize * buffer.channels)) / wordSize), b = 0, w = 0; + pos -= pos % wordSize; + + while (s > 0) { + if (wordSize == 1) b = #if js buf[pos++] #else buf.get(pos++) #end - bitSize; + else { + while (w < buffer.bitsPerSample) { + b |= #if js buf[pos] #else buf.get(pos) #end << w; + w += 8; + pos++; + } + if (b > bitSize) b -= bitUnsignedSize; + } + ((c % 2 == 0) ? (if (__leftPeak < b) __leftPeak = b) : (if (__rightPeak < b) __rightPeak = b)); + + if (pos >= size) #if lime_cffi { + if (!backend.streamed || ++i >= backend.bufferDatas.length) break; + size = backend.bufferSizes[i]; + buf = backend.bufferDatas[i].buffer; + pos = 0; + } + #else break; #end + + if (++c > buffer.channels) { + c = 0; + s--; + } + w = b = 0; + } + + if (buffer.channels == 1) __rightPeak = __leftPeak / bitSize; + else { + __leftPeak = __leftPeak / bitSize; + __rightPeak = __rightPeak / bitSize; + } + #end + + return true; + } + + @:noCompletion private function __initAudioSource(source:#if lime AudioSource #else Dynamic #end):Void + { + #if lime + __source = source; + if (__source == null) + { + return; + } + + __source.onComplete.add(source_onComplete); + __source.onLoop.add(source_onLoop); + __isValid = true; + + __source.play(); + #end + } + // Get & Set Methods @:noCompletion private function get_position():Float { @@ -167,27 +255,6 @@ import lime.media.AudioSource; return value; } - @:noCompletion private function get_pitch():Float - { - if (!__isValid) return 1; - - #if lime - return __source.pitch; - #else - return 1; - #end - } - - @:noCompletion private function set_pitch(value:Float):Float - { - if (!__isValid) return 1; - - #if lime - __source.pitch = value; - #end - return value; - } - @:noCompletion private function get_soundTransform():SoundTransform { return __soundTransform.clone(); @@ -203,7 +270,7 @@ import lime.media.AudioSource; var pan = SoundMixer.__soundTransform.pan + __soundTransform.pan; if (pan < -1) pan = -1; - else if (pan > 1) pan = 1; + if (pan > 1) pan = 1; var volume = SoundMixer.__soundTransform.volume * __soundTransform.volume; @@ -225,6 +292,107 @@ import lime.media.AudioSource; return value; } + @:noCompletion private function get_pitch():Float + { + if (!__isValid) return 1; + + #if lime + return __source.pitch; + #else + return 0; + #end + } + + @:noCompletion private function set_pitch(value:Float):Float + { + if (!__isValid) return 1; + + #if lime + return __source.pitch = value; + #else + return 0; + #end + } + + @:noCompletion private function get_loopTime():Float + { + if (!__isValid) return -1; + + #if lime + return __source.loopTime; + #else + return -1; + #end + } + + @:noCompletion private function set_loopTime(value:Float):Float + { + if (!__isValid) return -1; + + #if lime + return __source.loopTime = value; + #else + return -1; + #end + } + + @:noCompletion private function get_endTime():Null<Float> + { + if (!__isValid) return null; + + #if lime + return __source.length; + #else + return null; + #end + } + + @:noCompletion private function set_endTime(value:Null<Float>):Null<Float> + { + if (!__isValid) return null; + + #if lime + return __source.length = value; + #else + return null; + #end + } + + @:noCompletion private function get_loops():Int + { + if (!__isValid) return 0; + + #if lime + return __source.loops; + #else + return 0; + #end + } + + @:noCompletion private function set_loops(value:Int):Int + { + if (!__isValid) return 0; + + #if lime + return __source.loops = value; + #else + return 0; + #end + } + + @:noCompletion private function get_leftPeak():Float + { + __updatePeaks(get_position()); + return __leftPeak * (soundTransform == null ? 1 : soundTransform.volume); + } + + @:noCompletion private function get_rightPeak():Float + { + __updatePeaks(get_position()); + return __rightPeak * (soundTransform == null ? 1 : soundTransform.volume); + } + + // Event Handlers @:noCompletion private function source_onComplete():Void { @@ -234,14 +402,14 @@ import lime.media.AudioSource; dispatchEvent(new Event(Event.SOUND_COMPLETE)); } - function set___audioSource(value:AudioSource):AudioSource { - return __source = value; + @:noCompletion private function source_onLoop():Void + { + //dispatchEvent(new Event(Event.SOUND_LOOP)); } - function get___audioSource():AudioSource { - return __source; - } + @:noCompletion private function get___audioSource():AudioSource return __source; + @:noCompletion private function set___audioSource(source:AudioSource):AudioSource return __source = source; } #else typedef SoundChannel = flash.media.SoundChannel; -#end \ No newline at end of file +#end diff --git a/source/openfl/utils/Assets.hx b/source/openfl/utils/Assets.hx index db92e530..eef85212 100644 --- a/source/openfl/utils/Assets.hx +++ b/source/openfl/utils/Assets.hx @@ -243,16 +243,15 @@ class Assets public static function getMusic(id:String, useCache:Bool = true):Sound { - #if (lime_vorbis && lime > "7.9.0") - var path = getPath(id); - // TODO: What if it is a WAV or non-Vorbis file? - var vorbisFile = VorbisFile.fromFile(path); - var buffer = AudioBuffer.fromVorbisFile(vorbisFile); - return Sound.fromAudioBuffer(buffer); - #else - // TODO: Streaming sound - return getSound(id, useCache); + #if (lime_vorbis && lime > "7.9.0" && !macro) + if (Options.streamedMusic) { + var path = getPath(id); + // TODO: What if it is a WAV or non-Vorbis file? + var vorbisFile = VorbisFile.fromFile(path); + if (vorbisFile != null) return Sound.fromAudioBuffer(AudioBuffer.fromVorbisFile(vorbisFile)); + } #end + return getSound(id, useCache); } /**