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 7f430cbe..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 @@ -11,4 +10,6 @@ docs/doc.xml mods/* addons/* !mods/readme.txt +addons/* +!addons/readme.txt dev-libs/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 281a6d2d..4a694405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) 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/flashFiles/Alphabet_Bold.fla b/art/flashFiles/Alphabet_Bold.fla index 9ea381ee..b1dc1525 100644 Binary files a/art/flashFiles/Alphabet_Bold.fla and b/art/flashFiles/Alphabet_Bold.fla differ diff --git a/art/flashFiles/TitleEnter.fla b/art/flashFiles/TitleEnter.fla new file mode 100644 index 00000000..2605cfd3 Binary files /dev/null and b/art/flashFiles/TitleEnter.fla differ diff --git a/art/flashFiles/alphabet-letters.fla b/art/flashFiles/alphabet-letters.fla index 4f7a1609..60ef6592 100644 Binary files a/art/flashFiles/alphabet-letters.fla and b/art/flashFiles/alphabet-letters.fla differ diff --git a/art/flashFiles/checkbox.fla b/art/flashFiles/checkbox.fla new file mode 100644 index 00000000..77f8216f Binary files /dev/null and b/art/flashFiles/checkbox.fla 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/alphabet.xml b/assets/data/alphabet.xml index 8ae172cd..252af638 100644 --- a/assets/data/alphabet.xml +++ b/assets/data/alphabet.xml @@ -2,33 +2,76 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/data/alphabet/bold.xml b/assets/data/alphabet/bold.xml new file mode 100644 index 00000000..ee3ac4e9 --- /dev/null +++ b/assets/data/alphabet/bold.xml @@ -0,0 +1,110 @@ + + + menus/alphabet-bold + + character-LETTER0 + character-ampersand + character-anglebracket-left + character-anglebracket-right + character-asterisk + character-at + character-backslash + character-backtick + character-caret + character-colon + character-comma + character-curlybracket-left + character-curlybracket-right + character-dollar + character-doublequote0 + character-equal + character-exclamationmark0 + character-hyphen + character-percent + character-period + character-plus + character-pound + character-questionmark0 + character-roundbracket-left + character-roundbracket-right + character-semicolon + character-singlequote + character-slash + character-squarebracket-left + character-squarebracket-right + character-tilde0 + character-underscore0 + character-verticalbar0 + + character-zero + character-one + character-two + character-three + character-four + character-five + character-six + character-seven + character-eight + character-nine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/data/alphabet/normal.xml b/assets/data/alphabet/normal.xml new file mode 100644 index 00000000..9180d8df --- /dev/null +++ b/assets/data/alphabet/normal.xml @@ -0,0 +1,118 @@ + + + menus/alphabet + + character-LOWERLETTER-capital + character-LOWERLETTER-lowercase + LETTER + + character-g-lowercase + character-j-lowercase + character-p-lowercase + character-q-lowercase + character-y-lowercase + + character-zero + character-one + character-two + character-three + character-four + character-five + character-six + character-seven + character-eight + character-nine + + character-ampersand + character-anglebracket-left + character-anglebracket-right + character-arrow-right + character-arrow-left + character-arrow-down + character-arrow-up + character-asterisk + character-at + character-backslash + character-backtick + character-caret + character-colon + character-comma + character-curlybracket-left + character-curlybracket-right + character-dollar + character-doublequote-start + character-equal + character-exclamationmark + character-minus + character-percent + character-period + character-plus + character-pound + character-questionmark + character-roundbracket-left + character-roundbracket-right + character-semicolon + character-singlequote + character-slash + character-squarebracket-left + character-squarebracket-right + character-tilde + character-underscore + character-verticalbar + + + \ No newline at end of file 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/config/modpack.ini b/assets/data/config/modpack.ini new file mode 100644 index 00000000..313d86b2 --- /dev/null +++ b/assets/data/config/modpack.ini @@ -0,0 +1,25 @@ +[Common] +Name="baconeggandcheese" +Description="who gaf" +APIVersion="uhmmmm probs like 2" +DownloadedFrom="https://yourmom.org" +Dependencies=[ + "linkA", + "linkB", + "linkC" +] +Icon64="path/to/icon64.png" +Icon32="path/to/icon32.png" +Icon16="path/to/icon16.png" +Icon="path/to/icon.ico" + +[Flags] +UseNoteRendererV2=false +UseAway3D=false +UseDiscordRPCIntegration=true +UseLocalization=false +UseLegacyFlixelAnimateHitbox=false + +[Discord] +ClientID="1027994136193810442", +LogoKey="icon" \ No newline at end of file diff --git a/assets/data/editors/layouts/stage/characterEditScreen.xml b/assets/data/editors/layouts/stage/characterEditScreen.xml index 2658f4ac..31f0e5ea 100644 --- a/assets/data/editors/layouts/stage/characterEditScreen.xml +++ b/assets/data/editors/layouts/stage/characterEditScreen.xml @@ -1,4 +1,4 @@ - + @@ -16,7 +16,7 @@ } - + <title name="title" x="20" y="30 + 16" text="${translate('character-title')}" /> <section> <set name="curY" value="title.y + TO" /> @@ -24,51 +24,51 @@ <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="scrollXStepper" label="${translate('scroll', ['X'])}" value="char.scrollFactor.x" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scrollYStepper" label="${translate('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" /> + <stepper name="scaleXStepper" label="${translate('scale', ['X'])}" value="char.scale.x / charScale" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scaleYStepper" label="${translate('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" /> + <stepper name="camxStepper" label="${translate('camera', ['X'])}" value="getEx('camX')" x="col(0)" y="curY" step="1" width="100" /> + <stepper name="camyStepper" label="${translate('camera', ['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" /> + <label name="spacingLabel" text="${translate('spacing')}" 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" /> + <stepper name="skewXStepper" label="${translate('skew', ['X'])}" value="char.skew.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="skewYStepper" label="${translate('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="alphaStepper" label="${translate('alpha')}" value="char.alpha / 0.75" x="col(0)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="angleStepper" label="${translate('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" /> + <stepper name="zoomFactorStepper" label="${translate('zoomFactor')" 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"/> + <textbox name="charName" label="${translate('positionName')}" 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" /> + <checkbox name="flipXCheckbox" text="${translate('flipX')}" 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" /> + <radio for="memoryCheck" name="highMemoryRadio" text="${translate('highMemory')}" value="getEx('highMemory')" x="last.field.x + last.field.width + XOFFSET * 2" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="${translate('lowMemory')}" 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" /> + <title name="previewTitle" x="20" y="self.winHeight - 220" text="${translate('transformPreview')}" size="15" /> </section> <section if="previewSprite == null"> <exec> diff --git a/assets/data/editors/layouts/stage/spriteEditScreen.xml b/assets/data/editors/layouts/stage/spriteEditScreen.xml index 651756a0..de6e70bb 100644 --- a/assets/data/editors/layouts/stage/spriteEditScreen.xml +++ b/assets/data/editors/layouts/stage/spriteEditScreen.xml @@ -1,4 +1,4 @@ -<window width="1056" height="520" title="Editing Sprite ${button.getName()}"> +<window width="1056" height="520" title="${translate('win-sprite-title', [button.getName()])}"> <set name="MARGIN" value="20" /> <set name="XOFFSET" value="50" /> <set name="YOFFSET" value="40" /> @@ -17,15 +17,15 @@ } </exec> - <title name="title" x="20" y="30 + 16" text="Sprite Data" /> + <title name="title" x="20" y="30 + 16" text="${translate('sprite-title')}" /> <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="nameTextBox" label="${translate('nameID')}" 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" /> + <label name="imageFile" text="${translate('imageFile')}" x="spriteTextBox.x" y="spriteTextBox.y - 14" size="15" /> </section> <section> <!-- ROW 2 --> @@ -34,36 +34,36 @@ <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="scrollXStepper" label="${translate('scroll', ['X'])}" value="sprite.scrollFactor.x" x="spriteTextBox.x" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scrollYStepper" label="${translate('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" /> + <stepper name="scaleXStepper" label="${translate('scale', ['X'])}" value="sprite.scale.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scaleYStepper" label="${translate('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" /> + <stepper name="angleStepper" label="${translate('angle')}" value="sprite.angle" x="col(0)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="zoomFactorStepper" label="${translate('zoomFactor')}" 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 name="animType" label="${translate('animType')}" value="sprite.spriteAnimType" x="col(1)" y="curY" width="150" height="32"> + <item label="${translate('animType.none')}" value="0" /> + <item label="${translate('animType.beat')}" value="1" /> + <item label="${translate('animType.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" /> + <stepper name="skewXStepper" label="${translate('skew', ['X'])}" value="sprite.skew.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="skewYStepper" label="${translate('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" /> + <checkbox name="antialiasingCheckbox" text="${translate('antialiasing')}" value="sprite.antialiasing" x="col(0)" y="curY" /> + <radio for="memoryCheck" name="highMemoryRadio" text="${translate('highMemory')}" value="getEx('highMemory')" x="col(1)" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="${translate('lowMemory')}" value="getEx('lowMemory')" x="col(2)" y="curY" /> <!-- TODO: Animation list --> </section> @@ -72,7 +72,7 @@ <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"> + <buttonlist name="animations" label="${translate('animations')}" x="col(3)" y="curY" width="listWidth" height="330" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5"> <texture path="editors/ui/inputbox" /> <camSpacing value="0" /> @@ -103,14 +103,14 @@ </code> --> </buttonlist> - <text text="Tip: To access custom properties, hold shift when clicking edit" x="col(2) - 60" y="self.winHeight - 50" size="15" /> + <text text="${translate('tipCustomProperties')}" 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" /> + <title name="previewTitle" x="20" y="self.winHeight - 150" text="${translate('transformPreview')}" size="15" /> </section> <exec> diff --git a/assets/data/editors/layouts/stage/stageInfoScreen.xml b/assets/data/editors/layouts/stage/stageInfoScreen.xml index 1a477da8..9cfa1b28 100644 --- a/assets/data/editors/layouts/stage/stageInfoScreen.xml +++ b/assets/data/editors/layouts/stage/stageInfoScreen.xml @@ -1,4 +1,4 @@ -<window width="470" height="620" title="Editing Stage Info '${stage.stageName}'"> +<window width="470" height="620" title="${translate('win-title', [stage.stageName])}"> <set name="MARGIN" value="20" /> <set name="XOFFSET" value="50" /> <set name="YOFFSET" value="40" /> @@ -28,31 +28,31 @@ } </exec> - <title name="title" x="MARGIN" y="30 + 16" text="Stage Info" /> + <title name="title" x="MARGIN" y="30 + 16" text="${translate('title')}" /> <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="nameTextBox" label="${translate('stageName')}" 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" /> + <label text="${translate('spritePath')}" 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" /> + <label text="${translate('startCamPos')}" 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" /> + <stepper name="zoomStepper" label="${translate('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"> + <buttonlist name="attributesList" label="${translate('customAttributes')}" x="col(0)" y="curY" width="listWidth" height="290" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5"> <texture path="editors/ui/inputbox" /> <camSpacing value="0" /> diff --git a/assets/data/editors/layouts/stage/unknownEditScreen.xml b/assets/data/editors/layouts/stage/unknownEditScreen.xml index ec6f3d71..913574ca 100644 --- a/assets/data/editors/layouts/stage/unknownEditScreen.xml +++ b/assets/data/editors/layouts/stage/unknownEditScreen.xml @@ -1,4 +1,4 @@ -<window width="350" height="570" title="Editing XML <${button.getName()} />"> +<window width="350" height="570" title="${translate('win-title', [button.getName()])}"> <set name="MARGIN" value="20" /> <set name="XOFFSET" value="30" /> <set name="YOFFSET" value="40" /> @@ -10,25 +10,25 @@ } </exec> - <title name="title" x="MARGIN" y="30 + 16" text="Xml Data" /> + <title name="title" x="MARGIN" y="30 + 16" text="${translate('title')}" /> <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" /> + <textbox name="nodeNameTextBox" label="${translate('elementName')}" 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" /> + <radio for="memoryCheck" name="highMemoryRadio" text="${translate('highMemory')}" value="button.highMemory" x="col(0)" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="${translate('lowMemory')}" 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"> + <buttonlist name="attributesList" label="${translate('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" /> diff --git a/assets/data/editors/layouts/stage/xmlEditScreen.xml b/assets/data/editors/layouts/stage/xmlEditScreen.xml index 7a7ac956..f5885126 100644 --- a/assets/data/editors/layouts/stage/xmlEditScreen.xml +++ b/assets/data/editors/layouts/stage/xmlEditScreen.xml @@ -1,4 +1,4 @@ -<window width="450" height="570" title="Editing XML ${type}"> +<window width="450" height="570" title="${translate('win-title', [type])}"> <!-- BASED ON unknownEditScreen.xml --> <set name="MARGIN" value="20" /> <set name="XOFFSET" value="30" /> @@ -11,13 +11,13 @@ } </exec> - <title name="title" x="MARGIN" y="30 + 16" text="Xml Data" /> + <title name="title" x="MARGIN" y="30 + 16" text="${translate('title')}" /> <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"> + <buttonlist name="attributesList" label="${translate('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" /> 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 26d18251..7031838e 100644 --- a/assets/data/scripts/pixel.hx +++ b/assets/data/scripts/pixel.hx @@ -18,9 +18,7 @@ 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; @@ -38,18 +36,14 @@ function onNoteCreation(event) { 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; @@ -64,6 +58,7 @@ function onStrumCreation(event) { var strumScale = strumLines.members[event.player].strumScale; strum.scale.set(daPixelZoom*strumScale, daPixelZoom*strumScale); strum.updateHitbox(); + strum.antialiasing = false; } function onCountdown(event) { @@ -94,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; @@ -105,9 +100,6 @@ function postCreate() { defaultCamZoom /= daPixelZoom; } - iconP1.antialiasing = false; - iconP2.antialiasing = false; - if (enablePixelGameOver) { gameOverSong = "pixel/gameOver"; lossSFX = "pixel/gameOverSFX"; @@ -115,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); @@ -130,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!) @@ -156,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/titlescreen/titlescreen.xml b/assets/data/titlescreen/titlescreen.xml index 323e9ef1..fbd8fbd7 100644 --- a/assets/data/titlescreen/titlescreen.xml +++ b/assets/data/titlescreen/titlescreen.xml @@ -15,14 +15,14 @@ <line text="ninjamuffin99" /> <line text="phantomArcade" /> <line text="kawaisprite" /> - <line text="evilsk8er" /> + <line text="evilsk8r" /> </text> <text beat="3"> <line text="ninjamuffin99" /> <line text="phantomArcade" /> <line text="kawaisprite" /> - <line text="evilsk8er" /> + <line text="evilsk8r" /> <line text="presents" /> </text> 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/fonts/StatusPlz.ttf b/assets/fonts/StatusPlz.ttf new file mode 100644 index 00000000..0f31536f Binary files /dev/null and b/assets/fonts/StatusPlz.ttf differ diff --git a/assets/fonts/vcr.ttf b/assets/fonts/vcr.ttf index dcca687a..24a56b5d 100644 Binary files a/assets/fonts/vcr.ttf and b/assets/fonts/vcr.ttf differ 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/edit-button.png b/assets/images/editors/character/edit-button.png index c0ad7449..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/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/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.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.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.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/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/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/alphabet-bold.png b/assets/images/menus/alphabet-bold.png index 012721ce..8ded5848 100644 Binary files a/assets/images/menus/alphabet-bold.png and b/assets/images/menus/alphabet-bold.png differ diff --git a/assets/images/menus/alphabet-bold.xml b/assets/images/menus/alphabet-bold.xml index 26ce48db..258ad097 100644 --- a/assets/images/menus/alphabet-bold.xml +++ b/assets/images/menus/alphabet-bold.xml @@ -1,153 +1,282 @@ -<?xml version="1.0" encoding="utf-8"?> -<TextureAtlas imagePath="Alphabet_Bold.png"> - <!-- Created with Adobe Animate version 23.0.0.407 --> - <!-- http://www.adobe.com/products/animate.html --> - <SubTexture name="0 bold0000" x="0" y="0" width="56" height="74"/> - <SubTexture name="0 bold0001" x="0" y="0" width="56" height="74"/> - <SubTexture name="0 bold0002" x="56" y="0" width="56" height="74"/> - <SubTexture name="0 bold0003" x="56" y="0" width="56" height="74"/> - <SubTexture name="1 bold0000" x="112" y="0" width="52" height="70"/> - <SubTexture name="1 bold0001" x="112" y="0" width="52" height="70"/> - <SubTexture name="1 bold0002" x="164" y="0" width="52" height="70"/> - <SubTexture name="1 bold0003" x="164" y="0" width="52" height="70"/> - <SubTexture name="2 bold0000" x="216" y="0" width="59" height="73"/> - <SubTexture name="2 bold0001" x="216" y="0" width="59" height="73"/> - <SubTexture name="2 bold0002" x="275" y="0" width="59" height="73"/> - <SubTexture name="2 bold0003" x="275" y="0" width="59" height="73"/> - <SubTexture name="3 bold0000" x="334" y="0" width="57" height="72"/> - <SubTexture name="3 bold0001" x="334" y="0" width="57" height="72"/> - <SubTexture name="3 bold0002" x="391" y="0" width="56" height="72" frameX="0" frameY="0" frameWidth="57" frameHeight="72"/> - <SubTexture name="3 bold0003" x="391" y="0" width="56" height="72" frameX="0" frameY="0" frameWidth="57" frameHeight="72"/> - <SubTexture name="4 bold0000" x="447" y="0" width="58" height="69"/> - <SubTexture name="4 bold0001" x="447" y="0" width="58" height="69"/> - <SubTexture name="4 bold0002" x="505" y="0" width="58" height="69"/> - <SubTexture name="4 bold0003" x="505" y="0" width="58" height="69"/> - <SubTexture name="5 bold0000" x="563" y="0" width="56" height="71" frameX="0" frameY="0" frameWidth="56" frameHeight="72"/> - <SubTexture name="5 bold0001" x="563" y="0" width="56" height="71" frameX="0" frameY="0" frameWidth="56" frameHeight="72"/> - <SubTexture name="5 bold0002" x="619" y="0" width="56" height="72"/> - <SubTexture name="5 bold0003" x="619" y="0" width="56" height="72"/> - <SubTexture name="6 bold0000" x="675" y="0" width="53" height="68"/> - <SubTexture name="6 bold0001" x="675" y="0" width="53" height="68"/> - <SubTexture name="6 bold0002" x="728" y="0" width="53" height="68"/> - <SubTexture name="6 bold0003" x="728" y="0" width="53" height="68"/> - <SubTexture name="7 bold0000" x="781" y="0" width="54" height="73"/> - <SubTexture name="7 bold0001" x="781" y="0" width="54" height="73"/> - <SubTexture name="7 bold0002" x="835" y="0" width="54" height="73"/> - <SubTexture name="7 bold0003" x="835" y="0" width="54" height="73"/> - <SubTexture name="8 bold0000" x="889" y="0" width="54" height="70"/> - <SubTexture name="8 bold0001" x="889" y="0" width="54" height="70"/> - <SubTexture name="8 bold0002" x="943" y="0" width="54" height="70"/> - <SubTexture name="8 bold0003" x="943" y="0" width="54" height="70"/> - <SubTexture name="9 bold0000" x="0" y="74" width="53" height="68"/> - <SubTexture name="9 bold0001" x="0" y="74" width="53" height="68"/> - <SubTexture name="9 bold0002" x="53" y="74" width="53" height="67" frameX="0" frameY="0" frameWidth="53" frameHeight="68"/> - <SubTexture name="9 bold0003" x="53" y="74" width="53" height="67" frameX="0" frameY="0" frameWidth="53" frameHeight="68"/> - <SubTexture name="> Bold0000" x="106" y="74" width="51" height="73" frameX="0" frameY="0" frameWidth="52" frameHeight="73"/> - <SubTexture name="> Bold0001" x="106" y="74" width="51" height="73" frameX="0" frameY="0" frameWidth="52" frameHeight="73"/> - <SubTexture name="> Bold0002" x="157" y="74" width="52" height="73"/> - <SubTexture name="> Bold0003" x="157" y="74" width="52" height="73"/> - <SubTexture name="A bold0000" x="209" y="74" width="54" height="67"/> - <SubTexture name="A bold0001" x="209" y="74" width="54" height="67"/> - <SubTexture name="A bold0002" x="263" y="74" width="54" height="67"/> - <SubTexture name="A bold0003" x="263" y="74" width="54" height="67"/> - <SubTexture name="B bold0000" x="317" y="74" width="46" height="70"/> - <SubTexture name="B bold0001" x="317" y="74" width="46" height="70"/> - <SubTexture name="B bold0002" x="363" y="74" width="46" height="70"/> - <SubTexture name="B bold0003" x="363" y="74" width="46" height="70"/> - <SubTexture name="C bold0000" x="409" y="74" width="55" height="66"/> - <SubTexture name="C bold0001" x="409" y="74" width="55" height="66"/> - <SubTexture name="C bold0002" x="464" y="74" width="54" height="65" frameX="0" frameY="-1" frameWidth="55" frameHeight="66"/> - <SubTexture name="C bold0003" x="464" y="74" width="54" height="65" frameX="0" frameY="-1" frameWidth="55" frameHeight="66"/> - <SubTexture name="D bold0000" x="518" y="74" width="53" height="67"/> - <SubTexture name="D bold0001" x="518" y="74" width="53" height="67"/> - <SubTexture name="D bold0002" x="571" y="74" width="53" height="67"/> - <SubTexture name="D bold0003" x="571" y="74" width="53" height="67"/> - <SubTexture name="E bold0000" x="624" y="74" width="43" height="66"/> - <SubTexture name="E bold0001" x="624" y="74" width="43" height="66"/> - <SubTexture name="E bold0002" x="667" y="74" width="43" height="66"/> - <SubTexture name="E bold0003" x="667" y="74" width="43" height="66"/> - <SubTexture name="F bold0000" x="710" y="74" width="43" height="67"/> - <SubTexture name="F bold0001" x="710" y="74" width="43" height="67"/> - <SubTexture name="F bold0002" x="753" y="74" width="43" height="67"/> - <SubTexture name="F bold0003" x="753" y="74" width="43" height="67"/> - <SubTexture name="G bold0000" x="796" y="74" width="54" height="70"/> - <SubTexture name="G bold0001" x="796" y="74" width="54" height="70"/> - <SubTexture name="G bold0002" x="850" y="74" width="54" height="70"/> - <SubTexture name="G bold0003" x="850" y="74" width="54" height="70"/> - <SubTexture name="H bold0000" x="904" y="74" width="45" height="66"/> - <SubTexture name="H bold0001" x="904" y="74" width="45" height="66"/> - <SubTexture name="H bold0002" x="949" y="74" width="44" height="66" frameX="0" frameY="0" frameWidth="45" frameHeight="66"/> - <SubTexture name="H bold0003" x="949" y="74" width="44" height="66" frameX="0" frameY="0" frameWidth="45" frameHeight="66"/> - <SubTexture name="I bold0000" x="0" y="147" width="43" height="64"/> - <SubTexture name="I bold0001" x="0" y="147" width="43" height="64"/> - <SubTexture name="I bold0002" x="43" y="147" width="43" height="64"/> - <SubTexture name="I bold0003" x="43" y="147" width="43" height="64"/> - <SubTexture name="J bold0000" x="86" y="147" width="54" height="70"/> - <SubTexture name="J bold0001" x="86" y="147" width="54" height="70"/> - <SubTexture name="J bold0002" x="140" y="147" width="54" height="70"/> - <SubTexture name="J bold0003" x="140" y="147" width="54" height="70"/> - <SubTexture name="K bold0000" x="194" y="147" width="44" height="69"/> - <SubTexture name="K bold0001" x="194" y="147" width="44" height="69"/> - <SubTexture name="K bold0002" x="238" y="147" width="44" height="69"/> - <SubTexture name="K bold0003" x="238" y="147" width="44" height="69"/> - <SubTexture name="L bold0000" x="282" y="147" width="41" height="66"/> - <SubTexture name="L bold0001" x="282" y="147" width="41" height="66"/> - <SubTexture name="L bold0002" x="323" y="147" width="41" height="66"/> - <SubTexture name="L bold0003" x="323" y="147" width="41" height="66"/> - <SubTexture name="M bold0000" x="364" y="147" width="56" height="63"/> - <SubTexture name="M bold0001" x="364" y="147" width="56" height="63"/> - <SubTexture name="M bold0002" x="420" y="147" width="56" height="63"/> - <SubTexture name="M bold0003" x="420" y="147" width="56" height="63"/> - <SubTexture name="N bold0000" x="476" y="147" width="45" height="65"/> - <SubTexture name="N bold0001" x="476" y="147" width="45" height="65"/> - <SubTexture name="N bold0002" x="521" y="147" width="45" height="65"/> - <SubTexture name="N bold0003" x="521" y="147" width="45" height="65"/> - <SubTexture name="O bold0000" x="566" y="147" width="50" height="69"/> - <SubTexture name="O bold0001" x="566" y="147" width="50" height="69"/> - <SubTexture name="O bold0002" x="616" y="147" width="50" height="69"/> - <SubTexture name="O bold0003" x="616" y="147" width="50" height="69"/> - <SubTexture name="P bold0000" x="666" y="147" width="46" height="70"/> - <SubTexture name="P bold0001" x="666" y="147" width="46" height="70"/> - <SubTexture name="P bold0002" x="712" y="147" width="46" height="70"/> - <SubTexture name="P bold0003" x="712" y="147" width="46" height="70"/> - <SubTexture name="Q bold0000" x="758" y="147" width="52" height="67"/> - <SubTexture name="Q bold0001" x="758" y="147" width="52" height="67"/> - <SubTexture name="Q bold0002" x="810" y="147" width="52" height="67"/> - <SubTexture name="Q bold0003" x="810" y="147" width="52" height="67"/> - <SubTexture name="R bold0000" x="862" y="147" width="47" height="66"/> - <SubTexture name="R bold0001" x="862" y="147" width="47" height="66"/> - <SubTexture name="R bold0002" x="909" y="147" width="47" height="66"/> - <SubTexture name="R bold0003" x="909" y="147" width="47" height="66"/> - <SubTexture name="S bold0000" x="956" y="147" width="49" height="66"/> - <SubTexture name="S bold0001" x="956" y="147" width="49" height="66"/> - <SubTexture name="S bold0002" x="0" y="217" width="49" height="66"/> - <SubTexture name="S bold0003" x="0" y="217" width="49" height="66"/> - <SubTexture name="T bold0000" x="49" y="217" width="44" height="64"/> - <SubTexture name="T bold0001" x="49" y="217" width="44" height="64"/> - <SubTexture name="T bold0002" x="93" y="217" width="44" height="64"/> - <SubTexture name="T bold0003" x="93" y="217" width="44" height="64"/> - <SubTexture name="U bold0000" x="137" y="217" width="44" height="59"/> - <SubTexture name="U bold0001" x="137" y="217" width="44" height="59"/> - <SubTexture name="U bold0002" x="181" y="217" width="44" height="59"/> - <SubTexture name="U bold0003" x="181" y="217" width="44" height="59"/> - <SubTexture name="V bold0000" x="225" y="217" width="54" height="66"/> - <SubTexture name="V bold0001" x="225" y="217" width="54" height="66"/> - <SubTexture name="V bold0002" x="279" y="217" width="54" height="66"/> - <SubTexture name="V bold0003" x="279" y="217" width="54" height="66"/> - <SubTexture name="W bold0000" x="333" y="217" width="58" height="63"/> - <SubTexture name="W bold0001" x="333" y="217" width="58" height="63"/> - <SubTexture name="W bold0002" x="391" y="217" width="58" height="63"/> - <SubTexture name="W bold0003" x="391" y="217" width="58" height="63"/> - <SubTexture name="X bold0000" x="449" y="217" width="55" height="67"/> - <SubTexture name="X bold0001" x="449" y="217" width="55" height="67"/> - <SubTexture name="X bold0002" x="504" y="217" width="55" height="67"/> - <SubTexture name="X bold0003" x="504" y="217" width="55" height="67"/> - <SubTexture name="Y bold0000" x="559" y="217" width="54" height="69"/> - <SubTexture name="Y bold0001" x="559" y="217" width="54" height="69"/> - <SubTexture name="Y bold0002" x="613" y="217" width="54" height="69"/> - <SubTexture name="Y bold0003" x="613" y="217" width="54" height="69"/> - <SubTexture name="Z bold0000" x="667" y="217" width="52" height="65"/> - <SubTexture name="Z bold0001" x="667" y="217" width="52" height="65"/> - <SubTexture name="Z bold0002" x="719" y="217" width="52" height="65"/> - <SubTexture name="Z bold0003" x="719" y="217" width="52" height="65"/> -</TextureAtlas> +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="alphabet-bold.png" width="866" height="861"> + <SubTexture name="character-a0000" x="713" y="1" width="54" height="67" frameX="-0" frameY="-0" frameWidth="54" frameHeight="67"/> + <SubTexture name="character-a0001" x="713" y="71" width="54" height="67" frameX="-0" frameY="-0" frameWidth="54" frameHeight="67"/> + <SubTexture name="character-ampersand0000" x="251" y="77" width="60" height="72" frameX="-0" frameY="-0" frameWidth="60" frameHeight="72"/> + <SubTexture name="character-ampersand0001" x="251" y="152" width="60" height="72" frameX="-0" frameY="-0" frameWidth="60" frameHeight="72"/> + <SubTexture name="character-anglebracket-left0000" x="245" y="452" width="42" height="70" frameX="-0" frameY="-0" frameWidth="42" frameHeight="70"/> + <SubTexture name="character-anglebracket-left0001" x="290" y="452" width="42" height="70" frameX="-0" frameY="-0" frameWidth="42" frameHeight="70"/> + <SubTexture name="character-anglebracket-right0000" x="56" y="324" width="45" height="71" frameX="-0" frameY="-0" frameWidth="45" frameHeight="71"/> + <SubTexture name="character-anglebracket-right0001" x="104" y="324" width="45" height="71" frameX="-0" frameY="-0" frameWidth="45" frameHeight="71"/> + <SubTexture name="character-angryfaic0000" x="152" y="304" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-angryfaic0001" x="226" y="304" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-angryfaic-alt0000" x="713" y="715" width="54" height="54" frameX="-0" frameY="-0" frameWidth="54" frameHeight="54"/> + <SubTexture name="character-angryfaic-alt0001" x="276" y="763" width="54" height="54" frameX="-0" frameY="-0" frameWidth="54" frameHeight="54"/> + <SubTexture name="character-arrow-down0000" x="370" y="1" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-down0001" x="370" y="75" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-down-alt0000" x="660" y="720" width="48" height="53" frameX="-0" frameY="-0" frameWidth="48" frameHeight="53"/> + <SubTexture name="character-arrow-down-alt0001" x="333" y="763" width="48" height="53" frameX="-0" frameY="-0" frameWidth="48" frameHeight="53"/> + <SubTexture name="character-arrow-left0000" x="367" y="149" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-left0001" x="367" y="223" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-left-alt0000" x="384" y="763" width="51" height="48" frameX="-0" frameY="-0" frameWidth="51" frameHeight="48"/> + <SubTexture name="character-arrow-left-alt0001" x="438" y="763" width="51" height="48" frameX="-0" frameY="-0" frameWidth="51" frameHeight="48"/> + <SubTexture name="character-arrow-right0000" x="360" y="297" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-right0001" x="1" y="399" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-right-alt0000" x="492" y="776" width="51" height="46" frameX="-0" frameY="-0" frameWidth="51" frameHeight="46"/> + <SubTexture name="character-arrow-right-alt0001" x="546" y="776" width="51" height="46" frameX="-0" frameY="-0" frameWidth="51" frameHeight="46"/> + <SubTexture name="character-arrow-up0000" x="75" y="398" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-up0001" x="149" y="398" width="71" height="71" frameX="-0" frameY="-0" frameWidth="71" frameHeight="71"/> + <SubTexture name="character-arrow-up-alt0000" x="600" y="763" width="45" height="51" frameX="-0" frameY="-0" frameWidth="45" frameHeight="51"/> + <SubTexture name="character-arrow-up-alt0001" x="770" y="1" width="45" height="51" frameX="-0" frameY="-0" frameWidth="45" frameHeight="51"/> + <SubTexture name="character-ash0000" x="175" y="1" width="73" height="73" frameX="-0" frameY="-0" frameWidth="73" frameHeight="73"/> + <SubTexture name="character-ash0001" x="175" y="77" width="73" height="73" frameX="-0" frameY="-0" frameWidth="73" frameHeight="73"/> + <SubTexture name="character-asterisk0000" x="817" y="409" width="40" height="33" frameX="-0" frameY="-0" frameWidth="40" frameHeight="33"/> + <SubTexture name="character-asterisk0001" x="795" y="445" width="40" height="33" frameX="-0" frameY="-0" frameWidth="40" frameHeight="33"/> + <SubTexture name="character-at0000" x="141" y="153" width="68" height="73" frameX="-0" frameY="-0" frameWidth="68" frameHeight="73"/> + <SubTexture name="character-at0001" x="251" y="1" width="68" height="73" frameX="-0" frameY="-0" frameWidth="68" frameHeight="73"/> + <SubTexture name="character-b0000" x="335" y="452" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-b0001" x="384" y="450" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-backslash0000" x="433" y="445" width="51" height="70" frameX="-0" frameY="-0" frameWidth="51" frameHeight="70"/> + <SubTexture name="character-backslash0001" x="506" y="1" width="51" height="70" frameX="-0" frameY="-0" frameWidth="51" frameHeight="70"/> + <SubTexture name="character-backtick0000" x="829" y="799" width="33" height="31" frameX="-0" frameY="-0" frameWidth="33" frameHeight="31"/> + <SubTexture name="character-backtick0001" x="214" y="825" width="33" height="31" frameX="-0" frameY="-0" frameWidth="33" frameHeight="31"/> + <SubTexture name="character-c0000" x="223" y="378" width="58" height="71" frameX="-0" frameY="-0" frameWidth="58" frameHeight="71"/> + <SubTexture name="character-c0001" x="284" y="378" width="58" height="71" frameX="-0" frameY="-0" frameWidth="58" frameHeight="71"/> + <SubTexture name="character-caret0000" x="817" y="131" width="46" height="34" frameX="-0" frameY="-0" frameWidth="46" frameHeight="34"/> + <SubTexture name="character-caret0001" x="817" y="168" width="46" height="34" frameX="-0" frameY="-0" frameWidth="46" frameHeight="34"/> + <SubTexture name="character-colon0000" x="770" y="55" width="22" height="49" frameX="-0" frameY="-0" frameWidth="22" frameHeight="49"/> + <SubTexture name="character-colon0001" x="770" y="435" width="22" height="49" frameX="-0" frameY="-0" frameWidth="22" frameHeight="49"/> + <SubTexture name="character-comma0000" x="838" y="445" width="25" height="32" frameX="-0" frameY="-0" frameWidth="25" frameHeight="32"/> + <SubTexture name="character-comma0001" x="838" y="675" width="25" height="32" frameX="-0" frameY="-0" frameWidth="25" frameHeight="32"/> + <SubTexture name="character-curlybracket-left0000" x="300" y="302" width="46" height="71" frameX="-0" frameY="-0" frameWidth="46" frameHeight="71"/> + <SubTexture name="character-curlybracket-left0001" x="345" y="376" width="46" height="71" frameX="-0" frameY="-0" frameWidth="46" frameHeight="71"/> + <SubTexture name="character-curlybracket-right0000" x="468" y="371" width="47" height="70" frameX="-0" frameY="-0" frameWidth="47" frameHeight="70"/> + <SubTexture name="character-curlybracket-right0001" x="506" y="74" width="47" height="70" frameX="-0" frameY="-0" frameWidth="47" frameHeight="70"/> + <SubTexture name="character-d0000" x="653" y="365" width="53" height="68" frameX="-0" frameY="-0" frameWidth="53" frameHeight="68"/> + <SubTexture name="character-d0001" x="649" y="436" width="53" height="68" frameX="-0" frameY="-0" frameWidth="53" frameHeight="68"/> + <SubTexture name="character-dollar0000" x="506" y="147" width="47" height="70" frameX="-0" frameY="-0" frameWidth="47" frameHeight="70"/> + <SubTexture name="character-dollar0001" x="502" y="220" width="47" height="70" frameX="-0" frameY="-0" frameWidth="47" frameHeight="70"/> + <SubTexture name="character-doublequote0000" x="818" y="1" width="47" height="32" frameX="-0" frameY="-0" frameWidth="47" frameHeight="32"/> + <SubTexture name="character-doublequote0001" x="818" y="36" width="47" height="32" frameX="-0" frameY="-0" frameWidth="47" frameHeight="32"/> + <SubTexture name="character-doublequote-alt0000" x="770" y="159" width="43" height="45" frameX="-0" frameY="-0" frameWidth="43" frameHeight="45"/> + <SubTexture name="character-doublequote-alt0001" x="770" y="207" width="43" height="45" frameX="-0" frameY="-0" frameWidth="43" frameHeight="45"/> + <SubTexture name="character-e0000" x="705" y="577" width="43" height="66" frameX="-0" frameY="-0" frameWidth="43" frameHeight="66"/> + <SubTexture name="character-e0001" x="708" y="289" width="43" height="66" frameX="-0" frameY="-0" frameWidth="43" frameHeight="66"/> + <SubTexture name="character-eight0000" x="699" y="507" width="49" height="67" frameX="-0" frameY="-0" frameWidth="49" frameHeight="67"/> + <SubTexture name="character-eight0001" x="705" y="436" width="49" height="67" frameX="-0" frameY="-0" frameWidth="49" frameHeight="67"/> + <SubTexture name="character-equal0000" x="751" y="562" width="48" height="34" frameX="-0" frameY="-0" frameWidth="48" frameHeight="34"/> + <SubTexture name="character-equal0001" x="751" y="599" width="48" height="34" frameX="-0" frameY="-0" frameWidth="48" frameHeight="34"/> + <SubTexture name="character-ethel0000" x="93" y="1" width="79" height="71" frameX="-0" frameY="-0" frameWidth="79" frameHeight="71"/> + <SubTexture name="character-ethel0001" x="93" y="75" width="79" height="71" frameX="-0" frameY="-0" frameWidth="79" frameHeight="71"/> + <SubTexture name="character-exclamationmark0000" x="212" y="153" width="33" height="72" frameX="-0" frameY="-0" frameWidth="33" frameHeight="72"/> + <SubTexture name="character-exclamationmark0001" x="1" y="249" width="33" height="72" frameX="-0" frameY="-0" frameWidth="33" frameHeight="72"/> + <SubTexture name="character-exclamationmark-alt0000" x="1" y="87" width="29" height="76" frameX="-0" frameY="-0" frameWidth="29" frameHeight="76"/> + <SubTexture name="character-exclamationmark-alt0001" x="109" y="149" width="29" height="76" frameX="-0" frameY="-0" frameWidth="29" frameHeight="76"/> + <SubTexture name="character-f0000" x="653" y="507" width="43" height="68" frameX="-0" frameY="-0" frameWidth="43" frameHeight="68"/> + <SubTexture name="character-f0001" x="662" y="289" width="43" height="68" frameX="-0" frameY="-0" frameWidth="43" frameHeight="68"/> + <SubTexture name="character-five0000" x="37" y="249" width="47" height="72" frameX="-0" frameY="-0" frameWidth="47" frameHeight="72"/> + <SubTexture name="character-five0001" x="87" y="249" width="47" height="72" frameX="-0" frameY="-0" frameWidth="47" frameHeight="72"/> + <SubTexture name="character-four0000" x="544" y="439" width="53" height="69" frameX="-0" frameY="-0" frameWidth="53" frameHeight="69"/> + <SubTexture name="character-four0001" x="544" y="511" width="53" height="69" frameX="-0" frameY="-0" frameWidth="53" frameHeight="69"/> + <SubTexture name="character-g0000" x="248" y="227" width="54" height="72" frameX="-0" frameY="-0" frameWidth="54" frameHeight="72"/> + <SubTexture name="character-g0001" x="137" y="229" width="54" height="72" frameX="-0" frameY="-0" frameWidth="54" frameHeight="72"/> + <SubTexture name="character-h0000" x="560" y="1" width="45" height="69" frameX="-0" frameY="-0" frameWidth="45" frameHeight="69"/> + <SubTexture name="character-h0001" x="555" y="366" width="45" height="69" frameX="-0" frameY="-0" frameWidth="45" frameHeight="69"/> + <SubTexture name="character-heart0000" x="608" y="1" width="67" height="69" frameX="-0" frameY="-0" frameWidth="67" frameHeight="69"/> + <SubTexture name="character-heart0001" x="592" y="293" width="67" height="69" frameX="-0" frameY="-0" frameWidth="67" frameHeight="69"/> + <SubTexture name="character-heart-alt0000" x="160" y="763" width="55" height="54" frameX="-0" frameY="-0" frameWidth="55" frameHeight="54"/> + <SubTexture name="character-heart-alt0001" x="218" y="763" width="55" height="54" frameX="-0" frameY="-0" frameWidth="55" frameHeight="54"/> + <SubTexture name="character-hyphen0000" x="757" y="487" width="49" height="20" frameX="-0" frameY="-0" frameWidth="49" frameHeight="20"/> + <SubTexture name="character-hyphen0001" x="648" y="776" width="49" height="20" frameX="-0" frameY="-0" frameWidth="49" frameHeight="20"/> + <SubTexture name="character-i0000" x="697" y="141" width="43" height="66" frameX="-0" frameY="-0" frameWidth="43" frameHeight="66"/> + <SubTexture name="character-i0001" x="709" y="358" width="43" height="66" frameX="-0" frameY="-0" frameWidth="43" frameHeight="66"/> + <SubTexture name="character-j0000" x="487" y="444" width="54" height="70" frameX="-0" frameY="-0" frameWidth="54" frameHeight="70"/> + <SubTexture name="character-j0001" x="1" y="547" width="54" height="70" frameX="-0" frameY="-0" frameWidth="54" frameHeight="70"/> + <SubTexture name="character-k0000" x="194" y="229" width="45" height="72" frameX="-0" frameY="-0" frameWidth="45" frameHeight="72"/> + <SubTexture name="character-k0001" x="322" y="1" width="45" height="72" frameX="-0" frameY="-0" frameWidth="45" frameHeight="72"/> + <SubTexture name="character-l0000" x="603" y="365" width="43" height="69" frameX="-0" frameY="-0" frameWidth="43" frameHeight="69"/> + <SubTexture name="character-l0001" x="603" y="437" width="43" height="69" frameX="-0" frameY="-0" frameWidth="43" frameHeight="69"/> + <SubTexture name="character-m0000" x="58" y="547" width="61" height="70" frameX="-0" frameY="-0" frameWidth="61" frameHeight="70"/> + <SubTexture name="character-m0001" x="122" y="547" width="61" height="70" frameX="-0" frameY="-0" frameWidth="61" frameHeight="70"/> + <SubTexture name="character-multiply0000" x="817" y="205" width="46" height="46" frameX="-0" frameY="-0" frameWidth="46" frameHeight="46"/> + <SubTexture name="character-multiply0001" x="817" y="254" width="46" height="46" frameX="-0" frameY="-0" frameWidth="46" frameHeight="46"/> + <SubTexture name="character-n0000" x="658" y="578" width="44" height="68" frameX="-0" frameY="-0" frameWidth="44" frameHeight="68"/> + <SubTexture name="character-n0001" x="1" y="692" width="44" height="68" frameX="-0" frameY="-0" frameWidth="44" frameHeight="68"/> + <SubTexture name="character-nine0000" x="658" y="649" width="52" height="68" frameX="-0" frameY="-0" frameWidth="52" frameHeight="68"/> + <SubTexture name="character-nine0001" x="48" y="692" width="52" height="68" frameX="-0" frameY="-0" frameWidth="52" frameHeight="68"/> + <SubTexture name="character-o0000" x="601" y="73" width="50" height="69" frameX="-0" frameY="-0" frameWidth="50" frameHeight="69"/> + <SubTexture name="character-o0001" x="556" y="145" width="50" height="69" frameX="-0" frameY="-0" frameWidth="50" frameHeight="69"/> + <SubTexture name="character-one0000" x="601" y="217" width="51" height="69" frameX="-0" frameY="-0" frameWidth="51" frameHeight="69"/> + <SubTexture name="character-one0001" x="609" y="145" width="51" height="69" frameX="-0" frameY="-0" frameWidth="51" frameHeight="69"/> + <SubTexture name="character-p0000" x="186" y="546" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-p0001" x="235" y="546" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-percent0000" x="444" y="1" width="59" height="71" frameX="-0" frameY="-0" frameWidth="59" frameHeight="71"/> + <SubTexture name="character-percent0001" x="444" y="75" width="59" height="71" frameX="-0" frameY="-0" frameWidth="59" frameHeight="71"/> + <SubTexture name="character-period0000" x="109" y="228" width="23" height="18" frameX="-0" frameY="-0" frameWidth="23" frameHeight="18"/> + <SubTexture name="character-period0001" x="770" y="700" width="23" height="18" frameX="-0" frameY="-0" frameWidth="23" frameHeight="18"/> + <SubTexture name="character-plus0000" x="757" y="279" width="51" height="50" frameX="-0" frameY="-0" frameWidth="51" frameHeight="50"/> + <SubTexture name="character-plus0001" x="757" y="332" width="51" height="50" frameX="-0" frameY="-0" frameWidth="51" frameHeight="50"/> + <SubTexture name="character-pound0000" x="441" y="149" width="58" height="71" frameX="-0" frameY="-0" frameWidth="58" frameHeight="71"/> + <SubTexture name="character-pound0001" x="441" y="223" width="58" height="71" frameX="-0" frameY="-0" frameWidth="58" frameHeight="71"/> + <SubTexture name="character-q0000" x="487" y="517" width="52" height="70" frameX="-0" frameY="-0" frameWidth="52" frameHeight="70"/> + <SubTexture name="character-q0001" x="284" y="525" width="52" height="70" frameX="-0" frameY="-0" frameWidth="52" frameHeight="70"/> + <SubTexture name="character-questionmark0000" x="600" y="509" width="50" height="69" frameX="-0" frameY="-0" frameWidth="50" frameHeight="69"/> + <SubTexture name="character-questionmark0001" x="1" y="620" width="50" height="69" frameX="-0" frameY="-0" frameWidth="50" frameHeight="69"/> + <SubTexture name="character-questionmark-alt0000" x="1" y="169" width="51" height="77" frameX="-0" frameY="-0" frameWidth="51" frameHeight="77"/> + <SubTexture name="character-questionmark-alt0001" x="55" y="169" width="51" height="77" frameX="-0" frameY="-0" frameWidth="51" frameHeight="77"/> + <SubTexture name="character-r0000" x="103" y="692" width="46" height="68" frameX="-0" frameY="-0" frameWidth="46" frameHeight="68"/> + <SubTexture name="character-r0001" x="152" y="692" width="46" height="68" frameX="-0" frameY="-0" frameWidth="46" frameHeight="68"/> + <SubTexture name="character-roundbracket-left0000" x="560" y="73" width="34" height="69" frameX="-0" frameY="-0" frameWidth="34" frameHeight="69"/> + <SubTexture name="character-roundbracket-left0001" x="54" y="620" width="34" height="69" frameX="-0" frameY="-0" frameWidth="34" frameHeight="69"/> + <SubTexture name="character-roundbracket-right0000" x="394" y="371" width="34" height="71" frameX="-0" frameY="-0" frameWidth="34" frameHeight="71"/> + <SubTexture name="character-roundbracket-right0001" x="431" y="371" width="34" height="71" frameX="-0" frameY="-0" frameWidth="34" frameHeight="71"/> + <SubTexture name="character-s0000" x="201" y="691" width="49" height="68" frameX="-0" frameY="-0" frameWidth="49" frameHeight="68"/> + <SubTexture name="character-s0001" x="253" y="691" width="49" height="68" frameX="-0" frameY="-0" frameWidth="49" frameHeight="68"/> + <SubTexture name="character-semicolon0000" x="743" y="141" width="24" height="63" frameX="-0" frameY="-0" frameWidth="24" frameHeight="63"/> + <SubTexture name="character-semicolon0001" x="447" y="668" width="24" height="63" frameX="-0" frameY="-0" frameWidth="24" frameHeight="63"/> + <SubTexture name="character-seven0000" x="91" y="620" width="48" height="69" frameX="-0" frameY="-0" frameWidth="48" frameHeight="69"/> + <SubTexture name="character-seven0001" x="142" y="620" width="48" height="69" frameX="-0" frameY="-0" frameWidth="48" frameHeight="69"/> + <SubTexture name="character-singlequote0000" x="797" y="809" width="27" height="31" frameX="-0" frameY="-0" frameWidth="27" frameHeight="31"/> + <SubTexture name="character-singlequote0001" x="322" y="825" width="27" height="31" frameX="-0" frameY="-0" frameWidth="27" frameHeight="31"/> + <SubTexture name="character-six0000" x="600" y="653" width="54" height="68" frameX="-0" frameY="-0" frameWidth="54" frameHeight="68"/> + <SubTexture name="character-six0001" x="539" y="655" width="54" height="68" frameX="-0" frameY="-0" frameWidth="54" frameHeight="68"/> + <SubTexture name="character-slash0000" x="314" y="77" width="50" height="72" frameX="-0" frameY="-0" frameWidth="50" frameHeight="72"/> + <SubTexture name="character-slash0001" x="314" y="152" width="50" height="72" frameX="-0" frameY="-0" frameWidth="50" frameHeight="72"/> + <SubTexture name="character-squarebracket-left0000" x="434" y="297" width="33" height="71" frameX="-0" frameY="-0" frameWidth="33" frameHeight="71"/> + <SubTexture name="character-squarebracket-left0001" x="470" y="297" width="33" height="71" frameX="-0" frameY="-0" frameWidth="33" frameHeight="71"/> + <SubTexture name="character-squarebracket-right0000" x="506" y="293" width="34" height="70" frameX="-0" frameY="-0" frameWidth="34" frameHeight="70"/> + <SubTexture name="character-squarebracket-right0001" x="518" y="366" width="34" height="70" frameX="-0" frameY="-0" frameWidth="34" frameHeight="70"/> + <SubTexture name="character-t0000" x="193" y="619" width="47" height="69" frameX="-0" frameY="-0" frameWidth="47" frameHeight="69"/> + <SubTexture name="character-t0001" x="243" y="619" width="47" height="69" frameX="-0" frameY="-0" frameWidth="47" frameHeight="69"/> + <SubTexture name="character-three0000" x="433" y="518" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-three0001" x="384" y="523" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-tilde0000" x="101" y="794" width="52" height="28" frameX="-0" frameY="-0" frameWidth="52" frameHeight="28"/> + <SubTexture name="character-tilde0001" x="711" y="772" width="52" height="28" frameX="-0" frameY="-0" frameWidth="52" frameHeight="28"/> + <SubTexture name="character-two0000" x="476" y="662" width="51" height="68" frameX="-0" frameY="-0" frameWidth="51" frameHeight="68"/> + <SubTexture name="character-two0001" x="305" y="670" width="51" height="68" frameX="-0" frameY="-0" frameWidth="51" frameHeight="68"/> + <SubTexture name="character-u0000" x="543" y="293" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-u0001" x="552" y="220" width="46" height="70" frameX="-0" frameY="-0" frameWidth="46" frameHeight="70"/> + <SubTexture name="character-underscore0000" x="447" y="734" width="56" height="18" frameX="-0" frameY="-0" frameWidth="56" frameHeight="18"/> + <SubTexture name="character-underscore0001" x="101" y="763" width="56" height="18" frameX="-0" frameY="-0" frameWidth="56" frameHeight="18"/> + <SubTexture name="character-v0000" x="713" y="646" width="54" height="66" frameX="-0" frameY="-0" frameWidth="54" frameHeight="66"/> + <SubTexture name="character-v0001" x="713" y="210" width="54" height="66" frameX="-0" frameY="-0" frameWidth="54" frameHeight="66"/> + <SubTexture name="character-verticalbar0000" x="654" y="73" width="21" height="69" frameX="-0" frameY="-0" frameWidth="21" frameHeight="69"/> + <SubTexture name="character-verticalbar0001" x="655" y="217" width="21" height="69" frameX="-0" frameY="-0" frameWidth="21" frameHeight="69"/> + <SubTexture name="character-w0000" x="1" y="473" width="63" height="71" frameX="-0" frameY="-0" frameWidth="63" frameHeight="71"/> + <SubTexture name="character-w0001" x="67" y="473" width="63" height="71" frameX="-0" frameY="-0" frameWidth="63" frameHeight="71"/> + <SubTexture name="character-x0000" x="600" y="581" width="55" height="69" frameX="-0" frameY="-0" frameWidth="55" frameHeight="69"/> + <SubTexture name="character-x0001" x="542" y="583" width="55" height="69" frameX="-0" frameY="-0" frameWidth="55" frameHeight="69"/> + <SubTexture name="character-y0000" x="482" y="590" width="54" height="69" frameX="-0" frameY="-0" frameWidth="54" frameHeight="69"/> + <SubTexture name="character-y0001" x="293" y="598" width="54" height="69" frameX="-0" frameY="-0" frameWidth="54" frameHeight="69"/> + <SubTexture name="character-z0000" x="305" y="227" width="52" height="72" frameX="-0" frameY="-0" frameWidth="52" frameHeight="72"/> + <SubTexture name="character-z0001" x="1" y="324" width="52" height="72" frameX="-0" frameY="-0" frameWidth="52" frameHeight="72"/> + <SubTexture name="character-zero0000" x="133" y="472" width="53" height="71" frameX="-0" frameY="-0" frameWidth="53" frameHeight="71"/> + <SubTexture name="character-zero0001" x="189" y="472" width="53" height="71" frameX="-0" frameY="-0" frameWidth="53" frameHeight="71"/> + <SubTexture name="diacritic-acute-fill0000" x="811" y="723" width="14" height="16" frameX="-0" frameY="-0" frameWidth="14" frameHeight="16"/> + <SubTexture name="diacritic-acute-fill0001" x="810" y="751" width="14" height="16" frameX="-0" frameY="-0" frameWidth="14" frameHeight="16"/> + <SubTexture name="diacritic-acute-outline0000" x="553" y="825" width="25" height="26" frameX="-0" frameY="-0" frameWidth="25" frameHeight="26"/> + <SubTexture name="diacritic-acute-outline0001" x="581" y="825" width="25" height="26" frameX="-0" frameY="-0" frameWidth="25" frameHeight="26"/> + <SubTexture name="diacritic-apostophe-fill0000" x="198" y="378" width="17" height="17" frameX="-0" frameY="-0" frameWidth="17" frameHeight="17"/> + <SubTexture name="diacritic-apostophe-fill0001" x="791" y="843" width="17" height="17" frameX="-0" frameY="-0" frameWidth="17" frameHeight="17"/> + <SubTexture name="diacritic-apostophe-outline0000" x="384" y="814" width="29" height="26" frameX="-0" frameY="-0" frameWidth="29" frameHeight="26"/> + <SubTexture name="diacritic-apostophe-outline0001" x="827" y="833" width="29" height="26" frameX="-0" frameY="-0" frameWidth="29" frameHeight="26"/> + <SubTexture name="diacritic-breve-fill0000" x="678" y="109" width="30" height="15" frameX="-0" frameY="-0" frameWidth="30" frameHeight="15"/> + <SubTexture name="diacritic-breve-fill0001" x="374" y="843" width="30" height="15" frameX="-0" frameY="-0" frameWidth="30" frameHeight="15"/> + <SubTexture name="diacritic-breve-outline0000" x="797" y="639" width="38" height="25" frameX="-0" frameY="-0" frameWidth="38" frameHeight="25"/> + <SubTexture name="diacritic-breve-outline0001" x="797" y="667" width="38" height="25" frameX="-0" frameY="-0" frameWidth="38" frameHeight="25"/> + <SubTexture name="diacritic-cadilla-fill0000" x="795" y="55" width="19" height="31" frameX="-0" frameY="-0" frameWidth="19" frameHeight="31"/> + <SubTexture name="diacritic-cadilla-fill0001" x="352" y="825" width="19" height="31" frameX="-0" frameY="-0" frameWidth="19" frameHeight="31"/> + <SubTexture name="diacritic-cadilla-outline0000" x="797" y="771" width="29" height="35" frameX="-0" frameY="-0" frameWidth="29" frameHeight="35"/> + <SubTexture name="diacritic-cadilla-outline0001" x="829" y="723" width="29" height="35" frameX="-0" frameY="-0" frameWidth="29" frameHeight="35"/> + <SubTexture name="diacritic-circumflex-fill0000" x="480" y="843" width="28" height="14" frameX="-0" frameY="-0" frameWidth="28" frameHeight="14"/> + <SubTexture name="diacritic-circumflex-fill0001" x="480" y="825" width="28" height="14" frameX="-0" frameY="-0" frameWidth="28" frameHeight="14"/> + <SubTexture name="diacritic-circumflex-outline0000" x="797" y="695" width="38" height="25" frameX="-0" frameY="-0" frameWidth="38" frameHeight="25"/> + <SubTexture name="diacritic-circumflex-outline0001" x="770" y="723" width="38" height="25" frameX="-0" frameY="-0" frameWidth="38" frameHeight="25"/> + <SubTexture name="diacritic-dot-fill0000" x="682" y="847" width="12" height="12" frameX="-0" frameY="-0" frameWidth="12" frameHeight="12"/> + <SubTexture name="diacritic-dot-fill0001" x="811" y="843" width="12" height="12" frameX="-0" frameY="-0" frameWidth="12" frameHeight="12"/> + <SubTexture name="diacritic-dot-outline0000" x="648" y="799" width="23" height="23" frameX="-0" frameY="-0" frameWidth="23" frameHeight="23"/> + <SubTexture name="diacritic-dot-outline0001" x="765" y="825" width="23" height="23" frameX="-0" frameY="-0" frameWidth="23" frameHeight="23"/> + <SubTexture name="diacritic-hookabove-fill0000" x="609" y="825" width="20" height="26" frameX="-0" frameY="-0" frameWidth="20" frameHeight="26"/> + <SubTexture name="diacritic-hookabove-fill0001" x="632" y="825" width="20" height="26" frameX="-0" frameY="-0" frameWidth="20" frameHeight="26"/> + <SubTexture name="diacritic-hookabove-outline0000" x="829" y="761" width="30" height="35" frameX="-0" frameY="-0" frameWidth="30" frameHeight="35"/> + <SubTexture name="diacritic-hookabove-outline0001" x="1" y="825" width="30" height="35" frameX="-0" frameY="-0" frameWidth="30" frameHeight="35"/> + <SubTexture name="diacritic-hookhorizontal-fill0000" x="250" y="833" width="33" height="23" frameX="-0" frameY="-0" frameWidth="33" frameHeight="23"/> + <SubTexture name="diacritic-hookhorizontal-fill0001" x="286" y="833" width="33" height="23" frameX="-0" frameY="-0" frameWidth="33" frameHeight="23"/> + <SubTexture name="diacritic-hookhorizontal-outline0000" x="817" y="481" width="39" height="34" frameX="-0" frameY="-0" frameWidth="39" frameHeight="34"/> + <SubTexture name="diacritic-hookhorizontal-outline0001" x="817" y="518" width="39" height="34" frameX="-0" frameY="-0" frameWidth="39" frameHeight="34"/> + <SubTexture name="diacritic-hookside-fill0000" x="766" y="772" width="28" height="50" frameX="-0" frameY="-0" frameWidth="28" frameHeight="50"/> + <SubTexture name="diacritic-hookside-fill0001" x="339" y="525" width="28" height="50" frameX="-0" frameY="-0" frameWidth="28" frameHeight="50"/> + <SubTexture name="diacritic-hookside-outline0000" x="359" y="668" width="34" height="62" frameX="-0" frameY="-0" frameWidth="34" frameHeight="62"/> + <SubTexture name="diacritic-hookside-outline0001" x="396" y="668" width="34" height="62" frameX="-0" frameY="-0" frameWidth="34" frameHeight="62"/> + <SubTexture name="diacritic-hookvertical-fill0000" x="34" y="825" width="23" height="35" frameX="-0" frameY="-0" frameWidth="23" frameHeight="35"/> + <SubTexture name="diacritic-hookvertical-fill0001" x="60" y="825" width="23" height="35" frameX="-0" frameY="-0" frameWidth="23" frameHeight="35"/> + <SubTexture name="diacritic-hookvertical-outline0000" x="817" y="555" width="34" height="39" frameX="-0" frameY="-0" frameWidth="34" frameHeight="39"/> + <SubTexture name="diacritic-hookvertical-outline0001" x="802" y="597" width="34" height="39" frameX="-0" frameY="-0" frameWidth="34" frameHeight="39"/> + <SubTexture name="diacritic-horn-fill0000" x="751" y="510" width="16" height="18" frameX="-0" frameY="-0" frameWidth="16" frameHeight="18"/> + <SubTexture name="diacritic-horn-fill0001" x="751" y="531" width="16" height="18" frameX="-0" frameY="-0" frameWidth="16" frameHeight="18"/> + <SubTexture name="diacritic-horn-outline0000" x="655" y="825" width="24" height="26" frameX="-0" frameY="-0" frameWidth="24" frameHeight="26"/> + <SubTexture name="diacritic-horn-outline0001" x="682" y="818" width="24" height="26" frameX="-0" frameY="-0" frameWidth="24" frameHeight="26"/> + <SubTexture name="diacritic-macron-fill0000" x="516" y="763" width="35" height="10" frameX="-0" frameY="-0" frameWidth="35" frameHeight="10"/> + <SubTexture name="diacritic-macron-fill0001" x="554" y="763" width="35" height="10" frameX="-0" frameY="-0" frameWidth="35" frameHeight="10"/> + <SubTexture name="diacritic-macron-outline0000" x="817" y="303" width="46" height="20" frameX="-0" frameY="-0" frameWidth="46" frameHeight="20"/> + <SubTexture name="diacritic-macron-outline0001" x="817" y="326" width="46" height="20" frameX="-0" frameY="-0" frameWidth="46" frameHeight="20"/> + <SubTexture name="diacritic-ogonek-fill0000" x="770" y="636" width="19" height="29" frameX="-0" frameY="-0" frameWidth="19" frameHeight="29"/> + <SubTexture name="diacritic-ogonek-fill0001" x="770" y="668" width="19" height="29" frameX="-0" frameY="-0" frameWidth="19" frameHeight="29"/> + <SubTexture name="diacritic-ogonek-outline0000" x="86" y="825" width="28" height="34" frameX="-0" frameY="-0" frameWidth="28" frameHeight="34"/> + <SubTexture name="diacritic-ogonek-outline0001" x="117" y="825" width="28" height="34" frameX="-0" frameY="-0" frameWidth="28" frameHeight="34"/> + <SubTexture name="diacritic-ring-fill0000" x="223" y="452" width="19" height="16" frameX="-0" frameY="-0" frameWidth="19" frameHeight="16"/> + <SubTexture name="diacritic-ring-fill0001" x="674" y="799" width="19" height="16" frameX="-0" frameY="-0" frameWidth="19" frameHeight="16"/> + <SubTexture name="diacritic-ring-outline0000" x="416" y="825" width="29" height="25" frameX="-0" frameY="-0" frameWidth="29" frameHeight="25"/> + <SubTexture name="diacritic-ring-outline0001" x="448" y="825" width="29" height="25" frameX="-0" frameY="-0" frameWidth="29" frameHeight="25"/> + <SubTexture name="diacritic-sicilicus-fill0000" x="802" y="562" width="12" height="24" frameX="-0" frameY="-0" frameWidth="12" frameHeight="24"/> + <SubTexture name="diacritic-sicilicus-fill0001" x="750" y="803" width="12" height="24" frameX="-0" frameY="-0" frameWidth="12" frameHeight="24"/> + <SubTexture name="diacritic-sicilicus-outline0000" x="839" y="597" width="25" height="36" frameX="-0" frameY="-0" frameWidth="25" frameHeight="36"/> + <SubTexture name="diacritic-sicilicus-outline0001" x="839" y="636" width="25" height="36" frameX="-0" frameY="-0" frameWidth="25" frameHeight="36"/> + <SubTexture name="diacritic-stroke0-long-fill0000" x="101" y="784" width="55" height="7" frameX="-0" frameY="-0" frameWidth="55" frameHeight="7"/> + <SubTexture name="diacritic-stroke0-long-fill0001" x="101" y="784" width="55" height="7" frameX="-0" frameY="-0" frameWidth="55" frameHeight="7"/> + <SubTexture name="diacritic-stroke0-long-outline0000" x="305" y="741" width="68" height="19" frameX="-0" frameY="-0" frameWidth="68" frameHeight="19"/> + <SubTexture name="diacritic-stroke0-long-outline0001" x="376" y="733" width="68" height="19" frameX="-0" frameY="-0" frameWidth="68" frameHeight="19"/> + <SubTexture name="diacritic-stroke0-short-fill0000" x="376" y="755" width="30" height="5" frameX="-0" frameY="-0" frameWidth="30" frameHeight="5"/> + <SubTexture name="diacritic-stroke0-short-fill0001" x="409" y="755" width="30" height="5" frameX="-0" frameY="-0" frameWidth="30" frameHeight="5"/> + <SubTexture name="diacritic-stroke0-short-outline0000" x="152" y="378" width="43" height="17" frameX="-0" frameY="-0" frameWidth="43" frameHeight="17"/> + <SubTexture name="diacritic-stroke0-short-outline0001" x="770" y="255" width="43" height="17" frameX="-0" frameY="-0" frameWidth="43" frameHeight="17"/> + <SubTexture name="diacritic-stroke30-fill0000" x="757" y="385" width="50" height="22" frameX="-0" frameY="-0" frameWidth="50" frameHeight="22"/> + <SubTexture name="diacritic-stroke30-fill0001" x="755" y="410" width="50" height="22" frameX="-0" frameY="-0" frameWidth="50" frameHeight="22"/> + <SubTexture name="diacritic-stroke30-outline0000" x="530" y="726" width="62" height="34" frameX="-0" frameY="-0" frameWidth="62" frameHeight="34"/> + <SubTexture name="diacritic-stroke30-outline0001" x="595" y="726" width="62" height="34" frameX="-0" frameY="-0" frameWidth="62" frameHeight="34"/> + <SubTexture name="diacritic-stroke45-long-fill0000" x="1" y="763" width="47" height="57" frameX="-0" frameY="-0" frameWidth="47" frameHeight="57"/> + <SubTexture name="diacritic-stroke45-long-fill0001" x="51" y="763" width="47" height="57" frameX="-0" frameY="-0" frameWidth="47" frameHeight="57"/> + <SubTexture name="diacritic-stroke45-long-outline0000" x="350" y="596" width="60" height="69" frameX="-0" frameY="-0" frameWidth="60" frameHeight="69"/> + <SubTexture name="diacritic-stroke45-long-outline0001" x="413" y="596" width="60" height="69" frameX="-0" frameY="-0" frameWidth="60" frameHeight="69"/> + <SubTexture name="diacritic-stroke45-short-fill0000" x="148" y="825" width="30" height="34" frameX="-0" frameY="-0" frameWidth="30" frameHeight="34"/> + <SubTexture name="diacritic-stroke45-short-fill0001" x="181" y="825" width="30" height="34" frameX="-0" frameY="-0" frameWidth="30" frameHeight="34"/> + <SubTexture name="diacritic-stroke45-short-outline0000" x="770" y="107" width="44" height="49" frameX="-0" frameY="-0" frameWidth="44" frameHeight="49"/> + <SubTexture name="diacritic-stroke45-short-outline0001" x="770" y="510" width="44" height="49" frameX="-0" frameY="-0" frameWidth="44" frameHeight="49"/> + <SubTexture name="diacritic-stroke60-fill0000" x="663" y="145" width="31" height="69" frameX="-0" frameY="-0" frameWidth="31" frameHeight="69"/> + <SubTexture name="diacritic-stroke60-fill0001" x="679" y="217" width="31" height="69" frameX="-0" frameY="-0" frameWidth="31" frameHeight="69"/> + <SubTexture name="diacritic-stroke60-outline0000" x="45" y="1" width="45" height="81" frameX="-0" frameY="-0" frameWidth="45" frameHeight="81"/> + <SubTexture name="diacritic-stroke60-outline0001" x="45" y="85" width="45" height="81" frameX="-0" frameY="-0" frameWidth="45" frameHeight="81"/> + <SubTexture name="diacritic-stroke90-long-fill0000" x="349" y="302" width="8" height="70" frameX="-0" frameY="-0" frameWidth="8" frameHeight="70"/> + <SubTexture name="diacritic-stroke90-long-fill0001" x="33" y="87" width="8" height="70" frameX="-0" frameY="-0" frameWidth="8" frameHeight="70"/> + <SubTexture name="diacritic-stroke90-long-outline0000" x="1" y="1" width="19" height="83" frameX="-0" frameY="-0" frameWidth="19" frameHeight="83"/> + <SubTexture name="diacritic-stroke90-long-outline0001" x="23" y="1" width="19" height="83" frameX="-0" frameY="-0" frameWidth="19" frameHeight="83"/> + <SubTexture name="diacritic-stroke90-short-fill0000" x="506" y="733" width="7" height="39" frameX="-0" frameY="-0" frameWidth="7" frameHeight="39"/> + <SubTexture name="diacritic-stroke90-short-fill0001" x="700" y="776" width="7" height="39" frameX="-0" frameY="-0" frameWidth="7" frameHeight="39"/> + <SubTexture name="diacritic-stroke90-short-outline0000" x="678" y="1" width="19" height="51" frameX="-0" frameY="-0" frameWidth="19" frameHeight="51"/> + <SubTexture name="diacritic-stroke90-short-outline0001" x="678" y="55" width="19" height="51" frameX="-0" frameY="-0" frameWidth="19" frameHeight="51"/> + <SubTexture name="diacritic-tilde-fill0000" x="339" y="578" width="34" height="15" frameX="-0" frameY="-0" frameWidth="34" frameHeight="15"/> + <SubTexture name="diacritic-tilde-fill0001" x="245" y="525" width="34" height="15" frameX="-0" frameY="-0" frameWidth="34" frameHeight="15"/> + <SubTexture name="diacritic-tilde-outline0000" x="817" y="349" width="44" height="27" frameX="-0" frameY="-0" frameWidth="44" frameHeight="27"/> + <SubTexture name="diacritic-tilde-outline0001" x="817" y="379" width="44" height="27" frameX="-0" frameY="-0" frameWidth="44" frameHeight="27"/> + <SubTexture name="diacritic-titlo-fill0000" x="770" y="751" width="37" height="17" frameX="-0" frameY="-0" frameWidth="37" frameHeight="17"/> + <SubTexture name="diacritic-titlo-fill0001" x="710" y="803" width="37" height="17" frameX="-0" frameY="-0" frameWidth="37" frameHeight="17"/> + <SubTexture name="diacritic-titlo-outline0000" x="817" y="71" width="47" height="27" frameX="-0" frameY="-0" frameWidth="47" frameHeight="27"/> + <SubTexture name="diacritic-titlo-outline0001" x="817" y="101" width="47" height="27" frameX="-0" frameY="-0" frameWidth="47" frameHeight="27"/> + <SubTexture name="diacritic-verticalline-fill0000" x="93" y="149" width="8" height="17" frameX="-0" frameY="-0" frameWidth="8" frameHeight="17"/> + <SubTexture name="diacritic-verticalline-fill0001" x="137" y="304" width="8" height="17" frameX="-0" frameY="-0" frameWidth="8" frameHeight="17"/> + <SubTexture name="diacritic-verticalline-outline0000" x="511" y="825" width="18" height="27" frameX="-0" frameY="-0" frameWidth="18" frameHeight="27"/> + <SubTexture name="diacritic-verticalline-outline0001" x="532" y="825" width="18" height="27" frameX="-0" frameY="-0" frameWidth="18" frameHeight="27"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/images/menus/alphabet.png b/assets/images/menus/alphabet.png index 545e3505..3c9dbfae 100644 Binary files a/assets/images/menus/alphabet.png and b/assets/images/menus/alphabet.png differ diff --git a/assets/images/menus/alphabet.xml b/assets/images/menus/alphabet.xml index a5c09760..8ebfe898 100644 --- a/assets/images/menus/alphabet.xml +++ b/assets/images/menus/alphabet.xml @@ -1,401 +1,270 @@ -<?xml version="1.0" encoding="utf-8"?> -<TextureAtlas imagePath="alphabet.png"> - <!-- Created with Adobe Animate version 21.0.1.37179 --> - <!-- http://www.adobe.com/products/animate.html --> - <SubTexture name="#0000" x="2" y="2" width="39" height="44" pivotX="0" pivotY="0"/> - <SubTexture name="#0001" x="2" y="2" width="39" height="44"/> - <SubTexture name="#0002" x="43" y="2" width="39" height="44"/> - <SubTexture name="#0003" x="43" y="2" width="39" height="44"/> - <SubTexture name="$0000" x="84" y="2" width="40" height="64" pivotX="0" pivotY="0"/> - <SubTexture name="$0001" x="84" y="2" width="40" height="64"/> - <SubTexture name="$0002" x="126" y="2" width="39" height="63" frameX="0" frameY="0" frameWidth="40" frameHeight="64"/> - <SubTexture name="$0003" x="126" y="2" width="39" height="63" frameX="0" frameY="0" frameWidth="40" frameHeight="64"/> - <SubTexture name="%0000" x="167" y="2" width="46" height="52" pivotX="0.05" pivotY="0"/> - <SubTexture name="%0001" x="167" y="2" width="46" height="52"/> - <SubTexture name="%0002" x="215" y="2" width="45" height="52" frameX="0" frameY="0" frameWidth="46" frameHeight="52"/> - <SubTexture name="%0003" x="215" y="2" width="45" height="52" frameX="0" frameY="0" frameWidth="46" frameHeight="52"/> - <SubTexture name="&0000" x="262" y="2" width="36" height="58" pivotX="0" pivotY="0"/> - <SubTexture name="&0001" x="262" y="2" width="36" height="58"/> - <SubTexture name="&0002" x="300" y="2" width="36" height="58"/> - <SubTexture name="&0003" x="300" y="2" width="36" height="58"/> - <SubTexture name="(0000" x="338" y="2" width="26" height="57" pivotX="0" pivotY="0.15"/> - <SubTexture name="(0001" x="338" y="2" width="26" height="57"/> - <SubTexture name="(0002" x="366" y="2" width="26" height="56" frameX="0" frameY="0" frameWidth="26" frameHeight="57"/> - <SubTexture name="(0003" x="366" y="2" width="26" height="56" frameX="0" frameY="0" frameWidth="26" frameHeight="57"/> - <SubTexture name=")0000" x="394" y="2" width="26" height="55" pivotX="0" pivotY="0"/> - <SubTexture name=")0001" x="394" y="2" width="26" height="55"/> - <SubTexture name=")0002" x="422" y="2" width="26" height="55"/> - <SubTexture name=")0003" x="422" y="2" width="26" height="55"/> - <SubTexture name="*0000" x="450" y="2" width="27" height="31" pivotX="0" pivotY="0"/> - <SubTexture name="*0001" x="450" y="2" width="27" height="31"/> - <SubTexture name="*0002" x="479" y="2" width="26" height="31" frameX="0" frameY="0" frameWidth="27" frameHeight="31"/> - <SubTexture name="*0003" x="479" y="2" width="26" height="31" frameX="0" frameY="0" frameWidth="27" frameHeight="31"/> - <SubTexture name="+0000" x="507" y="2" width="42" height="39" pivotX="0" pivotY="0"/> - <SubTexture name="+0001" x="507" y="2" width="42" height="39"/> - <SubTexture name="+0002" x="551" y="2" width="42" height="39"/> - <SubTexture name="+0003" x="551" y="2" width="42" height="39"/> - <SubTexture name="-0000" x="595" y="2" width="37" height="13" pivotX="0" pivotY="0.5"/> - <SubTexture name="-0001" x="595" y="2" width="37" height="13"/> - <SubTexture name="-0002" x="634" y="2" width="37" height="13"/> - <SubTexture name="-0003" x="634" y="2" width="37" height="13"/> - <SubTexture name="00000" x="673" y="2" width="34" height="54" pivotX="0" pivotY="0" frameX="0" frameY="0" frameWidth="35" frameHeight="54"/> - <SubTexture name="00001" x="673" y="2" width="34" height="54" frameX="0" frameY="0" frameWidth="35" frameHeight="54"/> - <SubTexture name="00002" x="709" y="2" width="35" height="54"/> - <SubTexture name="00003" x="709" y="2" width="35" height="54"/> - <SubTexture name="10000" x="746" y="2" width="23" height="53" pivotX="0" pivotY="0"/> - <SubTexture name="10001" x="746" y="2" width="23" height="53"/> - <SubTexture name="10002" x="771" y="2" width="23" height="53"/> - <SubTexture name="10003" x="771" y="2" width="23" height="53"/> - <SubTexture name="20000" x="796" y="2" width="37" height="52" pivotX="0" pivotY="0"/> - <SubTexture name="20001" x="796" y="2" width="37" height="52"/> - <SubTexture name="20002" x="835" y="2" width="36" height="52" frameX="0" frameY="0" frameWidth="37" frameHeight="52"/> - <SubTexture name="20003" x="835" y="2" width="36" height="52" frameX="0" frameY="0" frameWidth="37" frameHeight="52"/> - <SubTexture name="30000" x="873" y="2" width="34" height="56" pivotX="0" pivotY="0"/> - <SubTexture name="30001" x="873" y="2" width="34" height="56"/> - <SubTexture name="30002" x="2" y="68" width="34" height="55" frameX="0" frameY="0" frameWidth="34" frameHeight="56"/> - <SubTexture name="30003" x="2" y="68" width="34" height="55" frameX="0" frameY="0" frameWidth="34" frameHeight="56"/> - <SubTexture name="40000" x="38" y="68" width="35" height="55" pivotX="0.05" pivotY="0"/> - <SubTexture name="40001" x="38" y="68" width="35" height="55"/> - <SubTexture name="40002" x="75" y="68" width="35" height="54" frameX="0" frameY="0" frameWidth="35" frameHeight="55"/> - <SubTexture name="40003" x="75" y="68" width="35" height="54" frameX="0" frameY="0" frameWidth="35" frameHeight="55"/> - <SubTexture name="50000" x="112" y="68" width="41" height="53" pivotX="0.25" pivotY="0"/> - <SubTexture name="50001" x="112" y="68" width="41" height="53"/> - <SubTexture name="50002" x="155" y="68" width="41" height="53"/> - <SubTexture name="50003" x="155" y="68" width="41" height="53"/> - <SubTexture name="60000" x="198" y="68" width="39" height="54" pivotX="0.2" pivotY="0"/> - <SubTexture name="60001" x="198" y="68" width="39" height="54"/> - <SubTexture name="60002" x="239" y="68" width="39" height="54"/> - <SubTexture name="60003" x="239" y="68" width="39" height="54"/> - <SubTexture name="70000" x="280" y="68" width="40" height="49" pivotX="0" pivotY="0.1"/> - <SubTexture name="70001" x="280" y="68" width="40" height="49"/> - <SubTexture name="70002" x="322" y="68" width="40" height="49"/> - <SubTexture name="70003" x="322" y="68" width="40" height="49"/> - <SubTexture name="80000" x="364" y="68" width="34" height="56" pivotX="0.1" pivotY="0"/> - <SubTexture name="80001" x="364" y="68" width="34" height="56"/> - <SubTexture name="80002" x="400" y="68" width="33" height="56" frameX="0" frameY="0" frameWidth="34" frameHeight="56"/> - <SubTexture name="80003" x="400" y="68" width="33" height="56" frameX="0" frameY="0" frameWidth="34" frameHeight="56"/> - <SubTexture name="90000" x="435" y="68" width="34" height="57" pivotX="0.35" pivotY="0"/> - <SubTexture name="90001" x="435" y="68" width="34" height="57"/> - <SubTexture name="90002" x="471" y="68" width="34" height="57"/> - <SubTexture name="90003" x="471" y="68" width="34" height="57"/> - <SubTexture name=":0000" x="507" y="68" width="14" height="40" pivotX="0" pivotY="0"/> - <SubTexture name=":0001" x="507" y="68" width="14" height="40"/> - <SubTexture name=":0002" x="523" y="68" width="14" height="40"/> - <SubTexture name=":0003" x="523" y="68" width="14" height="40"/> - <SubTexture name=";0000" x="539" y="68" width="15" height="44" pivotX="0" pivotY="0"/> - <SubTexture name=";0001" x="539" y="68" width="15" height="44"/> - <SubTexture name=";0002" x="556" y="68" width="15" height="44"/> - <SubTexture name=";0003" x="556" y="68" width="15" height="44"/> - <SubTexture name="<0000" x="573" y="68" width="38" height="56" pivotX="0" pivotY="0"/> - <SubTexture name="<0001" x="573" y="68" width="38" height="56"/> - <SubTexture name="<0002" x="613" y="68" width="38" height="55" frameX="0" frameY="0" frameWidth="38" frameHeight="56"/> - <SubTexture name="<0003" x="613" y="68" width="38" height="55" frameX="0" frameY="0" frameWidth="38" frameHeight="56"/> - <SubTexture name="=0000" x="653" y="68" width="39" height="29" pivotX="0" pivotY="0"/> - <SubTexture name="=0001" x="653" y="68" width="39" height="29"/> - <SubTexture name="=0002" x="694" y="68" width="39" height="29"/> - <SubTexture name="=0003" x="694" y="68" width="39" height="29"/> - <SubTexture name=">0000" x="735" y="68" width="38" height="61" pivotX="0" pivotY="0"/> - <SubTexture name=">0001" x="735" y="68" width="38" height="61"/> - <SubTexture name=">0002" x="775" y="68" width="38" height="61"/> - <SubTexture name=">0003" x="775" y="68" width="38" height="61"/> - <SubTexture name="@0000" x="815" y="68" width="51" height="56" pivotX="0.3" pivotY="0" frameX="0" frameY="0" frameWidth="52" frameHeight="56"/> - <SubTexture name="@0001" x="815" y="68" width="51" height="56" frameX="0" frameY="0" frameWidth="52" frameHeight="56"/> - <SubTexture name="@0002" x="868" y="68" width="52" height="56"/> - <SubTexture name="@0003" x="868" y="68" width="52" height="56"/> - <SubTexture name="A capital0000" x="2" y="131" width="45" height="59" pivotX="0.15" pivotY="0"/> - <SubTexture name="A capital0001" x="2" y="131" width="45" height="59"/> - <SubTexture name="A capital0002" x="49" y="131" width="45" height="59"/> - <SubTexture name="A capital0003" x="49" y="131" width="45" height="59"/> - <SubTexture name="B capital0000" x="96" y="131" width="38" height="59" pivotX="0" pivotY="0"/> - <SubTexture name="B capital0001" x="96" y="131" width="38" height="59"/> - <SubTexture name="B capital0002" x="136" y="131" width="38" height="59"/> - <SubTexture name="B capital0003" x="136" y="131" width="38" height="59"/> - <SubTexture name="C capital0000" x="176" y="131" width="45" height="57" pivotX="0.1" pivotY="0"/> - <SubTexture name="C capital0001" x="176" y="131" width="45" height="57"/> - <SubTexture name="C capital0002" x="223" y="131" width="45" height="57"/> - <SubTexture name="C capital0003" x="223" y="131" width="45" height="57"/> - <SubTexture name="D capital0000" x="270" y="131" width="48" height="56" pivotX="0" pivotY="0"/> - <SubTexture name="D capital0001" x="270" y="131" width="48" height="56"/> - <SubTexture name="D capital0002" x="320" y="131" width="48" height="56"/> - <SubTexture name="D capital0003" x="320" y="131" width="48" height="56"/> - <SubTexture name="E capital0000" x="370" y="131" width="34" height="53" pivotX="0" pivotY="0"/> - <SubTexture name="E capital0001" x="370" y="131" width="34" height="53"/> - <SubTexture name="E capital0002" x="406" y="131" width="34" height="53"/> - <SubTexture name="E capital0003" x="406" y="131" width="34" height="53"/> - <SubTexture name="F capital0000" x="442" y="131" width="36" height="51" pivotX="0" pivotY="0"/> - <SubTexture name="F capital0001" x="442" y="131" width="36" height="51"/> - <SubTexture name="F capital0002" x="480" y="131" width="36" height="51"/> - <SubTexture name="F capital0003" x="480" y="131" width="36" height="51"/> - <SubTexture name="G capital0000" x="518" y="131" width="43" height="55" pivotX="0" pivotY="0"/> - <SubTexture name="G capital0001" x="518" y="131" width="43" height="55"/> - <SubTexture name="G capital0002" x="563" y="131" width="43" height="55"/> - <SubTexture name="G capital0003" x="563" y="131" width="43" height="55"/> - <SubTexture name="H capital0000" x="608" y="131" width="32" height="53" pivotX="0" pivotY="0"/> - <SubTexture name="H capital0001" x="608" y="131" width="32" height="53"/> - <SubTexture name="H capital0002" x="642" y="131" width="32" height="52" frameX="0" frameY="0" frameWidth="32" frameHeight="53"/> - <SubTexture name="H capital0003" x="642" y="131" width="32" height="52" frameX="0" frameY="0" frameWidth="32" frameHeight="53"/> - <SubTexture name="I capital0000" x="676" y="131" width="36" height="52" pivotX="0" pivotY="0"/> - <SubTexture name="I capital0001" x="676" y="131" width="36" height="52"/> - <SubTexture name="I capital0002" x="714" y="131" width="36" height="52"/> - <SubTexture name="I capital0003" x="714" y="131" width="36" height="52"/> - <SubTexture name="J capital0000" x="752" y="131" width="39" height="54" pivotX="0.05" pivotY="0"/> - <SubTexture name="J capital0001" x="752" y="131" width="39" height="54"/> - <SubTexture name="J capital0002" x="793" y="131" width="39" height="54"/> - <SubTexture name="J capital0003" x="793" y="131" width="39" height="54"/> - <SubTexture name="K capital0000" x="834" y="131" width="35" height="52" pivotX="0.05" pivotY="0"/> - <SubTexture name="K capital0001" x="834" y="131" width="35" height="52"/> - <SubTexture name="K capital0002" x="871" y="131" width="35" height="52"/> - <SubTexture name="K capital0003" x="871" y="131" width="35" height="52"/> - <SubTexture name="L capital0000" x="2" y="192" width="34" height="53" pivotX="0" pivotY="0"/> - <SubTexture name="L capital0001" x="2" y="192" width="34" height="53"/> - <SubTexture name="L capital0002" x="38" y="192" width="34" height="53"/> - <SubTexture name="L capital0003" x="38" y="192" width="34" height="53"/> - <SubTexture name="M capital0000" x="74" y="192" width="44" height="50" pivotX="0.2" pivotY="0"/> - <SubTexture name="M capital0001" x="74" y="192" width="44" height="50"/> - <SubTexture name="M capital0002" x="120" y="192" width="44" height="50"/> - <SubTexture name="M capital0003" x="120" y="192" width="44" height="50"/> - <SubTexture name="N capital0000" x="166" y="192" width="32" height="50" pivotX="0" pivotY="0"/> - <SubTexture name="N capital0001" x="166" y="192" width="32" height="50"/> - <SubTexture name="N capital0002" x="200" y="192" width="32" height="50"/> - <SubTexture name="N capital0003" x="200" y="192" width="32" height="50"/> - <SubTexture name="O capital0000" x="234" y="192" width="40" height="54" pivotX="0" pivotY="0"/> - <SubTexture name="O capital0001" x="234" y="192" width="40" height="54"/> - <SubTexture name="O capital0002" x="276" y="192" width="40" height="54"/> - <SubTexture name="O capital0003" x="276" y="192" width="40" height="54"/> - <SubTexture name="P capital0000" x="318" y="192" width="35" height="51" pivotX="0.05" pivotY="0"/> - <SubTexture name="P capital0001" x="318" y="192" width="35" height="51"/> - <SubTexture name="P capital0002" x="355" y="192" width="35" height="51"/> - <SubTexture name="P capital0003" x="355" y="192" width="35" height="51"/> - <SubTexture name="Q capital0000" x="392" y="192" width="41" height="51" pivotX="0.05" pivotY="0"/> - <SubTexture name="Q capital0001" x="392" y="192" width="41" height="51"/> - <SubTexture name="Q capital0002" x="435" y="192" width="41" height="51"/> - <SubTexture name="Q capital0003" x="435" y="192" width="41" height="51"/> - <SubTexture name="R capital0000" x="478" y="192" width="40" height="55" pivotX="0.05" pivotY="0"/> - <SubTexture name="R capital0001" x="478" y="192" width="40" height="55"/> - <SubTexture name="R capital0002" x="520" y="192" width="40" height="55"/> - <SubTexture name="R capital0003" x="520" y="192" width="40" height="55"/> - <SubTexture name="S capital0000" x="562" y="192" width="38" height="55" pivotX="0" pivotY="0"/> - <SubTexture name="S capital0001" x="562" y="192" width="38" height="55"/> - <SubTexture name="S capital0002" x="602" y="192" width="38" height="55"/> - <SubTexture name="S capital0003" x="602" y="192" width="38" height="55"/> - <SubTexture name="T capital0000" x="642" y="192" width="42" height="50" pivotX="0" pivotY="0"/> - <SubTexture name="T capital0001" x="642" y="192" width="42" height="50"/> - <SubTexture name="T capital0002" x="686" y="192" width="42" height="50"/> - <SubTexture name="T capital0003" x="686" y="192" width="42" height="50"/> - <SubTexture name="U capital0000" x="730" y="192" width="38" height="49" pivotX="0" pivotY="0"/> - <SubTexture name="U capital0001" x="730" y="192" width="38" height="49"/> - <SubTexture name="U capital0002" x="770" y="192" width="38" height="49"/> - <SubTexture name="U capital0003" x="770" y="192" width="38" height="49"/> - <SubTexture name="V capital0000" x="810" y="192" width="40" height="48" pivotX="0" pivotY="0"/> - <SubTexture name="V capital0001" x="810" y="192" width="40" height="48"/> - <SubTexture name="V capital0002" x="852" y="192" width="40" height="48"/> - <SubTexture name="V capital0003" x="852" y="192" width="40" height="48"/> - <SubTexture name="W capital0000" x="2" y="249" width="46" height="51" pivotX="0.05" pivotY="0"/> - <SubTexture name="W capital0001" x="2" y="249" width="46" height="51"/> - <SubTexture name="W capital0002" x="50" y="249" width="46" height="51"/> - <SubTexture name="W capital0003" x="50" y="249" width="46" height="51"/> - <SubTexture name="X capital0000" x="98" y="249" width="41" height="53" pivotX="0.05" pivotY="0"/> - <SubTexture name="X capital0001" x="98" y="249" width="41" height="53"/> - <SubTexture name="X capital0002" x="141" y="249" width="41" height="53"/> - <SubTexture name="X capital0003" x="141" y="249" width="41" height="53"/> - <SubTexture name="Y capital0000" x="184" y="249" width="42" height="52" pivotX="0" pivotY="0"/> - <SubTexture name="Y capital0001" x="184" y="249" width="42" height="52"/> - <SubTexture name="Y capital0002" x="228" y="249" width="42" height="52"/> - <SubTexture name="Y capital0003" x="228" y="249" width="42" height="52"/> - <SubTexture name="Z capital0000" x="272" y="249" width="38" height="47" pivotX="0.05" pivotY="0"/> - <SubTexture name="Z capital0001" x="272" y="249" width="38" height="47"/> - <SubTexture name="Z capital0002" x="312" y="249" width="38" height="47"/> - <SubTexture name="Z capital0003" x="312" y="249" width="38" height="47"/> - <SubTexture name="[0000" x="352" y="249" width="25" height="61" pivotX="0.1" pivotY="0.25"/> - <SubTexture name="[0001" x="352" y="249" width="25" height="61"/> - <SubTexture name="[0002" x="379" y="249" width="24" height="61" frameX="0" frameY="0" frameWidth="25" frameHeight="61"/> - <SubTexture name="[0003" x="379" y="249" width="24" height="61" frameX="0" frameY="0" frameWidth="25" frameHeight="61"/> - <SubTexture name="\0000" x="405" y="249" width="43" height="59" pivotX="0" pivotY="0"/> - <SubTexture name="\0001" x="405" y="249" width="43" height="59"/> - <SubTexture name="\0002" x="450" y="249" width="42" height="59" frameX="0" frameY="0" frameWidth="43" frameHeight="59"/> - <SubTexture name="\0003" x="450" y="249" width="42" height="59" frameX="0" frameY="0" frameWidth="43" frameHeight="59"/> - <SubTexture name="]0000" x="494" y="249" width="25" height="63" pivotX="0" pivotY="0.05"/> - <SubTexture name="]0001" x="494" y="249" width="25" height="63"/> - <SubTexture name="]0002" x="521" y="249" width="25" height="63"/> - <SubTexture name="]0003" x="521" y="249" width="25" height="63"/> - <SubTexture name="^0000" x="548" y="249" width="32" height="27" pivotX="0" pivotY="0"/> - <SubTexture name="^0001" x="548" y="249" width="32" height="27"/> - <SubTexture name="^0002" x="582" y="249" width="32" height="27"/> - <SubTexture name="^0003" x="582" y="249" width="32" height="27"/> - <SubTexture name="_0000" x="616" y="249" width="53" height="13" pivotX="0" pivotY="0"/> - <SubTexture name="_0001" x="616" y="249" width="53" height="13"/> - <SubTexture name="_0002" x="671" y="249" width="53" height="13"/> - <SubTexture name="_0003" x="671" y="249" width="53" height="13"/> - <SubTexture name="a lowercase0000" x="726" y="249" width="36" height="38" pivotX="0" pivotY="0"/> - <SubTexture name="a lowercase0001" x="726" y="249" width="36" height="38"/> - <SubTexture name="a lowercase0002" x="764" y="249" width="36" height="38"/> - <SubTexture name="a lowercase0003" x="764" y="249" width="36" height="38"/> - <SubTexture name="angry faic0000" x="802" y="249" width="54" height="54" pivotX="0.35" pivotY="0"/> - <SubTexture name="angry faic0001" x="802" y="249" width="54" height="54"/> - <SubTexture name="angry faic0002" x="858" y="249" width="54" height="54"/> - <SubTexture name="angry faic0003" x="858" y="249" width="54" height="54"/> - <SubTexture name="apostraphie0000" x="2" y="314" width="17" height="27" pivotX="0" pivotY="0"/> - <SubTexture name="apostraphie0001" x="2" y="314" width="17" height="27"/> - <SubTexture name="apostraphie0002" x="21" y="314" width="17" height="27"/> - <SubTexture name="apostraphie0003" x="21" y="314" width="17" height="27"/> - <SubTexture name="b lowercase0000" x="40" y="314" width="31" height="50" pivotX="0.2" pivotY="0"/> - <SubTexture name="b lowercase0001" x="40" y="314" width="31" height="50"/> - <SubTexture name="b lowercase0002" x="73" y="314" width="31" height="50"/> - <SubTexture name="b lowercase0003" x="73" y="314" width="31" height="50"/> - <SubTexture name="c lowercase0000" x="106" y="314" width="33" height="33" pivotX="0" pivotY="0"/> - <SubTexture name="c lowercase0001" x="106" y="314" width="33" height="33"/> - <SubTexture name="c lowercase0002" x="141" y="314" width="32" height="33" frameX="0" frameY="0" frameWidth="33" frameHeight="33"/> - <SubTexture name="c lowercase0003" x="141" y="314" width="32" height="33" frameX="0" frameY="0" frameWidth="33" frameHeight="33"/> - <SubTexture name="comma0000" x="175" y="314" width="19" height="23" pivotX="0" pivotY="0"/> - <SubTexture name="comma0001" x="175" y="314" width="19" height="23"/> - <SubTexture name="comma0002" x="196" y="314" width="19" height="23"/> - <SubTexture name="comma0003" x="196" y="314" width="19" height="23"/> - <SubTexture name="d lowercase0000" x="217" y="314" width="32" height="53" pivotX="0" pivotY="0"/> - <SubTexture name="d lowercase0001" x="217" y="314" width="32" height="53"/> - <SubTexture name="d lowercase0002" x="251" y="314" width="32" height="53"/> - <SubTexture name="d lowercase0003" x="251" y="314" width="32" height="53"/> - <SubTexture name="down arrow0000" x="285" y="314" width="39" height="44" pivotX="0" pivotY="0"/> - <SubTexture name="down arrow0001" x="285" y="314" width="39" height="44"/> - <SubTexture name="down arrow0002" x="326" y="314" width="38" height="44" frameX="0" frameY="0" frameWidth="39" frameHeight="44"/> - <SubTexture name="down arrow0003" x="326" y="314" width="38" height="44" frameX="0" frameY="0" frameWidth="39" frameHeight="44"/> - <SubTexture name="e lowercase0000" x="366" y="314" width="33" height="37" pivotX="0" pivotY="0"/> - <SubTexture name="e lowercase0001" x="366" y="314" width="33" height="37"/> - <SubTexture name="e lowercase0002" x="401" y="314" width="33" height="37"/> - <SubTexture name="e lowercase0003" x="401" y="314" width="33" height="37"/> - <SubTexture name="end parentheses0000" x="436" y="314" width="28" height="36" pivotX="0" pivotY="0"/> - <SubTexture name="end parentheses0001" x="436" y="314" width="28" height="36"/> - <SubTexture name="end parentheses0002" x="466" y="314" width="28" height="36"/> - <SubTexture name="end parentheses0003" x="466" y="314" width="28" height="36"/> - <SubTexture name="exclamation point0000" x="496" y="314" width="17" height="64" pivotX="0" pivotY="0"/> - <SubTexture name="exclamation point0001" x="496" y="314" width="17" height="64"/> - <SubTexture name="exclamation point0002" x="515" y="314" width="16" height="64" frameX="0" frameY="0" frameWidth="17" frameHeight="64"/> - <SubTexture name="exclamation point0003" x="515" y="314" width="16" height="64" frameX="0" frameY="0" frameWidth="17" frameHeight="64"/> - <SubTexture name="f lowercase0000" x="533" y="314" width="27" height="48" pivotX="0" pivotY="0"/> - <SubTexture name="f lowercase0001" x="533" y="314" width="27" height="48"/> - <SubTexture name="f lowercase0002" x="562" y="314" width="27" height="48"/> - <SubTexture name="f lowercase0003" x="562" y="314" width="27" height="48"/> - <SubTexture name="forward slash0000" x="591" y="314" width="46" height="58" pivotX="0" pivotY="0"/> - <SubTexture name="forward slash0001" x="591" y="314" width="46" height="58"/> - <SubTexture name="forward slash0002" x="639" y="314" width="46" height="58"/> - <SubTexture name="forward slash0003" x="639" y="314" width="46" height="58"/> - <SubTexture name="g lowercase0000" x="687" y="314" width="33" height="41" pivotX="0" pivotY="0"/> - <SubTexture name="g lowercase0001" x="687" y="314" width="33" height="41"/> - <SubTexture name="g lowercase0002" x="722" y="314" width="33" height="41"/> - <SubTexture name="g lowercase0003" x="722" y="314" width="33" height="41"/> - <SubTexture name="h lowercase0000" x="757" y="314" width="29" height="48" pivotX="0" pivotY="0"/> - <SubTexture name="h lowercase0001" x="757" y="314" width="29" height="48"/> - <SubTexture name="h lowercase0002" x="788" y="314" width="29" height="48"/> - <SubTexture name="h lowercase0003" x="788" y="314" width="29" height="48"/> - <SubTexture name="heart0000" x="819" y="314" width="43" height="41" pivotX="0" pivotY="0"/> - <SubTexture name="heart0001" x="819" y="314" width="43" height="41"/> - <SubTexture name="heart0002" x="864" y="314" width="43" height="41"/> - <SubTexture name="heart0003" x="864" y="314" width="43" height="41"/> - <SubTexture name="i lowercase0000" x="2" y="380" width="13" height="42" pivotX="0" pivotY="0"/> - <SubTexture name="i lowercase0001" x="2" y="380" width="13" height="42"/> - <SubTexture name="i lowercase0002" x="17" y="380" width="13" height="42"/> - <SubTexture name="i lowercase0003" x="17" y="380" width="13" height="42"/> - <SubTexture name="j lowercase0000" x="32" y="380" width="28" height="48" pivotX="0.25" pivotY="0"/> - <SubTexture name="j lowercase0001" x="32" y="380" width="28" height="48"/> - <SubTexture name="j lowercase0002" x="62" y="380" width="28" height="48"/> - <SubTexture name="j lowercase0003" x="62" y="380" width="28" height="48"/> - <SubTexture name="k lowercase0000" x="92" y="380" width="35" height="48" pivotX="0.05" pivotY="0"/> - <SubTexture name="k lowercase0001" x="92" y="380" width="35" height="48"/> - <SubTexture name="k lowercase0002" x="129" y="380" width="35" height="48"/> - <SubTexture name="k lowercase0003" x="129" y="380" width="35" height="48"/> - <SubTexture name="l lowercase0000" x="166" y="380" width="10" height="48" pivotX="0" pivotY="0"/> - <SubTexture name="l lowercase0001" x="166" y="380" width="10" height="48"/> - <SubTexture name="l lowercase0002" x="178" y="380" width="10" height="48"/> - <SubTexture name="l lowercase0003" x="178" y="380" width="10" height="48"/> - <SubTexture name="left arrow0000" x="190" y="380" width="41" height="39" pivotX="0" pivotY="0"/> - <SubTexture name="left arrow0001" x="190" y="380" width="41" height="39"/> - <SubTexture name="left arrow0002" x="233" y="380" width="41" height="39"/> - <SubTexture name="left arrow0003" x="233" y="380" width="41" height="39"/> - <SubTexture name="m lowercase0000" x="276" y="380" width="41" height="27" pivotX="0" pivotY="0"/> - <SubTexture name="m lowercase0001" x="276" y="380" width="41" height="27"/> - <SubTexture name="m lowercase0002" x="319" y="380" width="41" height="27"/> - <SubTexture name="m lowercase0003" x="319" y="380" width="41" height="27"/> - <SubTexture name="multiply x0000" x="362" y="380" width="34" height="32" pivotX="0" pivotY="0"/> - <SubTexture name="multiply x0001" x="362" y="380" width="34" height="32"/> - <SubTexture name="multiply x0002" x="398" y="380" width="34" height="32"/> - <SubTexture name="multiply x0003" x="398" y="380" width="34" height="32"/> - <SubTexture name="n lowercase0000" x="434" y="380" width="27" height="30" pivotX="0" pivotY="0"/> - <SubTexture name="n lowercase0001" x="434" y="380" width="27" height="30"/> - <SubTexture name="n lowercase0002" x="463" y="380" width="27" height="30"/> - <SubTexture name="n lowercase0003" x="463" y="380" width="27" height="30"/> - <SubTexture name="o lowercase0000" x="492" y="380" width="30" height="32" pivotX="0" pivotY="0"/> - <SubTexture name="o lowercase0001" x="492" y="380" width="30" height="32"/> - <SubTexture name="o lowercase0002" x="524" y="380" width="30" height="32"/> - <SubTexture name="o lowercase0003" x="524" y="380" width="30" height="32"/> - <SubTexture name="p lowercase0000" x="556" y="380" width="28" height="43" pivotX="0.4" pivotY="0"/> - <SubTexture name="p lowercase0001" x="556" y="380" width="28" height="43"/> - <SubTexture name="p lowercase0002" x="586" y="380" width="28" height="43"/> - <SubTexture name="p lowercase0003" x="586" y="380" width="28" height="43"/> - <SubTexture name="period0000" x="616" y="380" width="14" height="14" pivotX="0" pivotY="0.1"/> - <SubTexture name="period0001" x="616" y="380" width="14" height="14"/> - <SubTexture name="period0002" x="632" y="380" width="14" height="14"/> - <SubTexture name="period0003" x="632" y="380" width="14" height="14"/> - <SubTexture name="q lowercase0000" x="648" y="380" width="36" height="43" pivotX="0.05" pivotY="0"/> - <SubTexture name="q lowercase0001" x="648" y="380" width="36" height="43"/> - <SubTexture name="q lowercase0002" x="686" y="380" width="36" height="43"/> - <SubTexture name="q lowercase0003" x="686" y="380" width="36" height="43"/> - <SubTexture name="question mark0000" x="724" y="380" width="39" height="65" pivotX="0" pivotY="0"/> - <SubTexture name="question mark0001" x="724" y="380" width="39" height="65"/> - <SubTexture name="question mark0002" x="765" y="380" width="39" height="65"/> - <SubTexture name="question mark0003" x="765" y="380" width="39" height="65"/> - <SubTexture name="r lowercase0000" x="806" y="380" width="24" height="29" pivotX="0" pivotY="0"/> - <SubTexture name="r lowercase0001" x="806" y="380" width="24" height="29"/> - <SubTexture name="r lowercase0002" x="832" y="380" width="24" height="29"/> - <SubTexture name="r lowercase0003" x="832" y="380" width="24" height="29"/> - <SubTexture name="right arrow0000" x="858" y="380" width="40" height="36" pivotX="0" pivotY="0"/> - <SubTexture name="right arrow0001" x="858" y="380" width="40" height="36"/> - <SubTexture name="right arrow0002" x="2" y="447" width="40" height="36"/> - <SubTexture name="right arrow0003" x="2" y="447" width="40" height="36"/> - <SubTexture name="s lowercase0000" x="44" y="447" width="27" height="35" pivotX="0" pivotY="0.05"/> - <SubTexture name="s lowercase0001" x="44" y="447" width="27" height="35"/> - <SubTexture name="s lowercase0002" x="73" y="447" width="27" height="35"/> - <SubTexture name="s lowercase0003" x="73" y="447" width="27" height="35"/> - <SubTexture name="start parentheses0000" x="102" y="447" width="31" height="33" pivotX="0" pivotY="0"/> - <SubTexture name="start parentheses0001" x="102" y="447" width="31" height="33"/> - <SubTexture name="start parentheses0002" x="135" y="447" width="31" height="32" frameX="0" frameY="0" frameWidth="31" frameHeight="33"/> - <SubTexture name="start parentheses0003" x="135" y="447" width="31" height="32" frameX="0" frameY="0" frameWidth="31" frameHeight="33"/> - <SubTexture name="t lowercase0000" x="168" y="447" width="28" height="48" pivotX="0" pivotY="0"/> - <SubTexture name="t lowercase0001" x="168" y="447" width="28" height="48"/> - <SubTexture name="t lowercase0002" x="198" y="447" width="28" height="48"/> - <SubTexture name="t lowercase0003" x="198" y="447" width="28" height="48"/> - <SubTexture name="u lowercase0000" x="228" y="447" width="31" height="33" pivotX="0.1" pivotY="0"/> - <SubTexture name="u lowercase0001" x="228" y="447" width="31" height="33"/> - <SubTexture name="u lowercase0002" x="261" y="447" width="31" height="33"/> - <SubTexture name="u lowercase0003" x="261" y="447" width="31" height="33"/> - <SubTexture name="up arrow0000" x="294" y="447" width="36" height="42" pivotX="0" pivotY="0"/> - <SubTexture name="up arrow0001" x="294" y="447" width="36" height="42"/> - <SubTexture name="up arrow0002" x="332" y="447" width="36" height="42"/> - <SubTexture name="up arrow0003" x="332" y="447" width="36" height="42"/> - <SubTexture name="v lowercase0000" x="370" y="447" width="32" height="31" pivotX="0" pivotY="0"/> - <SubTexture name="v lowercase0001" x="370" y="447" width="32" height="31"/> - <SubTexture name="v lowercase0002" x="404" y="447" width="31" height="31" frameX="0" frameY="0" frameWidth="32" frameHeight="31"/> - <SubTexture name="v lowercase0003" x="404" y="447" width="31" height="31" frameX="0" frameY="0" frameWidth="32" frameHeight="31"/> - <SubTexture name="w lowercase0000" x="437" y="447" width="42" height="31" pivotX="0" pivotY="0"/> - <SubTexture name="w lowercase0001" x="437" y="447" width="42" height="31"/> - <SubTexture name="w lowercase0002" x="481" y="447" width="42" height="31"/> - <SubTexture name="w lowercase0003" x="481" y="447" width="42" height="31"/> - <SubTexture name="x lowercase0000" x="525" y="447" width="33" height="33" pivotX="0" pivotY="0"/> - <SubTexture name="x lowercase0001" x="525" y="447" width="33" height="33"/> - <SubTexture name="x lowercase0002" x="560" y="447" width="33" height="33"/> - <SubTexture name="x lowercase0003" x="560" y="447" width="33" height="33"/> - <SubTexture name="y lowercase0000" x="595" y="447" width="35" height="42" pivotX="0" pivotY="0"/> - <SubTexture name="y lowercase0001" x="595" y="447" width="35" height="42"/> - <SubTexture name="y lowercase0002" x="632" y="447" width="35" height="42"/> - <SubTexture name="y lowercase0003" x="632" y="447" width="35" height="42"/> - <SubTexture name="z lowercase0000" x="669" y="447" width="38" height="31" pivotX="0" pivotY="0"/> - <SubTexture name="z lowercase0001" x="669" y="447" width="38" height="31"/> - <SubTexture name="z lowercase0002" x="709" y="447" width="37" height="31" frameX="0" frameY="0" frameWidth="38" frameHeight="31"/> - <SubTexture name="z lowercase0003" x="709" y="447" width="37" height="31" frameX="0" frameY="0" frameWidth="38" frameHeight="31"/> - <SubTexture name="|0000" x="748" y="447" width="11" height="70" pivotX="0" pivotY="0.05"/> - <SubTexture name="|0001" x="748" y="447" width="11" height="70"/> - <SubTexture name="|0002" x="761" y="447" width="11" height="69" frameX="0" frameY="0" frameWidth="11" frameHeight="70"/> - <SubTexture name="|0003" x="761" y="447" width="11" height="69" frameX="0" frameY="0" frameWidth="11" frameHeight="70"/> - <SubTexture name="~0000" x="774" y="447" width="43" height="21" pivotX="0" pivotY="0"/> - <SubTexture name="~0001" x="774" y="447" width="43" height="21"/> - <SubTexture name="~0002" x="819" y="447" width="43" height="21"/> - <SubTexture name="~0003" x="819" y="447" width="43" height="21"/> -</TextureAtlas> +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="alphabet.png" width="692" height="689"> + <SubTexture name="character-a-capital0000" x="209" y="191" width="45" height="59" frameX="-0" frameY="-0" frameWidth="45" frameHeight="59"/> + <SubTexture name="character-a-capital0001" x="1" y="257" width="45" height="59" frameX="-0" frameY="-0" frameWidth="45" frameHeight="59"/> + <SubTexture name="character-a-lowercase0000" x="619" y="48" width="36" height="38" frameX="-0" frameY="-0" frameWidth="36" frameHeight="38"/> + <SubTexture name="character-a-lowercase0001" x="619" y="89" width="36" height="38" frameX="-0" frameY="-0" frameWidth="36" frameHeight="38"/> + <SubTexture name="character-ampersand0000" x="223" y="253" width="36" height="58" frameX="-0" frameY="-0" frameWidth="36" frameHeight="58"/> + <SubTexture name="character-ampersand0001" x="275" y="1" width="36" height="58" frameX="-0" frameY="-0" frameWidth="36" frameHeight="58"/> + <SubTexture name="character-anglebracket-left0000" x="311" y="181" width="38" height="56" frameX="-0" frameY="-0" frameWidth="38" frameHeight="56"/> + <SubTexture name="character-anglebracket-left0001" x="311" y="240" width="38" height="56" frameX="-0" frameY="-0" frameWidth="38" frameHeight="56"/> + <SubTexture name="character-anglebracket-right0000" x="168" y="189" width="38" height="61" frameX="-0" frameY="-0" frameWidth="38" frameHeight="61"/> + <SubTexture name="character-anglebracket-right0001" x="83" y="191" width="38" height="61" frameX="-0" frameY="-0" frameWidth="38" frameHeight="61"/> + <SubTexture name="character-angryfaic0000" x="439" y="1" width="54" height="54" frameX="-0" frameY="-0" frameWidth="54" frameHeight="54"/> + <SubTexture name="character-angryfaic0001" x="439" y="58" width="54" height="54" frameX="-0" frameY="-0" frameWidth="54" frameHeight="54"/> + <SubTexture name="character-arrow-down0000" x="507" y="546" width="39" height="44" frameX="-0" frameY="-0" frameWidth="39" frameHeight="44"/> + <SubTexture name="character-arrow-down0001" x="578" y="1" width="39" height="44" frameX="-0" frameY="-0" frameWidth="39" frameHeight="44"/> + <SubTexture name="character-arrow-left0000" x="312" y="605" width="41" height="39" frameX="-0" frameY="-0" frameWidth="41" frameHeight="39"/> + <SubTexture name="character-arrow-left0001" x="356" y="605" width="41" height="39" frameX="-0" frameY="-0" frameWidth="41" frameHeight="39"/> + <SubTexture name="character-arrow-right0000" x="488" y="600" width="40" height="36" frameX="-0" frameY="-0" frameWidth="40" frameHeight="36"/> + <SubTexture name="character-arrow-right0001" x="531" y="593" width="40" height="36" frameX="-0" frameY="-0" frameWidth="40" frameHeight="36"/> + <SubTexture name="character-arrow-up0000" x="580" y="529" width="36" height="42" frameX="-0" frameY="-0" frameWidth="36" frameHeight="42"/> + <SubTexture name="character-arrow-up0001" x="580" y="574" width="36" height="42" frameX="-0" frameY="-0" frameWidth="36" frameHeight="42"/> + <SubTexture name="character-ash-capital0000" x="29" y="1" width="67" height="55" frameX="-0" frameY="-0" frameWidth="67" frameHeight="55"/> + <SubTexture name="character-ash-capital0001" x="29" y="59" width="67" height="55" frameX="-0" frameY="-0" frameWidth="67" frameHeight="55"/> + <SubTexture name="character-ash-lowercase0000" x="465" y="451" width="51" height="38" frameX="-0" frameY="-0" frameWidth="51" frameHeight="38"/> + <SubTexture name="character-ash-lowercase0001" x="179" y="498" width="51" height="38" frameX="-0" frameY="-0" frameWidth="51" frameHeight="38"/> + <SubTexture name="character-asterisk0000" x="487" y="655" width="27" height="31" frameX="-0" frameY="-0" frameWidth="27" frameHeight="31"/> + <SubTexture name="character-asterisk0001" x="517" y="655" width="27" height="31" frameX="-0" frameY="-0" frameWidth="27" frameHeight="31"/> + <SubTexture name="character-at0000" x="1" y="319" width="52" height="56" frameX="-0" frameY="-0" frameWidth="52" frameHeight="56"/> + <SubTexture name="character-at0001" x="56" y="318" width="52" height="56" frameX="-0" frameY="-0" frameWidth="52" frameHeight="56"/> + <SubTexture name="character-b-capital0000" x="49" y="256" width="38" height="59" frameX="-0" frameY="-0" frameWidth="38" frameHeight="59"/> + <SubTexture name="character-b-capital0001" x="90" y="255" width="38" height="59" frameX="-0" frameY="-0" frameWidth="38" frameHeight="59"/> + <SubTexture name="character-b-lowercase0000" x="390" y="290" width="31" height="50" frameX="-0" frameY="-0" frameWidth="31" frameHeight="50"/> + <SubTexture name="character-b-lowercase0001" x="534" y="225" width="31" height="50" frameX="-0" frameY="-0" frameWidth="31" frameHeight="50"/> + <SubTexture name="character-backslash0000" x="131" y="254" width="43" height="59" frameX="-0" frameY="-0" frameWidth="43" frameHeight="59"/> + <SubTexture name="character-backslash0001" x="177" y="253" width="43" height="59" frameX="-0" frameY="-0" frameWidth="43" frameHeight="59"/> + <SubTexture name="character-backtick0000" x="661" y="440" width="19" height="22" frameX="-0" frameY="-0" frameWidth="19" frameHeight="22"/> + <SubTexture name="character-backtick0001" x="661" y="465" width="19" height="22" frameX="-0" frameY="-0" frameWidth="19" frameHeight="22"/> + <SubTexture name="character-c-capital0000" x="262" y="249" width="45" height="57" frameX="-0" frameY="-0" frameWidth="45" frameHeight="57"/> + <SubTexture name="character-c-capital0001" x="314" y="1" width="45" height="57" frameX="-0" frameY="-0" frameWidth="45" frameHeight="57"/> + <SubTexture name="character-c-lowercase0000" x="1" y="655" width="33" height="33" frameX="-0" frameY="-0" frameWidth="33" frameHeight="33"/> + <SubTexture name="character-c-lowercase0001" x="37" y="655" width="33" height="33" frameX="-0" frameY="-0" frameWidth="33" frameHeight="33"/> + <SubTexture name="character-caret0000" x="281" y="655" width="32" height="27" frameX="-0" frameY="-0" frameWidth="32" frameHeight="27"/> + <SubTexture name="character-caret0001" x="316" y="655" width="32" height="27" frameX="-0" frameY="-0" frameWidth="32" frameHeight="27"/> + <SubTexture name="character-colon0000" x="562" y="491" width="14" height="40" frameX="-0" frameY="-0" frameWidth="14" frameHeight="40"/> + <SubTexture name="character-colon0001" x="593" y="140" width="14" height="40" frameX="-0" frameY="-0" frameWidth="14" frameHeight="40"/> + <SubTexture name="character-comma0000" x="661" y="284" width="19" height="23" frameX="-0" frameY="-0" frameWidth="19" frameHeight="23"/> + <SubTexture name="character-comma0001" x="661" y="310" width="19" height="23" frameX="-0" frameY="-0" frameWidth="19" frameHeight="23"/> + <SubTexture name="character-curlybracket-left0000" x="29" y="191" width="24" height="62" frameX="-0" frameY="-0" frameWidth="24" frameHeight="62"/> + <SubTexture name="character-curlybracket-left0001" x="56" y="191" width="24" height="62" frameX="-0" frameY="-0" frameWidth="24" frameHeight="62"/> + <SubTexture name="character-curlybracket-right0000" x="231" y="65" width="23" height="60" frameX="-0" frameY="-0" frameWidth="23" frameHeight="60"/> + <SubTexture name="character-curlybracket-right0001" x="231" y="128" width="23" height="60" frameX="-0" frameY="-0" frameWidth="23" frameHeight="60"/> + <SubTexture name="character-d-capital0000" x="310" y="299" width="48" height="56" frameX="-0" frameY="-0" frameWidth="48" frameHeight="56"/> + <SubTexture name="character-d-capital0001" x="111" y="317" width="48" height="56" frameX="-0" frameY="-0" frameWidth="48" frameHeight="56"/> + <SubTexture name="character-d-lowercase0000" x="75" y="436" width="32" height="53" frameX="-0" frameY="-0" frameWidth="32" frameHeight="53"/> + <SubTexture name="character-d-lowercase0001" x="110" y="435" width="32" height="53" frameX="-0" frameY="-0" frameWidth="32" frameHeight="53"/> + <SubTexture name="character-dollar0000" x="85" y="123" width="40" height="64" frameX="-0" frameY="-0" frameWidth="40" frameHeight="64"/> + <SubTexture name="character-dollar0001" x="169" y="1" width="40" height="64" frameX="-0" frameY="-0" frameWidth="40" frameHeight="64"/> + <SubTexture name="character-doublequote-end0000" x="619" y="300" width="28" height="36" frameX="-0" frameY="-0" frameWidth="28" frameHeight="36"/> + <SubTexture name="character-doublequote-end0001" x="619" y="339" width="28" height="36" frameX="-0" frameY="-0" frameWidth="28" frameHeight="36"/> + <SubTexture name="character-doublequote-start0000" x="73" y="655" width="31" height="33" frameX="-0" frameY="-0" frameWidth="31" frameHeight="33"/> + <SubTexture name="character-doublequote-start0001" x="107" y="655" width="31" height="33" frameX="-0" frameY="-0" frameWidth="31" frameHeight="33"/> + <SubTexture name="character-e-capital0000" x="145" y="434" width="34" height="53" frameX="-0" frameY="-0" frameWidth="34" frameHeight="53"/> + <SubTexture name="character-e-capital0001" x="182" y="432" width="34" height="53" frameX="-0" frameY="-0" frameWidth="34" frameHeight="53"/> + <SubTexture name="character-e-lowercase0000" x="619" y="220" width="33" height="37" frameX="-0" frameY="-0" frameWidth="33" frameHeight="37"/> + <SubTexture name="character-e-lowercase0001" x="619" y="260" width="33" height="37" frameX="-0" frameY="-0" frameWidth="33" frameHeight="37"/> + <SubTexture name="character-eight0000" x="362" y="60" width="33" height="55" frameX="-0" frameY="-0" frameWidth="33" frameHeight="55"/> + <SubTexture name="character-eight0001" x="362" y="118" width="33" height="55" frameX="-0" frameY="-0" frameWidth="33" frameHeight="55"/> + <SubTexture name="character-equal0000" x="574" y="619" width="39" height="29" frameX="-0" frameY="-0" frameWidth="39" frameHeight="29"/> + <SubTexture name="character-equal0001" x="620" y="1" width="39" height="29" frameX="-0" frameY="-0" frameWidth="39" frameHeight="29"/> + <SubTexture name="character-ethel-capital0000" x="99" y="1" width="67" height="58" frameX="-0" frameY="-0" frameWidth="67" frameHeight="58"/> + <SubTexture name="character-ethel-capital0001" x="99" y="62" width="67" height="58" frameX="-0" frameY="-0" frameWidth="67" frameHeight="58"/> + <SubTexture name="character-ethel-lowercase0000" x="477" y="131" width="52" height="37" frameX="-0" frameY="-0" frameWidth="52" frameHeight="37"/> + <SubTexture name="character-ethel-lowercase0001" x="399" y="456" width="52" height="37" frameX="-0" frameY="-0" frameWidth="52" frameHeight="37"/> + <SubTexture name="character-exclamationmark0000" x="128" y="123" width="17" height="64" frameX="-0" frameY="-0" frameWidth="17" frameHeight="64"/> + <SubTexture name="character-exclamationmark0001" x="148" y="123" width="17" height="64" frameX="-0" frameY="-0" frameWidth="17" frameHeight="64"/> + <SubTexture name="character-f-capital0000" x="1" y="378" width="36" height="55" frameX="-0" frameY="-0" frameWidth="36" frameHeight="55"/> + <SubTexture name="character-f-capital0001" x="40" y="378" width="36" height="55" frameX="-0" frameY="-0" frameWidth="36" frameHeight="55"/> + <SubTexture name="character-f-lowercase0000" x="535" y="57" width="27" height="48" frameX="-0" frameY="-0" frameWidth="27" frameHeight="48"/> + <SubTexture name="character-f-lowercase0001" x="97" y="553" width="27" height="48" frameX="-0" frameY="-0" frameWidth="27" frameHeight="48"/> + <SubTexture name="character-five0000" x="219" y="432" width="40" height="53" frameX="-0" frameY="-0" frameWidth="40" frameHeight="53"/> + <SubTexture name="character-five0001" x="262" y="431" width="40" height="53" frameX="-0" frameY="-0" frameWidth="40" frameHeight="53"/> + <SubTexture name="character-four0000" x="439" y="115" width="35" height="54" frameX="-0" frameY="-0" frameWidth="35" frameHeight="54"/> + <SubTexture name="character-four0001" x="435" y="172" width="35" height="54" frameX="-0" frameY="-0" frameWidth="35" frameHeight="54"/> + <SubTexture name="character-g-capital0000" x="79" y="377" width="43" height="55" frameX="-0" frameY="-0" frameWidth="43" frameHeight="55"/> + <SubTexture name="character-g-capital0001" x="125" y="376" width="43" height="55" frameX="-0" frameY="-0" frameWidth="43" frameHeight="55"/> + <SubTexture name="character-g-lowercase0000" x="578" y="48" width="33" height="43" frameX="-0" frameY="-0" frameWidth="33" frameHeight="43"/> + <SubTexture name="character-g-lowercase0001" x="578" y="94" width="33" height="43" frameX="-0" frameY="-0" frameWidth="33" frameHeight="43"/> + <SubTexture name="character-h-capital0000" x="196" y="132" width="32" height="54" frameX="-0" frameY="-0" frameWidth="32" frameHeight="54"/> + <SubTexture name="character-h-capital0001" x="435" y="229" width="32" height="54" frameX="-0" frameY="-0" frameWidth="32" frameHeight="54"/> + <SubTexture name="character-h-lowercase0000" x="127" y="553" width="29" height="48" frameX="-0" frameY="-0" frameWidth="29" frameHeight="48"/> + <SubTexture name="character-h-lowercase0001" x="159" y="553" width="29" height="48" frameX="-0" frameY="-0" frameWidth="29" frameHeight="48"/> + <SubTexture name="character-heart0000" x="568" y="216" width="43" height="41" frameX="-0" frameY="-0" frameWidth="43" frameHeight="41"/> + <SubTexture name="character-heart0001" x="568" y="260" width="43" height="41" frameX="-0" frameY="-0" frameWidth="43" frameHeight="41"/> + <SubTexture name="character-i-capital0000" x="496" y="57" width="36" height="52" frameX="-0" frameY="-0" frameWidth="36" frameHeight="52"/> + <SubTexture name="character-i-capital0001" x="495" y="228" width="36" height="52" frameX="-0" frameY="-0" frameWidth="36" frameHeight="52"/> + <SubTexture name="character-i-lowercase0000" x="577" y="165" width="13" height="43" frameX="-0" frameY="-0" frameWidth="13" frameHeight="43"/> + <SubTexture name="character-i-lowercase0001" x="311" y="122" width="13" height="43" frameX="-0" frameY="-0" frameWidth="13" frameHeight="43"/> + <SubTexture name="character-j-capital0000" x="390" y="233" width="39" height="54" frameX="-0" frameY="-0" frameWidth="39" frameHeight="54"/> + <SubTexture name="character-j-capital0001" x="432" y="286" width="39" height="54" frameX="-0" frameY="-0" frameWidth="39" frameHeight="54"/> + <SubTexture name="character-j-lowercase0000" x="271" y="65" width="28" height="57" frameX="-0" frameY="-0" frameWidth="28" frameHeight="57"/> + <SubTexture name="character-j-lowercase0001" x="302" y="62" width="28" height="57" frameX="-0" frameY="-0" frameWidth="28" frameHeight="57"/> + <SubTexture name="character-k-capital0000" x="476" y="341" width="35" height="52" frameX="-0" frameY="-0" frameWidth="35" frameHeight="52"/> + <SubTexture name="character-k-capital0001" x="476" y="396" width="35" height="52" frameX="-0" frameY="-0" frameWidth="35" frameHeight="52"/> + <SubTexture name="character-k-lowercase0000" x="356" y="526" width="35" height="48" frameX="-0" frameY="-0" frameWidth="35" frameHeight="48"/> + <SubTexture name="character-k-lowercase0001" x="191" y="539" width="35" height="48" frameX="-0" frameY="-0" frameWidth="35" frameHeight="48"/> + <SubTexture name="character-l-capital0000" x="262" y="309" width="34" height="56" frameX="-0" frameY="-0" frameWidth="34" frameHeight="56"/> + <SubTexture name="character-l-capital0001" x="223" y="314" width="34" height="56" frameX="-0" frameY="-0" frameWidth="34" frameHeight="56"/> + <SubTexture name="character-l-lowercase0000" x="565" y="55" width="10" height="48" frameX="-0" frameY="-0" frameWidth="10" frameHeight="48"/> + <SubTexture name="character-l-lowercase0001" x="564" y="165" width="10" height="48" frameX="-0" frameY="-0" frameWidth="10" frameHeight="48"/> + <SubTexture name="character-m-capital0000" x="305" y="426" width="44" height="53" frameX="-0" frameY="-0" frameWidth="44" frameHeight="53"/> + <SubTexture name="character-m-capital0001" x="352" y="416" width="44" height="53" frameX="-0" frameY="-0" frameWidth="44" frameHeight="53"/> + <SubTexture name="character-m-lowercase0000" x="400" y="619" width="41" height="27" frameX="-0" frameY="-0" frameWidth="41" frameHeight="27"/> + <SubTexture name="character-m-lowercase0001" x="444" y="619" width="41" height="27" frameX="-0" frameY="-0" frameWidth="41" frameHeight="27"/> + <SubTexture name="character-minus0000" x="267" y="603" width="37" height="13" frameX="-0" frameY="-0" frameWidth="37" frameHeight="13"/> + <SubTexture name="character-minus0001" x="488" y="639" width="37" height="13" frameX="-0" frameY="-0" frameWidth="37" frameHeight="13"/> + <SubTexture name="character-multiply0000" x="619" y="454" width="34" height="32" frameX="-0" frameY="-0" frameWidth="34" frameHeight="32"/> + <SubTexture name="character-multiply0001" x="619" y="489" width="34" height="32" frameX="-0" frameY="-0" frameWidth="34" frameHeight="32"/> + <SubTexture name="character-n-capital0000" x="361" y="472" width="32" height="51" frameX="-0" frameY="-0" frameWidth="32" frameHeight="51"/> + <SubTexture name="character-n-capital0001" x="233" y="498" width="32" height="51" frameX="-0" frameY="-0" frameWidth="32" frameHeight="51"/> + <SubTexture name="character-n-lowercase0000" x="547" y="653" width="27" height="30" frameX="-0" frameY="-0" frameWidth="27" frameHeight="30"/> + <SubTexture name="character-n-lowercase0001" x="577" y="653" width="27" height="30" frameX="-0" frameY="-0" frameWidth="27" frameHeight="30"/> + <SubTexture name="character-nine0000" x="177" y="315" width="33" height="56" frameX="-0" frameY="-0" frameWidth="33" frameHeight="56"/> + <SubTexture name="character-nine0001" x="362" y="1" width="33" height="56" frameX="-0" frameY="-0" frameWidth="33" frameHeight="56"/> + <SubTexture name="character-o-capital0000" x="390" y="343" width="40" height="54" frameX="-0" frameY="-0" frameWidth="40" frameHeight="54"/> + <SubTexture name="character-o-capital0001" x="433" y="343" width="40" height="54" frameX="-0" frameY="-0" frameWidth="40" frameHeight="54"/> + <SubTexture name="character-o-lowercase0000" x="351" y="655" width="30" height="32" frameX="-0" frameY="-0" frameWidth="30" frameHeight="32"/> + <SubTexture name="character-o-lowercase0001" x="384" y="655" width="30" height="32" frameX="-0" frameY="-0" frameWidth="30" frameHeight="32"/> + <SubTexture name="character-one0000" x="470" y="229" width="22" height="53" frameX="-0" frameY="-0" frameWidth="22" frameHeight="53"/> + <SubTexture name="character-one0001" x="399" y="400" width="22" height="53" frameX="-0" frameY="-0" frameWidth="22" frameHeight="53"/> + <SubTexture name="character-p-capital0000" x="352" y="181" width="35" height="56" frameX="-0" frameY="-0" frameWidth="35" frameHeight="56"/> + <SubTexture name="character-p-capital0001" x="352" y="240" width="35" height="56" frameX="-0" frameY="-0" frameWidth="35" frameHeight="56"/> + <SubTexture name="character-p-lowercase0000" x="559" y="391" width="28" height="43" frameX="-0" frameY="-0" frameWidth="28" frameHeight="43"/> + <SubTexture name="character-p-lowercase0001" x="549" y="546" width="28" height="43" frameX="-0" frameY="-0" frameWidth="28" frameHeight="43"/> + <SubTexture name="character-percent0000" x="171" y="374" width="48" height="55" frameX="-0" frameY="-0" frameWidth="48" frameHeight="55"/> + <SubTexture name="character-percent0001" x="222" y="373" width="48" height="55" frameX="-0" frameY="-0" frameWidth="48" frameHeight="55"/> + <SubTexture name="character-period0000" x="469" y="600" width="14" height="14" frameX="-0" frameY="-0" frameWidth="14" frameHeight="14"/> + <SubTexture name="character-period0001" x="653" y="586" width="14" height="14" frameX="-0" frameY="-0" frameWidth="14" frameHeight="14"/> + <SubTexture name="character-plus0000" x="1" y="606" width="42" height="39" frameX="-0" frameY="-0" frameWidth="42" frameHeight="39"/> + <SubTexture name="character-plus0001" x="46" y="606" width="42" height="39" frameX="-0" frameY="-0" frameWidth="42" frameHeight="39"/> + <SubTexture name="character-pound0000" x="1" y="498" width="41" height="52" frameX="-0" frameY="-0" frameWidth="41" frameHeight="52"/> + <SubTexture name="character-pound0001" x="45" y="498" width="41" height="52" frameX="-0" frameY="-0" frameWidth="41" frameHeight="52"/> + <SubTexture name="character-q-capital0000" x="268" y="498" width="41" height="51" frameX="-0" frameY="-0" frameWidth="41" frameHeight="51"/> + <SubTexture name="character-q-capital0001" x="312" y="498" width="41" height="51" frameX="-0" frameY="-0" frameWidth="41" frameHeight="51"/> + <SubTexture name="character-q-lowercase0000" x="580" y="437" width="36" height="43" frameX="-0" frameY="-0" frameWidth="36" frameHeight="43"/> + <SubTexture name="character-q-lowercase0001" x="580" y="483" width="36" height="43" frameX="-0" frameY="-0" frameWidth="36" frameHeight="43"/> + <SubTexture name="character-questionmark0000" x="1" y="123" width="39" height="65" frameX="-0" frameY="-0" frameWidth="39" frameHeight="65"/> + <SubTexture name="character-questionmark0001" x="43" y="123" width="39" height="65" frameX="-0" frameY="-0" frameWidth="39" frameHeight="65"/> + <SubTexture name="character-r-capital0000" x="273" y="368" width="40" height="55" frameX="-0" frameY="-0" frameWidth="40" frameHeight="55"/> + <SubTexture name="character-r-capital0001" x="316" y="358" width="40" height="55" frameX="-0" frameY="-0" frameWidth="40" frameHeight="55"/> + <SubTexture name="character-r-lowercase0000" x="607" y="653" width="24" height="29" frameX="-0" frameY="-0" frameWidth="24" frameHeight="29"/> + <SubTexture name="character-r-lowercase0001" x="634" y="653" width="24" height="29" frameX="-0" frameY="-0" frameWidth="24" frameHeight="29"/> + <SubTexture name="character-roundbracket-left0000" x="333" y="61" width="26" height="57" frameX="-0" frameY="-0" frameWidth="26" frameHeight="57"/> + <SubTexture name="character-roundbracket-left0001" x="333" y="121" width="26" height="57" frameX="-0" frameY="-0" frameWidth="26" frameHeight="57"/> + <SubTexture name="character-roundbracket-right0000" x="361" y="299" width="26" height="55" frameX="-0" frameY="-0" frameWidth="26" frameHeight="55"/> + <SubTexture name="character-roundbracket-right0001" x="361" y="357" width="26" height="55" frameX="-0" frameY="-0" frameWidth="26" frameHeight="55"/> + <SubTexture name="character-s-capital0000" x="398" y="1" width="38" height="55" frameX="-0" frameY="-0" frameWidth="38" frameHeight="55"/> + <SubTexture name="character-s-capital0001" x="398" y="59" width="38" height="55" frameX="-0" frameY="-0" frameWidth="38" frameHeight="55"/> + <SubTexture name="character-s-lowercase0000" x="619" y="378" width="27" height="35" frameX="-0" frameY="-0" frameWidth="27" frameHeight="35"/> + <SubTexture name="character-s-lowercase0001" x="619" y="416" width="27" height="35" frameX="-0" frameY="-0" frameWidth="27" frameHeight="35"/> + <SubTexture name="character-semicolon0000" x="546" y="444" width="15" height="44" frameX="-0" frameY="-0" frameWidth="15" frameHeight="44"/> + <SubTexture name="character-semicolon0001" x="1" y="74" width="15" height="44" frameX="-0" frameY="-0" frameWidth="15" frameHeight="44"/> + <SubTexture name="character-seven0000" x="229" y="552" width="39" height="48" frameX="-0" frameY="-0" frameWidth="39" frameHeight="48"/> + <SubTexture name="character-seven0001" x="271" y="552" width="39" height="48" frameX="-0" frameY="-0" frameWidth="39" frameHeight="48"/> + <SubTexture name="character-singlequote0000" x="661" y="51" width="17" height="27" frameX="-0" frameY="-0" frameWidth="17" frameHeight="27"/> + <SubTexture name="character-singlequote0001" x="661" y="81" width="17" height="27" frameX="-0" frameY="-0" frameWidth="17" frameHeight="27"/> + <SubTexture name="character-six0000" x="424" y="400" width="38" height="53" frameX="-0" frameY="-0" frameWidth="38" frameHeight="53"/> + <SubTexture name="character-six0001" x="496" y="1" width="38" height="53" frameX="-0" frameY="-0" frameWidth="38" frameHeight="53"/> + <SubTexture name="character-slash0000" x="262" y="127" width="46" height="58" frameX="-0" frameY="-0" frameWidth="46" frameHeight="58"/> + <SubTexture name="character-slash0001" x="262" y="188" width="46" height="58" frameX="-0" frameY="-0" frameWidth="46" frameHeight="58"/> + <SubTexture name="character-squarebracket-left0000" x="124" y="190" width="25" height="61" frameX="-0" frameY="-0" frameWidth="25" frameHeight="61"/> + <SubTexture name="character-squarebracket-left0001" x="212" y="1" width="25" height="61" frameX="-0" frameY="-0" frameWidth="25" frameHeight="61"/> + <SubTexture name="character-squarebracket-right0000" x="168" y="123" width="25" height="63" frameX="-0" frameY="-0" frameWidth="25" frameHeight="63"/> + <SubTexture name="character-squarebracket-right0001" x="1" y="191" width="25" height="63" frameX="-0" frameY="-0" frameWidth="25" frameHeight="63"/> + <SubTexture name="character-t-capital0000" x="533" y="112" width="42" height="50" frameX="-0" frameY="-0" frameWidth="42" frameHeight="50"/> + <SubTexture name="character-t-capital0001" x="514" y="391" width="42" height="50" frameX="-0" frameY="-0" frameWidth="42" frameHeight="50"/> + <SubTexture name="character-t-lowercase0000" x="313" y="552" width="28" height="48" frameX="-0" frameY="-0" frameWidth="28" frameHeight="48"/> + <SubTexture name="character-t-lowercase0001" x="394" y="550" width="28" height="48" frameX="-0" frameY="-0" frameWidth="28" frameHeight="48"/> + <SubTexture name="character-three0000" x="398" y="117" width="34" height="55" frameX="-0" frameY="-0" frameWidth="34" frameHeight="55"/> + <SubTexture name="character-three0001" x="398" y="175" width="34" height="55" frameX="-0" frameY="-0" frameWidth="34" frameHeight="55"/> + <SubTexture name="character-tilde0000" x="568" y="304" width="43" height="21" frameX="-0" frameY="-0" frameWidth="43" frameHeight="21"/> + <SubTexture name="character-tilde0001" x="568" y="328" width="43" height="21" frameX="-0" frameY="-0" frameWidth="43" frameHeight="21"/> + <SubTexture name="character-two0000" x="396" y="496" width="36" height="51" frameX="-0" frameY="-0" frameWidth="36" frameHeight="51"/> + <SubTexture name="character-two0001" x="435" y="496" width="36" height="51" frameX="-0" frameY="-0" frameWidth="36" frameHeight="51"/> + <SubTexture name="character-u-capital0000" x="474" y="492" width="38" height="51" frameX="-0" frameY="-0" frameWidth="38" frameHeight="51"/> + <SubTexture name="character-u-capital0001" x="537" y="1" width="38" height="51" frameX="-0" frameY="-0" frameWidth="38" frameHeight="51"/> + <SubTexture name="character-u-lowercase0000" x="141" y="655" width="31" height="33" frameX="-0" frameY="-0" frameWidth="31" frameHeight="33"/> + <SubTexture name="character-u-lowercase0001" x="175" y="655" width="31" height="33" frameX="-0" frameY="-0" frameWidth="31" frameHeight="33"/> + <SubTexture name="character-underscore0000" x="305" y="482" width="53" height="13" frameX="-0" frameY="-0" frameWidth="53" frameHeight="13"/> + <SubTexture name="character-underscore0001" x="477" y="115" width="53" height="13" frameX="-0" frameY="-0" frameWidth="53" frameHeight="13"/> + <SubTexture name="character-v-capital0000" x="517" y="171" width="44" height="51" frameX="-0" frameY="-0" frameWidth="44" frameHeight="51"/> + <SubTexture name="character-v-capital0001" x="515" y="492" width="44" height="51" frameX="-0" frameY="-0" frameWidth="44" frameHeight="51"/> + <SubTexture name="character-v-lowercase0000" x="417" y="655" width="32" height="31" frameX="-0" frameY="-0" frameWidth="32" frameHeight="31"/> + <SubTexture name="character-v-lowercase0001" x="452" y="649" width="32" height="31" frameX="-0" frameY="-0" frameWidth="32" frameHeight="31"/> + <SubTexture name="character-verticalbar0000" x="1" y="1" width="11" height="70" frameX="-0" frameY="-0" frameWidth="11" frameHeight="70"/> + <SubTexture name="character-verticalbar0001" x="15" y="1" width="11" height="70" frameX="-0" frameY="-0" frameWidth="11" frameHeight="70"/> + <SubTexture name="character-w-capital0000" x="519" y="283" width="46" height="51" frameX="-0" frameY="-0" frameWidth="46" frameHeight="51"/> + <SubTexture name="character-w-capital0001" x="519" y="337" width="46" height="51" frameX="-0" frameY="-0" frameWidth="46" frameHeight="51"/> + <SubTexture name="character-w-lowercase0000" x="91" y="619" width="42" height="31" frameX="-0" frameY="-0" frameWidth="42" frameHeight="31"/> + <SubTexture name="character-w-lowercase0001" x="136" y="619" width="42" height="31" frameX="-0" frameY="-0" frameWidth="42" frameHeight="31"/> + <SubTexture name="character-x-capital0000" x="473" y="172" width="41" height="53" frameX="-0" frameY="-0" frameWidth="41" frameHeight="53"/> + <SubTexture name="character-x-capital0001" x="474" y="285" width="41" height="53" frameX="-0" frameY="-0" frameWidth="41" frameHeight="53"/> + <SubTexture name="character-x-lowercase0000" x="209" y="655" width="33" height="33" frameX="-0" frameY="-0" frameWidth="33" frameHeight="33"/> + <SubTexture name="character-x-lowercase0001" x="245" y="655" width="33" height="33" frameX="-0" frameY="-0" frameWidth="33" frameHeight="33"/> + <SubTexture name="character-y-capital0000" x="89" y="498" width="42" height="52" frameX="-0" frameY="-0" frameWidth="42" frameHeight="52"/> + <SubTexture name="character-y-capital0001" x="134" y="498" width="42" height="52" frameX="-0" frameY="-0" frameWidth="42" frameHeight="52"/> + <SubTexture name="character-y-lowercase0000" x="191" y="590" width="35" height="42" frameX="-0" frameY="-0" frameWidth="35" frameHeight="42"/> + <SubTexture name="character-y-lowercase0001" x="229" y="605" width="35" height="42" frameX="-0" frameY="-0" frameWidth="35" frameHeight="42"/> + <SubTexture name="character-z-capital0000" x="425" y="550" width="38" height="47" frameX="-0" frameY="-0" frameWidth="38" frameHeight="47"/> + <SubTexture name="character-z-capital0001" x="466" y="550" width="38" height="47" frameX="-0" frameY="-0" frameWidth="38" frameHeight="47"/> + <SubTexture name="character-z-lowercase0000" x="616" y="619" width="38" height="31" frameX="-0" frameY="-0" frameWidth="38" frameHeight="31"/> + <SubTexture name="character-z-lowercase0001" x="619" y="130" width="38" height="31" frameX="-0" frameY="-0" frameWidth="38" frameHeight="31"/> + <SubTexture name="character-zero0000" x="1" y="436" width="34" height="54" frameX="-0" frameY="-0" frameWidth="34" frameHeight="54"/> + <SubTexture name="character-zero0001" x="38" y="436" width="34" height="54" frameX="-0" frameY="-0" frameWidth="34" frameHeight="54"/> + <SubTexture name="diacritic-acute0000" x="181" y="635" width="16" height="17" frameX="-0" frameY="-0" frameWidth="16" frameHeight="17"/> + <SubTexture name="diacritic-acute0001" x="200" y="635" width="16" height="17" frameX="-0" frameY="-0" frameWidth="16" frameHeight="17"/> + <SubTexture name="diacritic-apostophe0000" x="435" y="600" width="14" height="16" frameX="-0" frameY="-0" frameWidth="14" frameHeight="16"/> + <SubTexture name="diacritic-apostophe0001" x="452" y="600" width="14" height="16" frameX="-0" frameY="-0" frameWidth="14" frameHeight="16"/> + <SubTexture name="diacritic-breve0000" x="661" y="111" width="27" height="19" frameX="-0" frameY="-0" frameWidth="27" frameHeight="19"/> + <SubTexture name="diacritic-breve0001" x="661" y="133" width="27" height="19" frameX="-0" frameY="-0" frameWidth="27" frameHeight="19"/> + <SubTexture name="diacritic-cadilla0000" x="661" y="336" width="20" height="23" frameX="-0" frameY="-0" frameWidth="20" frameHeight="23"/> + <SubTexture name="diacritic-cadilla0001" x="661" y="362" width="20" height="23" frameX="-0" frameY="-0" frameWidth="20" frameHeight="23"/> + <SubTexture name="diacritic-circumflex0000" x="590" y="414" width="24" height="18" frameX="-0" frameY="-0" frameWidth="24" frameHeight="18"/> + <SubTexture name="diacritic-circumflex0001" x="661" y="155" width="24" height="18" frameX="-0" frameY="-0" frameWidth="24" frameHeight="18"/> + <SubTexture name="diacritic-dot0000" x="670" y="586" width="14" height="14" frameX="-0" frameY="-0" frameWidth="14" frameHeight="14"/> + <SubTexture name="diacritic-dot0001" x="661" y="603" width="14" height="14" frameX="-0" frameY="-0" frameWidth="14" frameHeight="14"/> + <SubTexture name="diacritic-hookabove0000" x="661" y="176" width="19" height="24" frameX="-0" frameY="-0" frameWidth="19" frameHeight="24"/> + <SubTexture name="diacritic-hookabove0001" x="661" y="203" width="19" height="24" frameX="-0" frameY="-0" frameWidth="19" frameHeight="24"/> + <SubTexture name="diacritic-hookhorizontal0000" x="662" y="1" width="29" height="22" frameX="-0" frameY="-0" frameWidth="29" frameHeight="22"/> + <SubTexture name="diacritic-hookhorizontal0001" x="662" y="26" width="29" height="22" frameX="-0" frameY="-0" frameWidth="29" frameHeight="22"/> + <SubTexture name="diacritic-hookside0000" x="169" y="68" width="24" height="45" frameX="-0" frameY="-0" frameWidth="24" frameHeight="45"/> + <SubTexture name="diacritic-hookside0001" x="519" y="444" width="24" height="45" frameX="-0" frameY="-0" frameWidth="24" frameHeight="45"/> + <SubTexture name="diacritic-hookvertical0000" x="593" y="183" width="22" height="30" frameX="-0" frameY="-0" frameWidth="22" frameHeight="30"/> + <SubTexture name="diacritic-hookvertical0001" x="590" y="381" width="22" height="30" frameX="-0" frameY="-0" frameWidth="22" frameHeight="30"/> + <SubTexture name="diacritic-horn0000" x="661" y="490" width="19" height="22" frameX="-0" frameY="-0" frameWidth="19" frameHeight="22"/> + <SubTexture name="diacritic-horn0001" x="661" y="515" width="19" height="22" frameX="-0" frameY="-0" frameWidth="19" frameHeight="22"/> + <SubTexture name="diacritic-macron0000" x="97" y="604" width="39" height="12" frameX="-0" frameY="-0" frameWidth="39" frameHeight="12"/> + <SubTexture name="diacritic-macron0001" x="620" y="33" width="39" height="12" frameX="-0" frameY="-0" frameWidth="39" frameHeight="12"/> + <SubTexture name="diacritic-ogonek0000" x="661" y="388" width="19" height="23" frameX="-0" frameY="-0" frameWidth="19" frameHeight="23"/> + <SubTexture name="diacritic-ogonek0001" x="661" y="414" width="19" height="23" frameX="-0" frameY="-0" frameWidth="19" frameHeight="23"/> + <SubTexture name="diacritic-ring0000" x="661" y="540" width="21" height="20" frameX="-0" frameY="-0" frameWidth="21" frameHeight="20"/> + <SubTexture name="diacritic-ring0001" x="661" y="563" width="21" height="20" frameX="-0" frameY="-0" frameWidth="21" frameHeight="20"/> + <SubTexture name="diacritic-sicilicus0000" x="661" y="230" width="15" height="24" frameX="-0" frameY="-0" frameWidth="15" frameHeight="24"/> + <SubTexture name="diacritic-sicilicus0001" x="661" y="257" width="15" height="24" frameX="-0" frameY="-0" frameWidth="15" frameHeight="24"/> + <SubTexture name="diacritic-stroke0-long0000" x="344" y="577" width="47" height="11" frameX="-0" frameY="-0" frameWidth="47" frameHeight="11"/> + <SubTexture name="diacritic-stroke0-long0001" x="344" y="591" width="47" height="11" frameX="-0" frameY="-0" frameWidth="47" frameHeight="11"/> + <SubTexture name="diacritic-stroke0-short0000" x="139" y="604" width="32" height="11" frameX="-0" frameY="-0" frameWidth="32" frameHeight="11"/> + <SubTexture name="diacritic-stroke0-short0001" x="400" y="601" width="32" height="11" frameX="-0" frameY="-0" frameWidth="32" frameHeight="11"/> + <SubTexture name="diacritic-stroke300000" x="568" y="352" width="42" height="26" frameX="-0" frameY="-0" frameWidth="42" frameHeight="26"/> + <SubTexture name="diacritic-stroke300001" x="267" y="619" width="42" height="26" frameX="-0" frameY="-0" frameWidth="42" frameHeight="26"/> + <SubTexture name="diacritic-stroke45-long0000" x="1" y="553" width="45" height="50" frameX="-0" frameY="-0" frameWidth="45" frameHeight="50"/> + <SubTexture name="diacritic-stroke45-long0001" x="49" y="553" width="45" height="50" frameX="-0" frameY="-0" frameWidth="45" frameHeight="50"/> + <SubTexture name="diacritic-stroke45-short0000" x="619" y="524" width="31" height="34" frameX="-0" frameY="-0" frameWidth="31" frameHeight="34"/> + <SubTexture name="diacritic-stroke45-short0001" x="619" y="561" width="31" height="34" frameX="-0" frameY="-0" frameWidth="31" frameHeight="34"/> + <SubTexture name="diacritic-stroke600000" x="196" y="68" width="32" height="61" frameX="-0" frameY="-0" frameWidth="32" frameHeight="61"/> + <SubTexture name="diacritic-stroke600001" x="240" y="1" width="32" height="61" frameX="-0" frameY="-0" frameWidth="32" frameHeight="61"/> + <SubTexture name="diacritic-stroke90-long0000" x="152" y="190" width="11" height="59" frameX="-0" frameY="-0" frameWidth="11" frameHeight="59"/> + <SubTexture name="diacritic-stroke90-long0001" x="257" y="65" width="11" height="59" frameX="-0" frameY="-0" frameWidth="11" frameHeight="59"/> + <SubTexture name="diacritic-stroke90-short0000" x="162" y="316" width="12" height="39" frameX="-0" frameY="-0" frameWidth="12" frameHeight="39"/> + <SubTexture name="diacritic-stroke90-short0001" x="564" y="437" width="12" height="39" frameX="-0" frameY="-0" frameWidth="12" frameHeight="39"/> + <SubTexture name="diacritic-tilde0000" x="619" y="598" width="31" height="18" frameX="-0" frameY="-0" frameWidth="31" frameHeight="18"/> + <SubTexture name="diacritic-tilde0001" x="531" y="632" width="31" height="18" frameX="-0" frameY="-0" frameWidth="31" frameHeight="18"/> + <SubTexture name="diacritic-titlo0000" x="619" y="164" width="38" height="25" frameX="-0" frameY="-0" frameWidth="38" frameHeight="25"/> + <SubTexture name="diacritic-titlo0001" x="619" y="192" width="38" height="25" frameX="-0" frameY="-0" frameWidth="38" frameHeight="25"/> + <SubTexture name="diacritic-verticalline0000" x="681" y="51" width="10" height="17" frameX="-0" frameY="-0" frameWidth="10" frameHeight="17"/> + <SubTexture name="diacritic-verticalline0001" x="681" y="71" width="10" height="17" frameX="-0" frameY="-0" frameWidth="10" frameHeight="17"/> +</TextureAtlas> \ No newline at end of file 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/options/checkboxThingie.png b/assets/images/menus/options/checkboxThingie.png index ffdf3d30..4a302ba9 100644 Binary files a/assets/images/menus/options/checkboxThingie.png and b/assets/images/menus/options/checkboxThingie.png differ diff --git a/assets/images/menus/options/checkboxThingie.xml b/assets/images/menus/options/checkboxThingie.xml index 40f9ad6c..7837544e 100644 --- a/assets/images/menus/options/checkboxThingie.xml +++ b/assets/images/menus/options/checkboxThingie.xml @@ -1,19 +1,30 @@ -<?xml version="1.0" encoding="utf-8"?> -<TextureAtlas imagePath="checkboxThingie.png"> - <!-- Created with Adobe Animate version 21.0.4.39603 --> - <!-- http://www.adobe.com/products/animate.html --> - <SubTexture name="Check Box Selected Static0000" x="0" y="198" width="120" height="174" frameX="0" frameY="0" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box Selected Static0001" x="0" y="198" width="120" height="174" frameX="0" frameY="0" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0000" x="371" y="0" width="121" height="82" frameX="-15" frameY="-133" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0001" x="371" y="0" width="121" height="82" frameX="-15" frameY="-133" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0002" x="132" y="0" width="127" height="180" frameX="-4" frameY="-45" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0003" x="0" y="0" width="127" height="193" frameX="0" frameY="0" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0004" x="0" y="0" width="127" height="193" frameX="0" frameY="0" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0005" x="257" y="185" width="138" height="146" frameX="-2" frameY="-62" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0006" x="257" y="185" width="138" height="146" frameX="-2" frameY="-62" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0007" x="132" y="185" width="120" height="176" frameX="-12" frameY="-29" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0008" x="132" y="185" width="120" height="176" frameX="-12" frameY="-29" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0009" x="0" y="198" width="120" height="174" frameX="-12" frameY="-29" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box selecting animation0010" x="0" y="198" width="120" height="174" frameX="-12" frameY="-29" frameWidth="140" frameHeight="225"/> - <SubTexture name="Check Box unselected0000" x="264" y="0" width="102" height="103"/> -</TextureAtlas> +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="checkboxThingie.png" width="550" height="512"> + <SubTexture name="Check Box Selected Static0000" x="250" y="311" width="138" height="143" frameX="-0" frameY="-0" frameWidth="138" frameHeight="143"/> + <SubTexture name="Check Box deselect animation0000" x="250" y="177" width="159" height="131" frameX="-0" frameY="-22" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0001" x="250" y="177" width="159" height="131" frameX="-0" frameY="-22" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0002" x="123" y="1" width="127" height="173" frameX="-13" frameY="-0" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0003" x="123" y="1" width="127" height="173" frameX="-13" frameY="-0" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0004" x="1" y="350" width="142" height="136" frameX="-5" frameY="-33" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0005" x="1" y="350" width="142" height="136" frameX="-5" frameY="-33" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0006" x="391" y="311" width="134" height="94" frameX="-9" frameY="-68" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0007" x="1" y="203" width="111" height="106" frameX="-21" frameY="-55" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0008" x="1" y="203" width="111" height="106" frameX="-21" frameY="-55" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0009" x="391" y="408" width="102" height="103" frameX="-25" frameY="-58" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box deselect animation0010" x="391" y="408" width="102" height="103" frameX="-25" frameY="-58" frameWidth="159" frameHeight="173"/> + <SubTexture name="Check Box selecting animation0000" x="412" y="128" width="121" height="82" frameX="-25" frameY="-132" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0001" x="412" y="128" width="121" height="82" frameX="-25" frameY="-132" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0002" x="1" y="1" width="119" height="199" frameX="-16" frameY="-0" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0003" x="123" y="177" width="124" height="170" frameX="-13" frameY="-22" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0004" x="123" y="177" width="124" height="170" frameX="-13" frameY="-22" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0005" x="394" y="1" width="155" height="124" frameX="-0" frameY="-90" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0006" x="394" y="1" width="155" height="124" frameX="-0" frameY="-90" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0007" x="253" y="1" width="138" height="145" frameX="-12" frameY="-61" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0008" x="253" y="1" width="138" height="145" frameX="-12" frameY="-61" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0009" x="250" y="311" width="138" height="143" frameX="-12" frameY="-61" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box selecting animation0010" x="250" y="311" width="138" height="143" frameX="-12" frameY="-61" frameWidth="155" frameHeight="214"/> + <SubTexture name="Check Box unselected0000" x="391" y="408" width="102" height="103" frameX="-0" frameY="-0" frameWidth="159" frameHeight="173"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/images/menus/options/meter.png b/assets/images/menus/options/meter.png new file mode 100644 index 00000000..4607e9b0 Binary files /dev/null and b/assets/images/menus/options/meter.png differ diff --git a/assets/images/menus/options/meter.xml b/assets/images/menus/options/meter.xml new file mode 100644 index 00000000..fddf0e4e --- /dev/null +++ b/assets/images/menus/options/meter.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TextureAtlas imagePath="meter.png" width="102" height="175"> + <SubTexture name="meter empty corner0000.png" x="0" y="104" width="39" height="71"/> + <SubTexture name="meter empty piece0000.png" x="42" y="104" width="2" height="71"/> + <SubTexture name="meter filled corner0000.png" x="47" y="104" width="39" height="71"/> + <SubTexture name="meter filled piece0000.png" x="89" y="104" width="2" height="71"/> + <SubTexture name="meter slider0000.png" x="0" y="0" width="102" height="102"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/images/menus/options/radioCrank.png b/assets/images/menus/options/radioCrank.png new file mode 100644 index 00000000..5e5eb08b Binary files /dev/null and b/assets/images/menus/options/radioCrank.png differ diff --git a/assets/images/menus/options/radioCrank.xml b/assets/images/menus/options/radioCrank.xml new file mode 100644 index 00000000..b731645d --- /dev/null +++ b/assets/images/menus/options/radioCrank.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="radioCrank.png" width="1328" height="117"> + <SubTexture name="Radio Selected Static0000" x="514" y="1" width="102" height="102" frameX="-0" frameY="-0" frameWidth="102" frameHeight="102"/> + <SubTexture name="Radio deselect animation0000" x="954" y="1" width="86" height="108" frameX="-22" frameY="-0" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0001" x="954" y="1" width="86" height="108" frameX="-22" frameY="-0" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0002" x="619" y="1" width="114" height="102" frameX="-7" frameY="-25" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0003" x="619" y="1" width="114" height="102" frameX="-7" frameY="-25" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0004" x="141" y="1" width="138" height="65" frameX="-0" frameY="-56" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0005" x="141" y="1" width="138" height="65" frameX="-0" frameY="-56" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0006" x="400" y="1" width="111" height="101" frameX="-11" frameY="-21" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0007" x="736" y="1" width="110" height="102" frameX="-12" frameY="-10" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0008" x="736" y="1" width="110" height="102" frameX="-12" frameY="-10" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0009" x="849" y="1" width="102" height="102" frameX="-15" frameY="-10" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio deselect animation0010" x="849" y="1" width="102" height="102" frameX="-15" frameY="-10" frameWidth="138" frameHeight="127"/> + <SubTexture name="Radio selecting animation0000" x="1" y="1" width="137" height="64" frameX="-0" frameY="-55" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0001" x="1" y="1" width="137" height="64" frameX="-0" frameY="-55" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0002" x="1142" y="1" width="94" height="113" frameX="-21" frameY="-0" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0003" x="1239" y="1" width="88" height="115" frameX="-24" frameY="-4" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0004" x="1239" y="1" width="88" height="115" frameX="-24" frameY="-4" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0005" x="282" y="1" width="115" height="74" frameX="-11" frameY="-58" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0006" x="282" y="1" width="115" height="74" frameX="-11" frameY="-58" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0007" x="1043" y="1" width="96" height="108" frameX="-20" frameY="-22" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0008" x="1043" y="1" width="96" height="108" frameX="-20" frameY="-22" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0009" x="514" y="1" width="102" height="102" frameX="-17" frameY="-25" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio selecting animation0010" x="514" y="1" width="102" height="102" frameX="-17" frameY="-25" frameWidth="137" frameHeight="132"/> + <SubTexture name="Radio unselected0000" x="849" y="1" width="102" height="102" frameX="-0" frameY="-0" frameWidth="138" frameHeight="127"/> +</TextureAtlas> \ No newline at end of file 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/README.md b/assets/languages/README.md new file mode 100644 index 00000000..f3e8bd3f --- /dev/null +++ b/assets/languages/README.md @@ -0,0 +1,20 @@ +# Translations + +## Adding a new language + +1. Create a new folder in this folder with the language's code. Using the ISO 3166-1 codes for the language. (ex: `en` for English, `es` for Spanish, etc) (For full list of codes, see [here](https://en.wikipedia.org/wiki/ISO_3166-1#Codes)) + +2. Create a `config.ini` file in the folder with the following contents: + +```ini +name="Your Language Name in translated language" +credits="Your Name (your github username)" +version="version based on the English config.ini version" +``` + +3. Copy the xml files from the `en` (English) folder and paste them in the new folder. + +4. Translate the texts in the copied xml files. The {0} and {1}, and so on are placeholders are used for variables, and should be left as is. + +5. Make a pull request with your changes. +Make sure the pull request is labeled as `[LANG] Add <language name>` or `[LANG] Update <language name>` or `[LANG] Fix typo in <language name>`, generally anything is allowed but it has to contain the language name. diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml new file mode 100644 index 00000000..86ca86ac --- /dev/null +++ b/assets/languages/en/Editors.xml @@ -0,0 +1,653 @@ +<?xml version="1.0" encoding="utf-8" ?> +<language> + <group name="General Editor Translations" prefix="editor."> + <str id="autosavingIn">Autosaving in {0} seconds{1}</str> + <str id="autosavingCanceledSuffix">(Canceled)!</str> + + <str id="ok">Ok</str> + <str id="cancel">Cancel</str> + <str id="save">Save</str> + <str id="saveClose">Save & Close</str> + <str id="close">Close</str> + </group> + + <group name="UIImageExplorer" prefix="uiImageExplorer."> + <str id="symbol">Symbol</str> + <str id="animation">Animation</str> + <str id="atlas">Atlas</str> + <str id="spritemap">Spritemap</str> + + <str id="directory">Directory ({0}/)</str> + <str id="found">{0} {1} Found</str> + <str id="foundplural">{0} {1} Found</str> + <str id="notFound">#NO {0}s Found#</str> + <str id="foundAtlas">{0} Spritemap Found</str> + <str id="foundAtlasplural">{0} Spritemaps Found</str> + + <str id="fileAlreadyExists">The following files alreadly exist: \n\n{0}\n\nIMPORTANT: OVERRIDING CAN NOT BE UNDONE!</str> + <str id="override">Override Files</str> + <str id="useExisting">Use Existing</str> + </group> + + <group name="EditorPicker" prefix="editor."> + <str id="alphabet.name">Alphabet Editor</str> + <str id="chart.name">Chart Editor</str> + <str id="character.name">Character Editor</str> + <str id="stage.name">Stage Editor</str> + <str id="uiDebug.name">UI Debug State</str> + <str id="wiki.name">Wiki</str> + <str id="debugOptions.name">Debug Options</str> + </group> + + <group name="ChartEditor"> + <group name="CharterSelection" prefix="charterSelection."> + <str id="desc">Select a song to modify the charts from.</str> + <str id="acceptSong">Press ACCEPT to choose a difficulty to edit.</str> + <str id="acceptDifficulty">Press ACCEPT to edit the chart for the selected difficulty.</str> + <str id="selectDifficulty">Select a difficulty to continue.</str> + <str id="newDifficulty">New Difficulty</str> + <str id="newSong">New Song</str> + </group> + + <group name="ChartCreationScreen" prefix="chartCreation."> + <str id="title">Creating New Chart</str> + <str id="info">Chart Info</str> + <str id="diffName">Difficulty Name</str> + <str id="stage">Stage</str> + <str id="scrollSpeed">Scroll Speed</str> + + <str id="strumLineList">Strumlines $* Atleast 1 Strumline Required$</str> + <str id="strumLine.format">Strumline - #{0}</str> + + <group name="Warnings" prefix="warnings."> + <str id="song-exists-title">Creating Song: Error!</str> + <str id="song-exists-body">The song you are trying to create already exists, if you would like to override it delete the song first!</str> + <str id="chart-exists-title">Creating Chart: Error!</str> + <str id="chart-exists-body">The chart you are trying to create already exists, if you would like to override it delete the chart first!</str> + </group> + </group> + + <group name="ChartDataScreen" prefix="chartDataScreen."> + <str id="title">Chart Properties</str> + <str id="editChartData">Edit Chart Data</str> + <str id="stage">Stage</str> + <str id="scrollSpeed">Scroll Speed</str> + </group> + + <group name="SongPosInfo" prefix="songPosInfo."> + <str id="step">Step: {0}</str> + <str id="beat">Beat: {0}</str> + <str id="measure">Measure: {0}</str> + <str id="bpm">BPM: {0}</str> + <str id="timeSignature">Time Signature: {0}/{1}</str> + </group> + + <group name="CharterEventScreen" prefix="charterEventScreen."> + <str id="title-creating">Create Event Group</str> + <str id="title-editing">Edit Event Group</str> + + <str id="noEvent">No Event</str> + + <str id="strumLine.format">Strumline #{0} ({1})</str> + </group> + + <group name="CharterEventTypeSelection" prefix="charterEventTypeSelection."> + <str id="title">Choose an event type...</str> + </group> + + <group name="CharterMetaDataScreen" prefix="charterMetaDataScreen."> + <str id="title">Edit Metadata</str> + + <str id="songData">Song Data</str> + <str id="songName">Song Name</str> + <str id="bpm">BPM</str> + <str id="timeSignature">Time Signature</str> + <str id="menusData">Menus Data (Freeplay/Story)</str> + <str id="displayName">Display Name</str> + <str id="icon">Icon</str> + <str id="modesAllowed">Modes Allowed</str> + <str id="color">Color</str> + <str id="difficulties">Difficulties</str> + <str id="customValues">Custom Values (Advanced)</str> + + <str id="opponentMode">Opponent Mode</str> + <str id="coopAllowed">Co-op Mode</str> + </group> + + <group name="CharterNoteTypesList" prefix="charterNoteTypesList."> + <str id="title">Note Types List Editor</str> + + <str id="editNoteTypes">Edit Note Types</str> + <str id="editType">Note Type {0}</str> + </group> + + <group name="CharterStrumlineScreen" prefix="charterStrumlineScreen."> + <str id="win-title-creating">Creating strumline {0}</str> + <str id="win-title-editing">Editing strumline {0} properties</str> + + <str id="title-creating">Create New Strumline</str> + <str id="title-editing">Edit Strumline Properties</str> + </group> + + <group name="SongCreationScreen" prefix="songCreationScreen."> + <str id="win-title">Creating New Song</str> + <str id="win-title-importing">Importing Song</str> + <str id="title">Song Info</str> + + <str id="instAudio">Inst Audio File $* Required*$</str> + <str id="voicesAudio">Vocal Audio File</str> + + <str id="importFrom">Import from...</str> + <str id="importSource">Import Source:</str> + <str id="legacyOrPsych">Psych/Legacy FNF</str> + <str id="vslice">V-Slice</str> + <str id="vsliceProject">V-Slice Project (.fnfc)</str> + <str id="importChartFormat">Chart Format</str> + <str id="importAudios">Add Audios</str> + <str id="songFileName">Song file name $* Required$</str> + <str id="songDataFile">Data/Chart File $* Required$</str> + <str id="songMetaFile">Meta File $* Required$</str> + + <str id="back">Back</str> + <str id="next">Next</str> + </group> + + <group name="StrumlineOptions" prefix="charterStrumLine."> + <str id="characters">Characters</str> + <str id="newChar">New Char</str> + <str id="type">Type</str> + <str id="scrollSpeed">Scroll Speed</str> + <str id="stagePos">Stage Position</str> + <str id="scale">Scale</str> + <str id="spacing">Spacing</str> + <str id="hudPos">Hud Position (X [Ratio 0-1],Y)</str> + <str id="visible">Visible?</str> + <str id="useChartScrollSpeed">Uses charts scroll speed?</str> + <str id="vocalSuffix">Vocal Suffix</str> + <str id="keyCount">Key Count</str> + </group> + + <group name="ChartEditor" prefix="charter."> + <!-- Charter.hx --> + <group name="TopBar"> + <str id="topBar.file">File</str> + <group name="FileMenu" prefix="file."> + <str id="new">New</str> + <str id="save">Save</str> + <str id="saveAs">Save As...</str> + <str id="saveGlobalEvents">Save Global Events</str> + <str id="saveGlobalEventsAs">Save Global Events As...</str> + <str id="saveNoLocalEvents">Save Chart without Local Events</str> + <str id="saveNoLocalEventsAs">Save Chart without Local Events As...</str> + <str id="saveMeta">Save Meta</str> + <str id="saveMetaAs">Save Meta As...</str> + <str id="exportFnfLegacy">Export For FNF Legacy...</str> + <str id="exportPsych">Export For Psych Engine...</str> + <str id="exit">Exit</str> + </group> + + <str id="topBar.edit">Edit</str> + <group name="EditMenu" prefix="edit."> + <str id="undo">Undo</str> + <str id="redo">Redo</str> + <str id="copy">Copy</str> + <str id="paste">Paste</str> + <str id="cut">Cut</str> + <str id="delete">Delete</str> + </group> + + <str id="topBar.chart">Chart</str> + <group name="ChartMenu" prefix="chart."> + <str id="playtest">Playtest</str> + <str id="playtestHere">Playtest here</str> + <str id="playtestOpponent">Playtest as opponent</str> + <str id="playtestOpponentHere">Playtest as opponent here</str> + <str id="enableScripts">Enable scripts during playtesting</str> + <str id="editChartData">Edit chart data</str> + <str id="editMetadata">Edit metadata information</str> + </group> + + <str id="topBar.view">View</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Zoom in</str> + <str id="zoomOut">Zoom out</str> + <str id="resetZoom">Reset Zoom</str> + <str id="showSectionsSeparator">Show Sections Separator</str> + <str id="showBeatsSeparator">Show Beats Separator</str> + <str id="rainbowWaveforms">Rainbow Waveforms</str> + <str id="lowDetailWaveforms">Low Detail Waveforms</str> + <str id="scrollLeft">Scroll Left</str> + <str id="scrollRight">Scroll Right</str> + <str id="scrollReset">Reset Scroll</str> + </group> + + <str id="topBar.song">Song</str> + <group name="SongMenu" prefix="song."> + <str id="goStart">Go back to the start</str> + <str id="goEnd">Go forward to the end</str> <!-- used to be "Go to the end" --> + <str id="muteInst">Mute instrumental</str> + <str id="muteVoices">Mute voices</str> + </group> + + <group name="BookmarkMenu" prefix="bookmarks."> + <str id="addBookmarkHere">Add Bookmark Here</str> + <str id="editBookmarkList">Edit Bookmark List</str> + <str id="goto">Go to "{0}"</str> + <str id="createBookmarkTitle">Creating New Bookmark...</str> + <str id="createBookmarkName">Bookmark Name:</str> + <str id="createBookmarkColor">Bookmark Color:</str> + <str id="editBookmarkListTitle">Bookmark List</str> + <str id="editBookmarksTitle">Edit Bookmarks</str> + <str id="newBookmarkName">New Bookmark</str> + </group> + + <str id="topBar.note">Note</str> + <group name="NoteMenu" prefix="note."> + <str id="addSustainLength">Add sustain length</str> + <str id="subtractSustainLength">Subtract sustain length</str> + <str id="selectAll">Select all</str> + <str id="selectMeasure">Select measure</str> + <str id="editNoteTypesList">Edit Note Types List</str> + </group> + <str id="topBar.snap">Snap</str> + <group name="SnapMenu" prefix="snap."> + <str id="increaseSnap">↑ Grid Snap</str> + <str id="resetSnap">Reset Grid Snap</str> + <str id="decreaseSnap">↓ Grid Snap</str> + + <str id="snap">{0}x Grid Snap</str> + </group> + + <str id="topBar.playback">Playback</str> + <group name="PlaybackMenu" prefix="playback."> + <str id="play">Play/Pause</str> + <str id="speedRaise">↑ Speed {0}%</str> + <str id="speedReset">Reset Speed</str> + <str id="speedLower">↓ Speed {0}%</str> + <str id="sectionBack">Go back a section</str> + <str id="sectionForward">Go forward a section</str> + <str id="sectionStart">Go to start of section</str> + <str id="backStep">Go back a step</str> + <str id="forwardStep">Go forward a step</str> + <str id="metronome">Metronome</str> + <str id="visualMetronome">Visual metronome</str> + </group> + </group> + + <group name="NoteTypes" prefix="noteTypes."> + <str id="default">Default Note</str> + </group> + + <group name="EventTypes" prefix="events."> + <!-- You can add your custom event types here! --> + <!-- Just need to do some conversion from the raw name to the id --> + <!-- Example: HScript Call -> hscriptCall --> + <!-- Example: Change Character -> changeCharacter --> + <str id="hscriptCall">HScript Call</str> + <str id="cameraMovement">Camera Movement</str> + <str id="addCameraZoom">Add Camera Zoom</str> + <str id="cameraModuloChange">Camera Modulo Change</str> + <str id="cameraFlash">Camera Flash</str> + <str id="bpmChange">BPM Change</str> + <str id="scrollSpeedChange">Scroll Speed Change</str> + <str id="altAnimationToggle">Alt Animation Toggle</str> + <str id="playAnimation">Play Animation</str> + </group> + + <group name="StrumlineOptions" prefix="strumLine."> + <str id="button-name">Options ↓</str> + + <str id="hitsounds">Hitsounds</str> + <str id="muteVocals">Mute Vocals</str> + <str id="edit">Edit</str> + <str id="delete">Delete</str> + + <str id="noWaveform">No Waveform</str> + </group> + + <str id="createNew">Create New</str> + <str id="lock-unlock">Lock/Unlock</str> + + <str id="info.localEvents">Local Events\n(Only This Difficulty)</str> + <str id="info.globalEvents">Global Events\n(All Difficulties)</str> + <str id="eventType-local">Local Event</str> + <str id="eventType-global">Global Event</str> + + <!-- CharterEventAdd.hx --> + <str id="addEvent">Add Event</str> + <str id="editEvent">Edit</str> <!-- Should maybe be "Edit Event" --> + <str id="deleteEvent">Delete Event</str> + + <group name="Notifications" prefix="popup."> + <str id="savedChartAt">Saved chart at {0}!</str> + </group> + </group> + </group> + + <group name="CharacterEditor"> + <group name="CharacterSelection" prefix="characterSelection."> + <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> + </group> + + <group name="CharacterInfoScreen" prefix="characterInfoScreen."> + <str id="win-title">Editing Character</str> + <str id="title">Edit Character Info</str> + <str id="icon">Icon</str> + <str id="iconColor">Icon Color</str> + <str id="singDuration">Sing Duration</str> + <str id="useSingDuration">Use Sing Duration?</str> + <str id="customValues">Custom Values (Advanced)</str> + </group> + + <group name="CharacterEditor" prefix="characterEditor."> + <group name="TopBar"> + <str id="topBar.file">File</str> + <group name="FileMenu" prefix="file."> + <str id="new">New</str> + <str id="save">Save</str> + <str id="saveAs">Save As...</str> + <str id="exit">Exit</str> + </group> + + <str id="topBar.edit">Edit</str> + <group name="EditMenu" prefix="edit."> + <str id="copyOffset">Copy Offset</str> + <str id="pasteOffset">Paste Offset</str> + <str id="undo">Undo</str> + <str id="redo">Redo</str> + <str id="editInfo">Edit information</str> + <str id="editSprite">Edit sprite</str> + </group> + + <str id="topBar.offset">Offsets</str> + <group name="OffsetsMenu" prefix="offset."> + <str id="moveLeft">Move Left</str> + <str id="moveUp">Move Up</str> + <str id="moveDown">Move Down</str> + <str id="moveRight">Move Right</str> + <str id="moveExtraLeft">Move Extra Left</str> + <str id="moveExtraUp">Move Extra Up</str> + <str id="moveExtraDown">Move Extra Down</str> + <str id="moveExtraRight">Move Extra Right</str> + <str id="dragOffsetsMouse">Drag Offsets With Mouse?</str> + <str id="clearOffsets">Clear Offsets</str> + </group> + + <str id="topBar.stage">Stage</str> + <group name="StageMenu" prefix="stage."> + <str id="useStage">Use "{0}"</str> + <str id="useNone">Use No Stage</str> + </group> + + <str id="topBar.view">View</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Zoom in</str> + <str id="zoomOut">Zoom out</str> + <str id="resetZoom">Reset Zoom</str> + <str id="charHitbox">Character Hitbox?</str> + <str id="charCam">Character Camera?</str> + <str id="axis">XY Axis?</str> + </group> + + <str id="topBar.animation">Animation</str> + <group name="AnimationMenu" prefix="animation."> + <str id="playAnim">Play Animation</str> + <str id="stopAnim">Stop Animation</str> + <str id="changeAnimUp">Change Animation ↑</str> + <str id="changeAnimDown">Change Animation ↓</str> + <str id="playAnimOnOffset">Play Animation On Offset?</str> + </group> + + <group name="CharacterPropertiesWindow" prefix="characterProperties."> + <str id="position">Position (X, Y)</str> + <str id="scale">Scale</str> + <str id="editInfo">Edit Info</str> + <str id="editSprite">Edit Sprite</str> + <str id="camPosition">Camera Position (X, Y)</str> + <str id="charFlipped">Flipped?</str> + <str id="antialiased">Antialiased?</str> + <str id="testCharAs">Test Character As...</str> + <str id="charDesign">Char Designed As...</str> + </group> + + <group name="CharacterAnimButton" prefix="characterAnim."> + <str id="defaultAnimName">New Anim</str> + <str id="name">Name</str> + <str id="anim">Animation</str> + <str id="position">Position (X, Y)</str> + <str id="fps">FPS</str> + <str id="indices">Indices (Frames)</str> + </group> + + <group name="CharacterSpriteScreen" prefix="characterSpriteScreen."> + <str id="win-title">Edit character sprite</str> + <str id="charImageFile">Character Image File $* Required$</str> + </group> + </group> + </group> + + <group name="CharacterCreationScreen" prefix="characterCreationScreen."> + <str id="win-title">Creating New Character</str> + <str id="charName">Character Name $* Required$</str> + <str id="charImgName">Character Image File $* Required$</str> + </group> + </group> + + <group name="AlphabetEditor" prefix="editor.alphabet."> + <group name="AlphabetSelection" prefix="selection."> + <str id="acceptTypeface">Press ACCEPT to edit this typeface.</str> + <str id="newTypeface">New Typeface</str> + <str id="desc">Select a typeface to edit</str> + + <group name="Warnings" prefix="warnings."> + <str id="notImplemented-title">New Typeface: Feature Not Implemented!</str> + <str id="notImplemented-body">This feature isn't implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Crew</str> + </group> + </group> + + <group name="AlphabetEditor"> + <group name="TopBar"> + <str id="topBar.file">File</str> + <group name="FileMenu" prefix="file."> + <str id="new">New</str> + <str id="save">Save</str> + <str id="saveAs">Save As...</str> + <str id="exit">Exit</str> + </group> + + <str id="topBar.edit">Edit</str> + <group name="EditMenu" prefix="edit."> + <str id="undo">Undo</str> + <str id="redo">Redo</str> + </group> + + <str id="topBar.glyph">Glyph</str> + <group name="GlyphMenu" prefix="glyph."> + <str id="newGlyph">New Glyph</str> + <str id="editGlyph">Edit Glyph</str> + <str id="deleteGlyph">Delete Glyph</str> + </group> + + <str id="topBar.offset">Offsets</str> + <group name="OffsetsMenu" prefix="offset."> + <str id="moveLeft">Move Left</str> + <str id="moveUp">Move Up</str> + <str id="moveDown">Move Down</str> + <str id="moveRight">Move Right</str> + <str id="moveExtraLeft">Move Extra Left</str> + <str id="moveExtraUp">Move Extra Up</str> + <str id="moveExtraDown">Move Extra Down</str> + <str id="moveExtraRight">Move Extra Right</str> + <str id="clearOffsets">Clear Offsets</str> + </group> + + <str id="topBar.view">View</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Zoom in</str> + <str id="zoomOut">Zoom out</str> + <str id="resetZoom">Reset Zoom</str> + </group> + </group> + </group> + </group> + + <group name="StageEditor"> + <group name="StageSelection" prefix="stageSelection."> + <str id="desc">Select a stage to edit. HSCRIPT only stages are not supported.</str> + <str id="acceptStage">Press ACCEPT to edit this stage.</str> + <str id="newStage">New Stage</str> + <str id="acceptNewStage">Press ACCEPT to create a new stage.</str> + </group> + + <group name="StageEditor" prefix="stageEditor."> + <group name="TopBar"> + <str id="topBar.file">File</str> + <group name="FileMenu" prefix="file."> + <str id="save">Save</str> + <str id="saveAs">Save As...</str> + <str id="exit">Exit</str> + </group> + + <str id="topBar.edit">Edit</str> + <group name="EditMenu" prefix="edit."> + <str id="undo">Undo</str> + <str id="redo">Redo</str> + <str id="editStageInfo">Edit Stage Info</str> + </group> + + <str id="topBar.select">Selection</str> + <group name="SelectionMenu" prefix="select."> + <str id="all">Select All</str> + <str id="deselect">Deselect</str> + <str id="inverse">Inverse Selection</str> + </group> + + <str id="topBar.view">View</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Zoom in</str> + <str id="zoomOut">Zoom out</str> + <str id="zoomReset">Reset Zoom</str> + <str id="focusDad">Focus Dad</str> + <str id="focusGF">Focus GF</str> + <str id="focusBF">Focus BF</str> + </group> + + <str id="topBar.editor">Editor</str> + <group name="EditorMenu" prefix="editor."> + <str id="showCharacters">Show Characters</str> + </group> + + <group name="StageSpriteScreen" prefix="stageSpriteScreen."> + <str id="win-title">Edit stage sprite</str> + <str id="stageImageFile">Stage Image File $* Required$</str> + </group> + </group> + </group> + + <group name="StageCreationScreen" prefix="stageCreationScreen."> + <str id="win-title">Creating New Stage</str> + <str id="title">Stage Info</str> + <str id="stageName">Stage Name</str> + <str id="stagePath">Stage Path</str> + </group> + + <group name="StageInfoScreen" prefix="stageInfoScreen"> + <str id="win-title">Editing Stage "{0}" Infos</str> + <str id="title">Stage Info</str> + <str id="stageName">Stage Name</str> + <str id="spritePath">Sprite Path\n(Needs Refresh)</str> + <str id="startCamPos">Start Camera Position (X, Y)</str> + <str id="zoom">Zoom</str> + </group> + + <group name="StageElementEditScreen" prefix="stageElementEditScreen."> + <str id="win-character-title">Editing Character "{0}"</str> + <str id="win-sprite-title">Editing Sprite "{0}"</str> + <str id="win-title">Editing XML <{0} /></str> + + <str id="character-title">Character Data</str> + <str id="sprite-title">Sprite Data</str> + <str id="title">XML Data</str> + + <str id="elementName">Element Name</str> + <str id="nameID">Name Identifier</str> + <str id="imageFile">Image File (WIP: needs reload)\nIn (${stage.spritesParentFolder})</str> + <str id="scroll">Scroll {0}</str> + <str id="scale">Scale {0}</str> + <str id="camera">Camera {0}</str> + <str id="spacing">Spacing (X, Y)</str> + <str id="skew">Skew {0}</str> + <str id="alpha">Alpha</str> + <str id="angle">Angle</str> + <str id="zoomFactor">Zoom Factor</str> + <str id="positionName">Position Name</str> + <str id="flipX">Flip X</str> + <str id="antialiasing">Antialiasing</str> + <str id="highMemory">High Memory</str> + <str id="lowMemory">Low Memory</str> + <str id="attributes">Attributes</str> + <str id="transformPreview">Transform Preview:</str> + + <str id="animType">Animation Type</str> + <group name="AnimTypeDropdown" prefix="animType."> + <str id="none">NONE</str> + <str id="beat">BEAT</str> + <str id="loop">LOOP</str> + </group> + <str id="tipCustomProperties">Tip: To access custom properties,\nHold Shift when clicking edit</str> + </group> + + <group name="StageXMLEditScreen" prefix="stageXMLEditScreen."> + <str id="win-title">Editing {0} XML</str> + <str id="title">Xml Data</str> + <str id="attributes">Attributes</str> + </group> + + <str id="stageCharacters.requiredPosition">This character position is required in stages!</str> + <str id="stageCharacters.noDeleteGirlfriend">You can't delete Girlfriend</str> + + <group name="StageElement" prefix="stageElement."> + <str id="scale">Scale: {0}, {1}</str> + <str id="scroll">Scroll: {0}, {1}</str> + </group> + + <group name="StageSprites" prefix="stageSprites."> + <str id="win-title">Stage Sprites</str> + <str id="sprite">Sprite</str> + <str id="box">Box</str> + <str id="character">Character</str> + <str id="warnings.not-implemented">Creating a box isnt implemented yet!</str> + </group> + </group> + + <!-- PlayTesting and SaveWarning are almost the same but with some small differences --> + <!-- PlayTesting allows color in the description by doing *hello* --> + <!-- SaveWarning doesn't allow color in the description --> + <group name="PlayTesting" prefix="playtesting."> + <str id="chartPlaytesting" notrim="true"> (Chart Playtesting)</str> + <str id="exitGame">Exit Game</str> + <str id="exitToMenu">Exit To Menu</str> + <str id="saveAndExitGame">Save & Exit Game</str> + <str id="saveAndExitToMenu">Save & Exit To Menu</str> + <str id="cancel">Cancel</str> + + <str id="warningTitle">UNSAVED CHANGES!</str> + <str id="warningDesc">Your changes will be *lost* if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?</str> + </group> + + <group name="SaveWarning" prefix="saveWarning."> + <str id="warningTitle">Unsaved Changes!</str> + <str id="warningDesc">Your changes will be lost if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?</str> + + <str id="exitGame">Exit Game</str> + <str id="exitToMenu">Exit To Menu</str> + <str id="saveAndExitGame">Save & Exit Game</str> + <str id="saveAndExitToMenu">Save & Exit To Menu</str> + <str id="cancel">Cancel</str> + </group> +</language> \ No newline at end of file diff --git a/assets/languages/en/Main.xml b/assets/languages/en/Main.xml new file mode 100644 index 00000000..88dc3830 --- /dev/null +++ b/assets/languages/en/Main.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8" ?> +<language> + <group name="PlayState"> + <str id="game.score">Score:{0}</str> + <str id="game.misses">Misses:{0}</str> + <str id="game.comboBreaks">Combo Breaks:{0}</str> + <str id="game.accuracy">Accuracy:{0} - {1}</str> + <str id="game.combo">Combo:{0}</str> + + <str id="game.rating.[N/A]">[N/A]</str> + <str id="game.rating.F">F</str> + <str id="game.rating.E">E</str> + <str id="game.rating.D">D</str> + <str id="game.rating.C">C</str> + <str id="game.rating.B">B</str> + <str id="game.rating.A">A</str> + <str id="game.rating.S">S</str> + <str id="game.rating.S++">S++</str> + </group> + + <group name="StoryMenu"> + <str id="story.score">WEEK SCORE:{0}</str> + <str id="story.tracks">TRACKS</str> + </group> + + <group name="Freeplay"> + <str id="freeplay.score">PERSONAL BEST:{0}</str> + <str id="freeplay.solo">Solo</str> + <str id="freeplay.opponentMode">Opponent Mode</str> + <str id="freeplay.coopMode">Co-Op Mode</str> + <str id="freeplay.coopModeSwitched">Co-Op Mode (Switched)</str> + </group> + + <group name="Pause"> + <group name="PauseOptions"> + <str id="pause.resume">Resume</str> + <str id="pause.restartSong">Restart Song</str> + <str id="pause.changeControls">Change Controls</str> + <str id="pause.changeOptions">Change Options</str> + <str id="pause.exitToMenu">Exit to menu</str> + <str id="pause.exitToCharter">Exit to charter</str> + <str id="pause.resumeCutscene">Resume Cutscene</str> + <str id="pause.skipCutscene">Skip Cutscene</str> + <str id="pause.restartCutscene">Restart Cutscene</str> + </group> + + <str id="pause.deathCounter">Blue balled: {0}</str> + <str id="pause.coopMode">CO-OP MODE</str> + <str id="pause.opponentMode">OPPONENT MODE</str> + </group> + + <group name="Credits"> + <group name="CreditsCodename"> + <str id="credits.mainDev" notrim="true"> *- Public member of the main Devs!*</str> + <str id="credits.totalContributions">Total Contributions: ~{0}~ / *{1}* (~{2}%~) - Select to open GitHub account</str> + <str id="credits.downloadingList">Downloading List...</str> + <str id="credits.allContributors">All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes).</str> + </group> + <group name="CreditsMain"> + <str id="credits.name">Credits</str> + <str id="credits.madePossible">The people who made this possible!</str> + <str id="credits.selectCodename">Select this to see all the contributors of the engine!</str> + <str id="credits.selectBase">Select this to open the itch.io page of the original game to donate!</str> + </group> + </group> + + <group name="Mods"> + <str id="mods.disableMods">Disable Mods</str> + </group> + + <group name="MainMenu"> + <str id="mainMenu.commit">Commit: {0} ({1})</str> + <str id="mainMenu.openMods">[{0}] Open Mods menu</str> + </group> + + <group name="BetaWarning"> + <str id="betaWarning.title">WARNING</str> + <str id="betaWarning.desc">This engine is still in a *{0}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.</str> + <str id="betaWarning.proceed">Press {0} to continue</str> + </group> + + <group name="Updater"> + <group name="UpdateAvailable"> + <str id="updateAvailable.title">NEW UPDATE</str> + <str id="updateAvailable.install">INSTALL</str> + <str id="updateAvailable.skip">SKIP</str> + </group> + <group name="UpdateScreen"> + <str id="updateScreen.general-1">Preparing update installation...</str> + <str id="updateScreen.part-1">Creating installation folder and cleaning old update files...</str> + + <str id="updateScreen.general-2">Downloading update assets...</str> + <str id="updateScreen.part-2">Downloading file {0}</str> + + <str id="updateScreen.general-3">Downloading new engine executable...</str> + <str id="updateScreen.part-3">Downloading {0}</str> + + <str id="updateScreen.general-4">Installing new files...</str> + <str id="updateScreen.part-4">Installing {0}</str> + </group> + </group> + + <group name="Difficulties"> + <str id="diff.easy">Easy</str> + <str id="diff.normal">Normal</str> + <str id="diff.hard">Hard</str> + <str id="diff.erect">Erect</str> + <str id="diff.nightmare">Nightmare</str> + </group> +</language> \ No newline at end of file diff --git a/assets/languages/en/Options.xml b/assets/languages/en/Options.xml new file mode 100644 index 00000000..f0ec9ea7 --- /dev/null +++ b/assets/languages/en/Options.xml @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="utf-8" ?> +<language> + <str id="optionsMenu.desc-missing">No Description</str> + + <group name="OptionsMenu"> + <str id="optionsMenu.advanced">Advanced</str> + <str id="optionsMenu.header.title">Options Menu</str> + <str id="optionsMenu.header.desc">Select a category to continue.</str> + + <str id="optionsTree.controls-name">Controls</str> + <str id="optionsTree.controls-desc">Change Controls for Player 1 and Player 2!</str> + + <str id="optionsTree.gameplay-name">Gameplay</str> + <str id="optionsTree.gameplay-desc">Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...</str> + <str id="optionsTree.gameplay.advanced-desc">Change more advanced options such as Window Size, Streaming Audio...</str> + + <str id="optionsTree.appearance-name">Appearance</str> + <str id="optionsTree.appearance-desc">Change Appearance options such as Flashing menus...</str> + + <str id="optionsTree.language-name">Language</str> + <str id="optionsTree.language-desc">Change the language that the engine uses.</str> + + <str id="optionsTree.miscellaneous-name">Miscellaneous</str> + <str id="optionsTree.miscellaneous-desc">Use this menu to reset save data or engine settings.</str> + + <!-- cant have debug here --> + </group> + + <group name="KeybindOptions" prefix="KeybindsOptions."> + <str id="left">Left</str> + <str id="down">Down</str> + <str id="up">Up</str> + <str id="right">Right</str> + + <str id="category.notes">Notes</str> + + <str id="category.ui">UI</str> + <group name="ui" prefix="ui."> + <str id="accept">Accept</str> + <str id="back">Back</str> + <str id="reset">Reset</str> + <str id="pause">Pause</str> + </group> + + <str id="category.engine">Engine</str> + <group name="engine" prefix="engine."> + <str id="switchMod">Switch Mod</str> + </group> + + <str id="category.volume">Volume</str> + <str id="volume.mute">mute</str> + + <str id="category.developer">Developer</str> + <group name="developer" prefix="developer."> + <str id="devMenus">Developer Menus</str> + <str id="openConsole">Open Console</str> + <str id="reloadState">Reload State</str> + </group> + </group> + + <group name="AppearanceOptions" prefix="AppearanceOptions."> + <str id="framerate-name">Framerate</str> + <str id="framerate-desc">Pretty self explanatory, isn't it?</str> + + <str id="antialiasing-name">Antialiasing</str> + <str id="antialiasing-desc">If unchecked, will disable antialiasing on every sprite. Can boost performances at the cost of sharper, more pixely sprites.</str> + + <str id="colorHealthBar-name">Colored Healthbar</str> + <str id="colorHealthBar-desc">If unchecked, the game will use the original red/green health bar from the week 6 fnf game.</str> + + <str id="week6PixelPerfect-name">Pixel Perfect Effect</str> + <str id="week6PixelPerfect-desc">If checked, Week 6 will have a pixel perfect effect to it enabled, aligning every pixel on the screen.</str> + + <str id="gameplayShaders-name">Gameplay Shaders</str> + <str id="gameplayShaders-desc">If unchecked, gameplay shaders (visual effects like Thorns's Chromatic Aberration) wont be loaded; this may be helpful on weak devices.</str> + + <str id="flashingMenu-name">Flashing Menu</str> + <str id="flashingMenu-desc">If unchecked, will disable menu flashing when you select an option in the Main Menu, and other flashes will be slower.</str> + + <str id="lowMemoryMode-name">Low Memory Mode</str> + <str id="lowMemoryMode-desc">If checked, will disable certain background elements in stages to reduce memory usage.</str> + + <str id="gpuOnlyBitmaps-name">VRAM-Only Sprites</str> + <str id="gpuOnlyBitmaps-desc">If checked, will only store the bitmaps in the GPU, freeing a LOT of memory (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on bigger sprites. If you aren't sure, leave this on.</str> + </group> + + <group name="GameplayOptions" prefix="GameplayOptions."> + <str id="downscroll-name">Downscroll</str> + <str id="downscroll-desc">If checked, notes will go from up to down instead of down to up, as if they're falling.</str> + + <str id="ghostTapping-name">Ghost Tapping</str> + <str id="ghostTapping-desc">If unchecked, trying to hit any strum that have no note that can be hit will cause a miss.</str> + + <str id="naughtyness-name">Naughtyness</str> + <str id="naughtyness-desc">If unchecked, will censor the Week 7 cutscenes.</str> + + <str id="camZoomOnBeat-name">Camera Zoom on Beat</str> + <str id="camZoomOnBeat-desc">If unchecked, will stop the camera from zooming in every 4 beats.</str> + + <str id="autoPause-name">Auto Pause</str> + <str id="autoPause-desc">If checked, switching windows will pause the game.</str> + + <str id="volumeSFX-name">SFX Volume</str> + <str id="volumeSFX-desc">Changes only the volume of the SFX instead of everything.</str> + + <str id="songOffset-name">Song Offset</str> + <str id="songOffset-desc">Changes the offset that songs should start with.</str> + + <group name="GameplayOptionsAdvanced" prefix="Advanced."> + <str id="windowSize-name">Window Size</str> + <str id="windowSize-desc">...</str> + + <str id="streamedMusic-name">Streamed Music</str> + <str id="streamedMusic-desc">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.</str> + + <str id="streamedVocals-name">Streamed Vocals</str> + <str id="streamedVocals-desc">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.</str> + </group> + </group> + + <group name="LanguageOptions" prefix="LanguageOptions."> + <str id="language-name">Language</str> + <str id="language-desc">Language by {0} (Version: {1})</str> + </group> + + <group name="MiscOptions" prefix="MiscOptions."> + <str id="forceCrash-name">Force Crash</str> + <str id="forceCrash-desc">Select this option to force a crash.</str> + + <str id="betaUpdates-name">Enable Nightly Updates</str> + <str id="betaUpdates-desc">If checked, will also include nightly builds in the update checking.</str> + + <str id="checkForUpdates-name">Check for Updates</str> + <str id="checkForUpdates-desc">Select this option to check for new engine updates.</str> + <str id="checkForUpdates-noUpdateFound">No update found.</str> + + <str id="devMode-name">Developer Mode</str> + <str id="devMode-desc">If checked, you will be able to access developer features like certain keybinds, editors, developer options, state reloads, console and more.</str> + + <str id="resetSaveData-name">Reset Save Data</str> + <str id="resetSaveData-desc">Select this option to reset save data. This will remove all of your highscores.</str> + </group> + + <group name="DebugOptions" prefix="DebugOptions."> + <str id="title">Debug Options</str> + <str id="desc">Use this menu to enable or disable debug options.</str> + + <str id="showConsole-name">Show Console</str> + <str id="showConsole-desc">Select this to show the debug console, which contains log information about the game.</str> + + <str id="editorsResizable-name">Resizable Editors</str> + <str id="editorsResizable-desc">If checked, this will allow the editors to render beyond the base 1280x720 resolution of FNF (allowing for more detail and space...)</str> + + <str id="bypassEditorsResize-name">Bypass Editor Resize</str> + <str id="bypassEditorsResize-desc">Disables the minimum resolution for resizing editors (Needs Resizable Editors To Be Enabled).</str> + + <str id="editorSFX-name">Editor SFXs</str> + <str id="editorSFX-desc">If checked, will play sound effects when working on editors (ex: will play sfxs when checking checkboxes...)</str> + + <str id="editorPrettyPrint-name">Pretty Print</str> + <str id="editorPrettyPrint-desc">If checked, the saved files from the editor will be formatted to be easily viewable (does not apply to xmls...)</str> + + <str id="intensiveBlur-name">Intensive Blur</str> + <str id="intensiveBlur-desc">If checked, will use more intensive blur that may be laggier but look better.</str> + + <str id="charterAutoSaves-name">Editor Autosaves</str> + <str id="charterAutoSaves-desc">If checked, this will autosave your files in the editor, with the settings listed below.</str> + + <str id="charterAutoSaveTime-name">Autosaving Time</str> + <str id="charterAutoSaveTime-desc">This controls how often the editor will autosave your file (in seconds...)</str> + + <str id="charterAutoSaveWarningTime-name">Save Warning Time</str> + <str id="charterAutoSaveWarningTime-desc">This controls how long the editor will warn you before it autosaves (in seconds..., 0 to disable)</str> + + <str id="charterAutoSavesSeparateFolder-name">Autosaves Folder</str> + <str id="charterAutoSavesSeparateFolder-desc">If checked, this will autosave your file in a separate folder with a time stamp instead of overriding your current file. (song/autosaves/)</str> + </group> + +</language> \ No newline at end of file diff --git a/assets/languages/en/config.ini b/assets/languages/en/config.ini new file mode 100644 index 00000000..b020d221 --- /dev/null +++ b/assets/languages/en/config.ini @@ -0,0 +1,9 @@ +name="English" +credits="Codename Crew" +version="1.0.0" + +; Moving these in the wiki when releasing +; Shows missing ID:s as {id} and prints in the console Missing Translation IDs: id +; otherwhise uses the other languages (based on the chosen order) for the missing translations +; NOTE: it is recommended to use this feature for debug only, but do as you wish +showMissingIds="true" \ No newline at end of file diff --git a/assets/languages/it/assets/images/menus/mainmenu/credits.png b/assets/languages/it/assets/images/menus/mainmenu/credits.png new file mode 100644 index 00000000..be84c3c3 Binary files /dev/null and b/assets/languages/it/assets/images/menus/mainmenu/credits.png differ diff --git a/assets/languages/it/assets/images/menus/mainmenu/credits.xml b/assets/languages/it/assets/images/menus/mainmenu/credits.xml new file mode 100644 index 00000000..d03cb87d --- /dev/null +++ b/assets/languages/it/assets/images/menus/mainmenu/credits.xml @@ -0,0 +1,19 @@ +<!-- Credits to ITALetsPlayer --> +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="credits.png" width="1095" height="515"> + <SubTexture name="credits basic0000" x="631" y="175" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0001" x="631" y="175" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0002" x="631" y="175" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0003" x="633" y="1" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0004" x="633" y="1" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0005" x="633" y="1" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0006" x="633" y="307" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0007" x="633" y="307" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits basic0008" x="633" y="307" width="461" height="129" frameX="-0" frameY="-0" frameWidth="461" frameHeight="129"/> + <SubTexture name="credits white0000" x="1" y="349" width="629" height="165" frameX="-0" frameY="-7" frameWidth="630" frameHeight="172"/> + <SubTexture name="credits white0001" x="1" y="175" width="627" height="171" frameX="-0" frameY="-0" frameWidth="630" frameHeight="172"/> + <SubTexture name="credits white0002" x="1" y="1" width="629" height="171" frameX="-1" frameY="-0" frameWidth="630" frameHeight="172"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/languages/it/assets/images/menus/mainmenu/donate.png b/assets/languages/it/assets/images/menus/mainmenu/donate.png new file mode 100644 index 00000000..a0c5dbff Binary files /dev/null and b/assets/languages/it/assets/images/menus/mainmenu/donate.png differ diff --git a/assets/languages/it/assets/images/menus/mainmenu/donate.xml b/assets/languages/it/assets/images/menus/mainmenu/donate.xml new file mode 100644 index 00000000..87349e68 --- /dev/null +++ b/assets/languages/it/assets/images/menus/mainmenu/donate.xml @@ -0,0 +1,19 @@ +<!-- Credits to ITALetsPlayer --> +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="donate.png" width="664" height="757"> + <SubTexture name="donate basic0000" x="1" y="259" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate white0002" x="1" y="1" width="662" height="126" frameX="-50" frameY="-0" frameWidth="779" frameHeight="149"/> + <SubTexture name="donate white0001" x="1" y="130" width="655" height="126" frameX="-53" frameY="-0" frameWidth="779" frameHeight="149"/> + <SubTexture name="donate basic0003" x="1" y="384" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate basic0006" x="1" y="509" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate white0000" x="1" y="634" width="653" height="122" frameX="-55" frameY="-3" frameWidth="779" frameHeight="149"/> + <SubTexture name="donate basic0001" x="1" y="259" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate basic0002" x="1" y="259" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate basic0004" x="1" y="384" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate basic0005" x="1" y="384" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate basic0007" x="1" y="509" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> + <SubTexture name="donate basic0008" x="1" y="509" width="653" height="122" frameX="-50" frameY="-0" frameWidth="769" frameHeight="144"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/languages/it/assets/images/menus/mainmenu/options.png b/assets/languages/it/assets/images/menus/mainmenu/options.png new file mode 100644 index 00000000..62327aa7 Binary files /dev/null and b/assets/languages/it/assets/images/menus/mainmenu/options.png differ diff --git a/assets/languages/it/assets/images/menus/mainmenu/options.xml b/assets/languages/it/assets/images/menus/mainmenu/options.xml new file mode 100644 index 00000000..9a4fd7bf --- /dev/null +++ b/assets/languages/it/assets/images/menus/mainmenu/options.xml @@ -0,0 +1,19 @@ +<!-- Credits to ITALetsPlayer --> +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="options.png" width="581" height="808"> + <SubTexture name="options basic0000" x="1" y="477" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0001" x="1" y="477" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0002" x="1" y="477" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0003" x="1" y="588" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0004" x="1" y="588" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0005" x="1" y="588" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0006" x="1" y="699" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0007" x="1" y="699" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options basic0008" x="1" y="699" width="487" height="108" frameX="-0" frameY="-0" frameWidth="487" frameHeight="108"/> + <SubTexture name="options white0000" x="1" y="324" width="574" height="150" frameX="-0" frameY="-5" frameWidth="580" frameHeight="162"/> + <SubTexture name="options white0001" x="1" y="166" width="575" height="155" frameX="-1" frameY="-6" frameWidth="580" frameHeight="162"/> + <SubTexture name="options white0002" x="1" y="1" width="579" height="162" frameX="-1" frameY="-0" frameWidth="580" frameHeight="162"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/languages/it/assets/images/menus/mainmenu/story mode.png b/assets/languages/it/assets/images/menus/mainmenu/story mode.png new file mode 100644 index 00000000..6fb7bfaf Binary files /dev/null and b/assets/languages/it/assets/images/menus/mainmenu/story mode.png differ diff --git a/assets/languages/it/assets/images/menus/mainmenu/story mode.xml b/assets/languages/it/assets/images/menus/mainmenu/story mode.xml new file mode 100644 index 00000000..c8a0b995 --- /dev/null +++ b/assets/languages/it/assets/images/menus/mainmenu/story mode.xml @@ -0,0 +1,18 @@ +<!-- Credits to ITALetsPlayer --> +<?xml version="1.0" encoding="utf-8"?> +<TextureAtlas imagePath="story mode.png"> + <!-- Created with Adobe Animate version 21.0.6.41649 --> + <!-- http://www.adobe.com/products/animate.html --> + <SubTexture name="story mode basic0000" x="100" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0001" x="100" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0002" x="100" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0003" x="607" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0004" x="607" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0005" x="607" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0006" x="1114" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0007" x="1114" y="100" width="407" height="134"/> + <SubTexture name="story mode basic0008" x="1114" y="100" width="407" height="134"/> + <SubTexture name="story mode white0000" x="100" y="334" width="472" height="181" frameX="-4" frameY="26" frameWidth="480" frameHeight="187"/> + <SubTexture name="story mode white0001" x="672" y="334" width="479" height="175" frameX="0" frameY="23" frameWidth="480" frameHeight="187"/> + <SubTexture name="story mode white0002" x="1251" y="334" width="479" height="187" frameX="-1" frameY="30" frameWidth="480" frameHeight="187"/> +</TextureAtlas> diff --git a/assets/languages/it/assets/images/menus/titlescreen/titleEnter.png b/assets/languages/it/assets/images/menus/titlescreen/titleEnter.png new file mode 100644 index 00000000..3add0306 Binary files /dev/null and b/assets/languages/it/assets/images/menus/titlescreen/titleEnter.png differ diff --git a/assets/languages/it/assets/images/menus/titlescreen/titleEnter.xml b/assets/languages/it/assets/images/menus/titlescreen/titleEnter.xml new file mode 100644 index 00000000..4e50520c --- /dev/null +++ b/assets/languages/it/assets/images/menus/titlescreen/titleEnter.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="titleEnter-it.png" width="1113" height="2208"> + <SubTexture name="ENTER PRESSED0001" x="1" y="1" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0002" x="1" y="82" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0003" x="1" y="1" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0004" x="1" y="82" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0005" x="1" y="1" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0006" x="1" y="82" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0007" x="1" y="1" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0008" x="1" y="82" width="1111" height="78" frameX="-174" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="Press Enter to Begin0001" x="1" y="163" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0002" x="1" y="252" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0003" x="1" y="341" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0004" x="1" y="430" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0005" x="1" y="519" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0006" x="1" y="608" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0007" x="1" y="697" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0008" x="1" y="786" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0009" x="1" y="875" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0010" x="1" y="964" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0011" x="1" y="1053" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0012" x="1" y="1142" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0013" x="1" y="1231" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0014" x="1" y="1320" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0015" x="1" y="1409" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0016" x="1" y="1498" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0017" x="1" y="1587" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0018" x="1" y="1676" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0019" x="1" y="1765" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0020" x="1" y="1854" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0021" x="1" y="1943" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0022" x="1" y="2032" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0023" x="1" y="2121" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0024" x="1" y="2121" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0025" x="1" y="2032" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0026" x="1" y="1943" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0027" x="1" y="1854" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0028" x="1" y="1765" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0029" x="1" y="1676" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0030" x="1" y="1587" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0031" x="1" y="1498" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0032" x="1" y="1409" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0033" x="1" y="1320" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0034" x="1" y="1231" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0035" x="1" y="1142" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0036" x="1" y="1053" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0037" x="1" y="964" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0038" x="1" y="875" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0039" x="1" y="786" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0040" x="1" y="697" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0041" x="1" y="608" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0042" x="1" y="519" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0043" x="1" y="430" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0044" x="1" y="341" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0045" x="1" y="252" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0046" x="1" y="163" width="1110" height="86" frameX="-181" frameY="-3" frameWidth="1494" frameHeight="96"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/languages/it/config.ini b/assets/languages/it/config.ini new file mode 100644 index 00000000..15396e03 --- /dev/null +++ b/assets/languages/it/config.ini @@ -0,0 +1,9 @@ +name="Italiano" +credits="Nex_isDumb" +version="1.0.0" + +; Moving these in the wiki when releasing +; Shows missing ID:s as {id} and prints in the console Missing Translation IDs: id +; otherwhise uses the other languages (based on the chosen order) for the missing translations +; NOTE: it is recommended to use this feature for debug only, but do as you wish +showMissingIds="false" \ No newline at end of file diff --git a/assets/languages/pl/Editors.xml b/assets/languages/pl/Editors.xml new file mode 100644 index 00000000..2e306386 --- /dev/null +++ b/assets/languages/pl/Editors.xml @@ -0,0 +1,654 @@ +<?xml version="1.0" encoding="utf-8" ?> +<language> + <group name="General Editor Translations" prefix="editor."> + <str id="autosavingIn">Autozapisywanie za {0} sekund{1}</str> + <str id="autosavingCanceledSuffix">((Anulowane)!</str> + + <str id="ok">Ok</str> + <str id="cancel">Anuluj</str> + <str id="save">Zapisz</str> + <str id="saveClose">Zapisz i Zamknij</str> + <str id="close">Zamknij</str> + </group> + + <group name="UIImageExplorer" prefix="uiImageExplorer."> + <str id="symbol">Symbol</str> + <str id="animation">Animacja</str> + <str id="atlas">Atlas</str> + <str id="spritemap">Mapa sprite'ów</str> + + <str id="directory">Folder ({0}/)</str> + <str id="found">{0} {1} Znaleziono</str> + <str id="foundplural">{0} {1} Znaleziono</str> + <str id="notFound">#Nie znaleziono {0}#</str> + <str id="foundAtlas">{0} Znalezione Atlasy</str> + <str id="foundAtlasplural">{0} Znalezione Mapy Sprite'ów</str> + + <str id="fileAlreadyExists">Dane pliki już istnieją: \n\n{0}\n\nWAŻNE: NADPISYWANIE NIE MOŻE BYĆ COFNIĘTE!</str> + <str id="override">Nadpisz</str> + <str id="useExisting">Użyj Istniejących</str> + </group> + + <group name="EditorPicker" prefix="editor."> + <str id="alphabet.name">Edytor Alfabetów</str> + <str id="chart.name">Edytor Mapy</str> + <str id="character.name">Edytor Postaci</str> + <str id="stage.name">Edytor Scen</str> + <str id="uiDebug.name">Debug Interfejsu Użytkownika</str> + <str id="wiki.name">Wiki</str> + <str id="debugOptions.name">Opcje Debugowania</str> + </group> + + <group name="ChartEditor"> + <group name="CharterSelection" prefix="charterSelection."> + <str id="desc">Wybierz mapę piosenki do edytowania."</str> + <str id="acceptSong">Naciśnij AKCEPTUJ aby wybrać trudność do edytowania.</str> + <str id="acceptDifficulty">Naciśnij AKCEPTUJ aby edytować mapę wybranej trudności.</str> + <str id="selectDifficulty">Wybierz trudność aby kontynuować.</str> + <str id="newDifficulty">Nowa Trudność</str> + <str id="newSong">Nowa Piosenka</str> + </group> + + <group name="ChartCreationScreen" prefix="chartCreation."> + <str id="title">Tworzenie Nowej Mapy</str> + <str id="info">Informacja Mapy</str> + <str id="diffName">Nazwa Trudności</str> + <str id="stage">Scena</str> + <str id="scrollSpeed">Prędkość Przewijania</str> + + <str id="strumLineList">Strumy $* Potrzebna Przynajmniej 1 Struma $</str> + <str id="strumLine.format">Struma - #{0}</str> + + <group name="Warnings" prefix="warnings."> + <str id="song-exists-title">Tworzenie Piosenki: Błąd!</str> + <str id="song-exists-body">Piosenka którą próbujesz utworzyć już istnieje, jeśli chcesz nadpisać piosenkę, najpierw ją usuń!</str> + <str id="chart-exists-title">Tworzenie Mapy: Błąd!</str> + <str id="chart-exists-body">Mapa którą próbujesz utworzyć już istnieje, jeśli chcesz nadpisać mapę, najpierw ją usuń!</str> + </group> + </group> + + <group name="ChartDataScreen" prefix="chartDataScreen."> + <str id="title">Właściwości Mapy</str> + <str id="editChartData">Edytuj Dane Mapy</str> + <str id="stage">Scena</str> + <str id="scrollSpeed">Prędkość Przewijania</str> + </group> + + <group name="SongPosInfo" prefix="songPosInfo."> + <str id="step">Krok: {0}</str> + <str id="beat">Bit: {0}</str> + <str id="measure">Miara: {0}</str> + <str id="bpm">Tempo: {0}</str> + <str id="timeSignature">Metrum: {0}/{1}</str> + </group> + + <group name="CharterEventScreen" prefix="charterEventScreen."> + <str id="title-creating">Stwórz Grupę Wydarzeń</str> + <str id="title-editing">Edytuj Grupę Wydarzeń</str> + + <str id="noEvent">Brak Wydarzenia</str> + + <str id="strumLine.format">Struma #{0} ({1})</str> + </group> + + <group name="CharterEventTypeSelection" prefix="charterEventTypeSelection."> + <str id="title">Wybierz typ wydarzenia...</str> + </group> + + <group name="CharterMetaDataScreen" prefix="charterMetaDataScreen."> + <str id="title">Edytuj Metadane</str> + + <str id="songData">Dane Ścieżki</str> + <str id="songName">Nazwa Ścieżki</str> + <str id="bpm">Tempo</str> + <str id="timeSignature">Metrum</str> + <str id="menusData">Dane Menu (Gra Dowolna/Opowieść)</str> + <str id="displayName">Wyświetlana Nazwa</str> + <str id="icon">Ikona</str> + <str id="modesAllowed">Dozwolone Tryby</str> + <str id="color">Kolor</str> + <str id="difficulties">Trudności</str> + <str id="customValues">Własna Waluta (Zaawansowane)</str> + + <str id="opponentMode">Tryb Przeciwnika</str> + <str id="coopAllowed">Tryb Ko-operacyjny</str> + </group> + + <group name="CharterNoteTypesList" prefix="charterNoteTypesList."> + <str id="title">Edytor Listy Typów Nut</str> + + <str id="editNoteTypes">Edytuj Typy Nut</str> + <str id="editType">Typ Nuty {0}</str> + </group> + + <group name="CharterStrumlineScreen" prefix="charterStrumlineScreen."> + <str id="win-title-creating">Tworzenie strumy {0}</str> + <str id="win-title-editing">Edytowanie właściwości strumy {0}</str> + + <str id="title-creating">Stwórz Nową Strumę</str> + <str id="title-editing">Edytuj Właściwości Strumy</str> + </group> + + <group name="SongCreationScreen" prefix="songCreationScreen."> + <str id="win-title">Tworzenie Nowej Ścieżki</str> + <str id="win-title-importing">Importowanie Ścieżki</str> + <str id="title">Informacja Ścieżki</str> + + <str id="instAudio">Instrumentalny Plik Audio $* Wymagane*$</str> + <str id="voicesAudio">Wokalny Plik Audio</str> + + <str id="importFrom">Importuj z...</str> + <str id="importSource">Źródło Importu:</str> + <str id="legacyOrPsych">Psych/Legacy FNF</str> + <str id="vslice">V-Slice</str> + <str id="vsliceProject">Projekt V-Slice (.fnfc)</str> + <str id="importChartFormat">Format Mapy</str> + <str id="importAudios">Dodaj pliki Audio</str> + <str id="songFileName">Nazwa pliku ścieżki $* Wymagane$</str> + <str id="songDataFile">Plik Danych/Mapy $* Wymagane$</str> + <str id="songMetaFile">Plik Meta $* Wymagane$</str> + + <str id="back">Wstecz</str> + <str id="next">Następny</str> + </group> + + <group name="StrumlineOptions" prefix="charterStrumLine."> + <str id="characters">Postacie</str> + <str id="newChar">Nowa Postać</str> + <str id="type">Typ</str> + <str id="scrollSpeed">Prędkość Przewijania</str> + <str id="spacing">Oddział</str> + <str id="stagePos">Pozycja Na Scenie</str> + <str id="scale">Rozmiar</str> + <str id="hudPos">Pozycja Interfejsu (X [Stosunek 0-1],Y)</str> + <str id="visible">Widoczne?</str> + <str id="useChartScrollSpeed">Używa Prędkości Przewijania Mapy?</str> + <str id="vocalSuffix">Suffix Wokali</str> + <str id="keyCount">Liczba Kluczy</str> + </group> + + <group name="ChartEditor" prefix="charter."> + <!-- Charter.hx --> + <group name="TopBar"> + <str id="topBar.file">Plik</str> + <group name="FileMenu" prefix="file."> + <str id="new">Nowy</str> + <str id="save">Zapisz</str> + <str id="saveAs">Zapisz Jako...</str> + <str id="saveGlobalEvents">Zapisz Wydarzenia Globalne</str> + <str id="saveGlobalEventsAs">Zapisz Wydarzenia Globalne Jako...</str> + <str id="saveNoLocalEvents">Zapisz mapę bez Wydarzeń Lokalnych</str> + <str id="saveNoLocalEventsAs">Zapisz mapę bez Wydarzeń Lokalnych Jako...</str> + <str id="saveMeta">Zapisz Metadane</str> + <str id="saveMetaAs">Zapisz Metadane Jako...</str> + <str id="exportFnfLegacy">Eksportuj Dla FNF Legacy...</str> + <str id="exportPsych">Eksportuj Dla Psych Engine...</str> + <str id="exit">Wyjdź</str> + </group> + + <str id="topBar.edit">Edytuj</str> + <group name="EditMenu" prefix="edit."> + <str id="undo">Cofnij</str> + <str id="redo">Awansuj</str> + <str id="copy">Kopiuj</str> + <str id="paste">Wklej</str> + <str id="cut">Wytnij</str> + <str id="delete">Usuń</str> + </group> + + <str id="topBar.chart">Mapa</str> + <group name="ChartMenu" prefix="chart."> + <str id="playtest">Testuj Mapę</str> + <str id="playtestHere">Testuj Mapę tutaj</str> + <str id="playtestOpponent">Testuj Mapę jako przeciwnik</str> + <str id="playtestOpponentHere">Testuj Mapę jako przeciwnik tutaj</str> + <str id="enableScripts">Włącz skrypty podczas Testowania Mapy</str> + <str id="editChartData">Edytuj Dane Mapy</str> + <str id="editMetadata">Edytuj Informacje Metadanych</str> + </group> + + <str id="topBar.view">Widok</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Przybliż</str> + <str id="zoomOut">Oddal</str> + <str id="resetZoom">Resetuj Przybliżenie</str> + <str id="showSectionsSeparator">Pokaż Oddział Sekcji</str> + <str id="showBeatsSeparator">Pokaż Oddział Bitów</str> + <str id="rainbowWaveforms">Tęczowe Fale Dźwiękowe</str> + <str id="lowDetailWaveforms">Mało Detalowe Fale Dźwiękowe</str> + <str id="scrollLeft">Przewiń w Lewo</str> + <str id="scrollRight">Przewiń w Prawo</str> + <str id="scrollReset">Resetuj Przewinięcie</str> + </group> + + <str id="topBar.song">Ścieżka</str> + <group name="SongMenu" prefix="song."> + <str id="goStart">Wróć do początku</str> + <str id="goEnd">Idź do końca</str> <!-- used to be "Go to the end" --> + <str id="muteInst">Wycisz Instrumental</str> + <str id="muteVoices">Wycisz Wokale</str> + </group> + + <group name="BookmarkMenu" prefix="bookmarks."> + <str id="addBookmarkHere">Dodaj Zakładkę Tutaj</str> + <str id="editBookmarkList">Edytuj Listę Zakładek</str> + <str id="goto">Idź do "{0}"</str> + <str id="createBookmarkTitle">Tworzenie Nowej Zakładki...</str> + <str id="createBookmarkName">Nazwa Zakładki:</str> + <str id="createBookmarkColor">Kolor Zakładki:</str> + <str id="editBookmarkListTitle">Lista Zakładek</str> + <str id="editBookmarksTitle">Edytuj Zakładki</str> + <str id="newBookmarkName">Nowa Zakładka</str> + </group> + + <str id="topBar.note">Nuty</str> + <group name="NoteMenu" prefix="note."> + <str id="addSustainLength">Przedłuż Nutę</str> + <str id="subtractSustainLength">Skróć Nutę</str> + <str id="selectAll">Wybierz wszystkie</str> + <str id="selectMeasure">Wybierz metrum</str> + <str id="editNoteTypesList">Edytuj Listę Typów Nut</str> + </group> + <str id="topBar.snap">Snapowanie</str> + <group name="SnapMenu" prefix="snap."> + <str id="increaseSnap">↑ Snapowanie do Siatki</str> + <str id="resetSnap">Resetuj Snapowanie do Siatki</str> + <str id="decreaseSnap">↓ Snapowanie do Siatki</str> + + <str id="snap">{0}x Snapowanie do Siatki</str> + </group> + + <str id="topBar.playback">Odtwarzanie</str> + <group name="PlaybackMenu" prefix="playback."> + <str id="play">Graj/Pauzuj</str> + <str id="speedRaise">↑ Prędkość {0}%</str> + <str id="speedReset">Resetuj Prędkość</str> + <str id="speedLower">↓ Prędkość {0}%</str> + <str id="sectionBack">Cofnij o Sekcję</str> + <str id="sectionForward">Awansuj o Sekcję</str> + <str id="sectionStart">Idź do początku Sekcji</str> + <str id="backStep">Wróć o Krok</str> + <str id="forwardStep">Awansuj o Krok</str> + <str id="metronome">Taktomierz</str> + <str id="visualMetronome">Wizualny taktomierz</str> + </group> + </group> + + <group name="NoteTypes" prefix="noteTypes."> + <str id="default">Domyślna Nuta</str> + </group> + + <group name="EventTypes" prefix="events."> + <!-- You can add your custom event types here! --> + <!-- Just need to do some conversion from the raw name to the id --> + <!-- Example: HScript Call -> hscriptCall --> + <!-- Example: Change Character -> changeCharacter --> + <str id="hscriptCall">Zawołanie HScript</str> + <str id="cameraMovement">Ruch Kamery</str> + <str id="addCameraZoom">Dodaj Przybliżenie Kamery</str> + <str id="cameraModuloChange">Zmień Modulo Kamery</str> + <str id="cameraFlash">Błyskanie Kamery</str> + <str id="bpmChange">Zmiana Tempa</str> + <str id="scrollSpeedChange">Zmiana Prędkości Przewijania</str> + <str id="altAnimationToggle">Przełącznik Animacji Alternatywnej</str> + <str id="playAnimation">Odtwórz Animację</str> + </group> + + <group name="StrumlineOptions" prefix="strumLine."> + <str id="button-name">Opcje ↓</str> + + <str id="hitsounds">Dźwięki Uderzenia</str> + <str id="muteVocals">Wycisz Wokal</str> + <str id="edit">Edytuj</str> + <str id="delete">Usuń</str> + + <str id="noWaveform">Brak Fali Dźwiękowej</str> + </group> + + <str id="createNew">Stwórz Nowe</str> + <str id="lock-unlock">Kłódka</str> + + <str id="info.localEvents">Wydarzenia Lokalne\n(Tylko Ta Trudność)</str> + <str id="info.globalEvents">Wydarzenia Globalne\n(Wszystkie Trudności)</str> + <str id="eventType-local">Wydarzenie Lokalne</str> + <str id="eventType-global">Wydarzenie Globalne</str> + + <!-- CharterEventAdd.hx --> + <str id="addEvent">Dodaj Wydarzenie</str> + <str id="editEvent">Edytuj Wydarzenie</str> <!-- Should maybe be "Edit Event" --> + <str id="deleteEvent">Usuń Wydarzenie</str> + + <group name="Notifications" prefix="popup."> + <str id="savedChartAt">Zapisano Mapę pod {0}!</str> + </group> + </group> + </group> + + <group name="CharacterEditor"> + <group name="CharacterSelection" prefix="characterSelection."> + <str id="acceptFolder">Naciśnij AKCEPTUJ aby edytować ten folder.</str> + <str id="acceptCharacter">Naciśnij AKCEPTUJ aby edytować tą postać.</str> + <str id="newCharacter">Nowa Postać</str> + <str id="newCharacterDesc">Naciśnij AKCEPTUJ aby stworzyć nową postać.</str> + <str id="desc">Wybierz postać do edytowania</str> + <str id="desc-folder">Wybierz postać do edytowania w: {0}</str> + </group> + + <group name="CharacterInfoScreen" prefix="characterInfoScreen."> + <str id="win-title">Edytowanie Postaci</str> + <str id="title">Edytuj Informacje Postaci</str> + <str id="icon">Ikona</str> + <str id="iconColor">Kolor Ikony</str> + <str id="singDuration">Trwanie Śpiewu</str> + <str id="useSingDuration">Użyj Trwania Śpiewu?</str> + <str id="customValues">Własna Waluta (Zaawansowane)</str> + </group> + + <group name="CharacterEditor" prefix="characterEditor."> + <group name="TopBar"> + <str id="topBar.file">Plik</str> + <group name="FileMenu" prefix="file."> + <str id="new">Nowy</str> + <str id="save">Zapisz</str> + <str id="saveAs">Zapisz jako...</str> + <str id="exit">Wyjdź</str> + </group> + + <str id="topBar.edit">Edytuj</str> + <group name="EditMenu" prefix="edit."> + <str id="copyOffset">Kopiuj Domiar</str> + <str id="pasteOffset">Wklej Domiar</str> + <str id="undo">Cofnij</str> + <str id="redo">Ponów</str> + <str id="editInfo">Edytuj Informacje</str> + <str id="editSprite">Edytuj Sprite'a</str> + </group> + + <str id="topBar.offset">Domiary</str> + <group name="OffsetsMenu" prefix="offset."> + <str id="moveLeft">Rusz w Lewo</str> + <str id="moveUp">Rusz w Górę</str> + <str id="moveDown">Rusz w Dół</str> + <str id="moveRight">Rusz w Prawo</str> + <str id="moveExtraLeft">Rusz Mocniej w Lewo</str> + <str id="moveExtraUp">Rusz Mocniej w Górę</str> + <str id="moveExtraDown">Rusz Mocniej w Dół</str> + <str id="moveExtraRight">Rusz Mocniej w Prawo</str> + <str id="dragOffsetsMouse">Ruszaj Domiary Myszą?</str> + <str id="clearOffsets">Wyczyść Domiary</str> + </group> + + <str id="topBar.stage">Scena</str> + <group name="StageMenu" prefix="stage."> + <str id="useStage">Użyj "{0}"</str> + <str id="useNone">Nie Używaj Scen</str> + </group> + + <str id="topBar.view">Widok</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Przybliż</str> + <str id="zoomOut">Oddal</str> + <str id="resetZoom">Resetuj Przybliżenie</str> + <str id="charHitbox">Hitboks Postaci?</str> + <str id="charCam">Kamera Postaci?</str> + <str id="axis">Osie XY?</str> + </group> + + <str id="topBar.animation">Animacje</str> + <group name="AnimationMenu" prefix="animation."> + <str id="playAnim">Odtwórz Animację</str> + <str id="stopAnim">Zatrzymaj Animację</str> + <str id="changeAnimUp">Zmień Animację ↑</str> + <str id="changeAnimDown">Zmień Animację ↓</str> + <str id="playAnimOnOffset">Graj Animacje na zmianę Domiaru?</str> + </group> + + <group name="CharacterPropertiesWindow" prefix="characterProperties."> + <str id="position">Pozycja (X, Y)</str> + <str id="scale">Skala</str> + <str id="editInfo">Edytuj Info</str> + <str id="editSprite">Edytuj Sprite'a</str> + <str id="camPosition">Pozycja Kamery (X, Y)</str> + <str id="charFlipped">Obrócone?</str> + <str id="antialiased">Wygładzone?</str> + <str id="testCharAs">Testuj Postać Jako...</str> + <str id="charDesign">Postać Zaprojektowana Jako...</str> + </group> + + <group name="CharacterAnimButton" prefix="characterAnim."> + <str id="defaultAnimName">Nowa Animacja</str> + <str id="name">Imię</str> + <str id="anim">Animacja</str> + <str id="position">Pozycja (X, Y)</str> + <str id="fps">Klatki na Sekundę</str> + <str id="indices">Indeksy (Klatki)</str> + </group> + + <group name="CharacterSpriteScreen" prefix="characterSpriteScreen."> + <str id="win-title">Edytuj sprite'a postaci</str> + <str id="charImageFile">Plik Obrazu Postaci $* Wymagane$</str> + </group> + </group> + </group> + + <group name="CharacterCreationScreen" prefix="characterCreationScreen."> + <str id="win-title">Tworzenie nowej Postaci</str> + <str id="charName">Imię postaci $* Wymagane$</str> + <str id="charImgName">Plik Obrazu Postaci $* Wymagane$</str> + </group> + </group> + + <group name="AlphabetEditor" prefix="editor.alphabet."> + <group name="AlphabetSelection" prefix="selection."> + <str id="acceptTypeface">Naciśnij AKCEPTUJ aby edytować to Pismo.</str> + <str id="newTypeface">Nowe Pismo</str> + <str id="desc">Wybierz Pismo do edytowania</str> + + <group name="Warnings" prefix="warnings."> + <str id="notImplemented-title">Funkcja nie dodana!</str> + <str id="notImplemented-body">Ta funkcja jeszcze nie została dodana. Proszę oczekiwać aktualizacji która doda tą funkcję.\n\n\n- Codename Crew</str> + </group> + </group> + + <group name="AlphabetEditor"> + <group name="TopBar"> + <str id="topBar.file">Plik</str> + <group name="FileMenu" prefix="file."> + <str id="new">Nowy</str> + <str id="save">Zapisz</str> + <str id="saveAs">Zapisz Jako...</str> + <str id="exit">Wyjdź</str> + </group> + + <str id="topBar.edit">Edytuj</str> + <group name="EditMenu" prefix="edit."> + <str id="undo">Cofnij</str> + <str id="redo">Ponów</str> + </group> + + <str id="topBar.glyph">Glif</str> + <group name="GlyphMenu" prefix="glyph."> + <str id="newGlyph">Nowy Glif</str> + <str id="editGlyph">Edytuj Glif</str> + <str id="deleteGlyph">Usuń Glif</str> + </group> + + <str id="topBar.offset">Domiary</str> + <group name="OffsetsMenu" prefix="offset."> + <str id="moveLeft">Rusz w Lewo</str> + <str id="moveUp">Rusz w Górę</str> + <str id="moveDown">Rusz w Dół</str> + <str id="moveRight">Rusz w Prawo</str> + <str id="moveExtraLeft">Rusz Mocniej w Lewo</str> + <str id="moveExtraUp">Rusz Mocniej w Górę</str> + <str id="moveExtraDown">Rusz Mocniej w Dół</str> + <str id="moveExtraRight">Rusz Mocniej w Prawo</str> + <str id="dragOffsetsMouse">Ruszaj Domiary Myszą?</str> + <str id="clearOffsets">Wyczyść Domiary</str> + </group> + + <str id="topBar.view">Widok</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Przybliż</str> + <str id="zoomOut">Oddal</str> + <str id="resetZoom">Resetuj Przybliżenie</str> + </group> + </group> + </group> + </group> + + <group name="StageEditor"> + <group name="StageSelection" prefix="stageSelection."> + <str id="desc">Wybierz scenę do edytowania. Sceny zaprogramowane w samym HSCRIPT nie są wspierane.</str> + <str id="acceptStage">Naciśnij AKCEPTUJ aby edytować tą scenę.</str> + <str id="newStage">Nowa Scena</str> + <str id="acceptNewStage">Naciśnij AKCEPTUJ aby stworzyć nową scenę.</str> + </group> + + <group name="StageEditor" prefix="stageEditor."> + <group name="TopBar"> + <str id="topBar.file">Plik</str> + <group name="FileMenu" prefix="file."> + <str id="save">Zapisz</str> + <str id="saveAs">Zapisz Jako...</str> + <str id="exit">Wyjdź</str> + </group> + + <str id="topBar.edit">Edytuj</str> + <group name="EditMenu" prefix="edit."> + <str id="undo">Cofnij</str> + <str id="redo">Ponów</str> + <str id="editStageInfo">Edytuj Info Sceny</str> + </group> + + <str id="topBar.select">Zaznaczenie</str> + <group name="SelectionMenu" prefix="select."> + <str id="all">Zaznacz Wszystko</str> + <str id="deselect">Odznacz</str> + <str id="inverse">Odwróć Zaznaczenie</str> + </group> + + <str id="topBar.view">Widok</str> + <group name="ViewMenu" prefix="view."> + <str id="zoomIn">Przybliż</str> + <str id="zoomOut">Oddal</str> + <str id="resetZoom">Resetuj Przybliżenie</str> + <str id="focusDad">Skupienie na Dad</str> + <str id="focusGF">Skupienie na GF</str> + <str id="focusBF">Skupienie na BF</str> + </group> + + <str id="topBar.editor">Edytor</str> + <group name="EditorMenu" prefix="editor."> + <str id="showCharacters">Pokaż Postacie</str> + </group> + + <group name="StageSpriteScreen" prefix="stageSpriteScreen."> + <str id="win-title">Edytuj Sprite'a Sceny</str> + <str id="stageImageFile">Plik Obrazu Sceny $* Wymagane$</str> + </group> + </group> + </group> + + <group name="StageCreationScreen" prefix="stageCreationScreen."> + <str id="win-title">Tworzenie Nowej Sceny</str> + <str id="title">Info Sceny</str> + <str id="stageName">Nazwa Sceny</str> + <str id="stagePath">Ścieżka Plikowa Sceny</str> + </group> + + <group name="StageInfoScreen" prefix="stageInfoScreen"> + <str id="win-title">Edytowanie Info Sceny "{0}"</str> + <str id="title">Info Sceny</str> + <str id="stageName">Nazwa Sceny</str> + <str id="spritePath">Ścieżka Plikowa Sprite'a\n(Wymaga Odświeżenia)</str> + <str id="startCamPos">Pozycja Startowa Kamery (X, Y)</str> + <str id="zoom">Przybliżenie</str> + </group> + + <group name="StageElementEditScreen" prefix="stageElementEditScreen."> + <str id="win-character-title">Edytowanie Postaci "{0}"</str> + <str id="win-sprite-title">Edytowanie Sprite'a "{0}"</str> + <str id="win-title">Edytowanie Pliku XML <{0} /></str> + + <str id="character-title">Dane Postaci</str> + <str id="sprite-title">Dane Sprite'a</str> + <str id="title">Dane Pliku XML</str> + + <str id="elementName">Nazwa Elementu</str> + <str id="nameID">Nazwa Identyfikatora</str> + <str id="imageFile">Plik Obrazu (W TRACKIE PRACY: wymaga odświeżenia)\nIn (${stage.spritesParentFolder})</str> + <str id="scroll">Przewijanie {0}</str> + <str id="scale">Skala {0}</str> + <str id="camera">Kamera {0}</str> + <str id="spacing">Miejsce (X, Y)</str> + <str id="skew">Krzywienie {0}</str> + <str id="alpha">Przezroczystość</str> + <str id="angle">Kąt</str> + <str id="zoomFactor">Czynnik Przybliżania</str> + <str id="positionName">Nazwa Pozycji</str> + <str id="flipX">Odwróć w Osi X</str> + <str id="antialiasing">Wygładzanie</str> + <str id="highMemory">Wysoka Pamięć</str> + <str id="lowMemory">Niska Pamięć</str> + <str id="attributes">Atrybuty</str> + <str id="transformPreview"> Podgląd Transformacji:</str> + + <str id="animType">Typ Animacji</str> + <group name="AnimTypeDropdown" prefix="animType."> + <str id="none">BRAK</str> + <str id="beat">BIT</str> + <str id="loop">PĘTLA</str> + </group> + <str id="tipCustomProperties">Wskazówka: Aby dostać się do własnych atrybutów,\ntrzymaj Shift przy kliknięciu "Edytuj"</str> + </group> + + <group name="StageXMLEditScreen" prefix="stageXMLEditScreen."> + <str id="win-title">Edytowanie Pliku XML {0}</str> + <str id="title">Dane Pliku XML</str> + <str id="attributes">Atrybuty</str> + </group> + + <str id="stageCharacters.requiredPosition">Ta pozycja postaci jest wymagana w Scenach!</str> + <str id="stageCharacters.noDeleteGirlfriend">Nie możesz usunąć Girlfriend.</str> + + <group name="StageElement" prefix="stageElement."> + <str id="scale">Skala: {0}, {1}</str> + <str id="scroll">Przewijanie: {0}, {1}</str> + </group> + + <group name="StageSprites" prefix="stageSprites."> + <str id="win-title">Sprite'y Sceny</str> + <str id="sprite">Sprite</str> + <str id="box">Blok</str> + <str id="character">Postać</str> + <str id="warnings.not-implemented">Tworzenie Bloku jeszcze nie istnieje!</str> + </group> + </group> + + <!-- PlayTesting and SaveWarning are almost the same but with some small differences --> + <!-- PlayTesting allows color in the description by doing *hello* --> + <!-- SaveWarning doesn't allow color in the description --> + <group name="PlayTesting" prefix="playtesting."> + <str id="chartPlaytesting" notrim="true"> (Testowanie Mapy)</str> + <str id="exitGame">Wyjdź z Gry</str> + <str id="exitToMenu">Wyjdź do Menu</str> + <str id="saveAndExitGame">Zapisz i Wyjdź z Gry</str> + <str id="saveAndExitToMenu">Zapisz i Wyjdź do Menu</str> + <str id="cancel">Anuluj</str> + + <str id="warningTitle">NIEZAPISANE ZMIANY!</str> + <str id="warningDesc">Twoje zmiany *znikną* jeśli nie zostaną zapisane. (Nie mogą być przywrócone)\n\n\nCzy chcesz anulować?</str> + </group> + + <group name="SaveWarning" prefix="saveWarning."> + <str id="warningTitle">Niezapisane Zmiany!</str> + <str id="warningDesc">Twoje zmiany znikną jeśli nie zostaną zapisane. (Nie mogą być przywrócone)\n\n\nCzy chcesz anulować?</str> + + <str id="exitGame">Wyjdź z Gry</str> + <str id="exitToMenu">Wyjdź do Menu</str> + <str id="saveAndExitGame">Zapisz i Wyjdź z Gry</str> + <str id="saveAndExitToMenu">Zapisz i Wyjdź do Menu</str> + <str id="cancel">Anuluj</str> + </group> +</language> diff --git a/assets/languages/pl/Main.xml b/assets/languages/pl/Main.xml new file mode 100644 index 00000000..c910f34b --- /dev/null +++ b/assets/languages/pl/Main.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8" ?> +<language> + <group name="PlayState"> + <str id="game.score">Wynik:{0}</str> + <str id="game.misses">Chybienia:{0}</str> + <str id="game.comboBreaks">Utraty Kombo:{0}</str> + <str id="game.accuracy">Celność:{0} - {1}</str> + <str id="game.combo">Kombo:{0}</str> + + <str id="game.rating.[N/A]">[N/A]</str> + <str id="game.rating.F">F</str> + <str id="game.rating.E">E</str> + <str id="game.rating.D">D</str> + <str id="game.rating.C">C</str> + <str id="game.rating.B">B</str> + <str id="game.rating.A">A</str> + <str id="game.rating.S">S</str> + <str id="game.rating.S++">S++</str> + </group> + + <group name="StoryMenu"> + <str id="story.score">WYNIK TYGODNIA:{0}</str> + <str id="story.tracks">PIOSENKI</str> + </group> + + <group name="Freeplay"> + <str id="freeplay.score">NAJWYŻSZY WYNIK:{0}</str> + <str id="freeplay.solo">Solo</str> + <str id="freeplay.opponentMode">Tryb Przeciwnika</str> + <str id="freeplay.coopMode">Tryb Ko-operacyjny</str> + <str id="freeplay.coopModeSwitched">Tryb Ko-operacyjny (Zamianka)</str> + </group> + + <group name="Pause"> + <group name="PauseOptions"> + <str id="pause.resume">Wznów</str> + <str id="pause.restartSong">Powtórz Piosenkę</str> + <str id="pause.changeControls">Zmień Klawisze</str> + <str id="pause.changeOptions">Zmień Ustawienia</str> + <str id="pause.exitToMenu">Wyjdź do Menu</str> + <str id="pause.exitToCharter">Wróć do Mapowania</str> + <str id="pause.resumeCutscene">Wznów Scenkę</str> + <str id="pause.skipCutscene">Pomiń Scenkę</str> + <str id="pause.restartCutscene">Powtórz Scenkę</str> + </group> + + <str id="pause.deathCounter">Niebieskie Jaja: {0}</str> + <str id="pause.coopMode">TRYB KO-OPERACYJNY</str> + <str id="pause.opponentMode">TRYB PRZECIWNIKA</str> + </group> + + <group name="Credits"> + <group name="CreditsCodename"> + <str id="credits.mainDev" notrim="true"> *- Publiczny członek głównej drużyny!*</str> + <str id="credits.totalContributions">Totalne Udziały: ~{0}~ / *{1}* (~{2}%~) - Wybierz by otworzyć konto Github</str> + <str id="credits.downloadingList">Pobieranie Listy...</str> + <str id="credits.allContributors">Wszyscy współpracownicy silnika! - Naciśnij RESET aby zaktualizować listę (Jeden reset co 2 minuty).</str> + </group> + <group name="CreditsMain"> + <str id="credits.name">Kredyty</str> + <str id="credits.madePossible">Ludzie którzy to umożliwili!</str> + <str id="credits.selectCodename">Wybierz to aby zobaczyć wszystkich współpracowników!</str> + <str id="credits.selectBase">Wybierz to aby otworzyć stronę itch.io oryginalnej gry!</str> + </group> + </group> + + <group name="Mods"> + <str id="mods.disableMods">Wyłącz Mody</str> + </group> + + <group name="MainMenu"> + <str id="mainMenu.commit">Udział: {0} ({1})</str> + <str id="mainMenu.openMods">[{0}] Otwórz menu modów</str> + </group> + + <group name="BetaWarning"> + <str id="betaWarning.title">OSTRZEŻENIE</str> + <str id="betaWarning.desc">Ten Silnik nadal jest w fazie *{0}*. To oznacza że *większość funkcji* ma *błędy* lub jest *niedokończona*. Jeśli znajdziesz jakiekolwiek błędy, zgłoś to na Github'ie silnika Codename. Proszę być uprzedzonym iż większość członków rozmawia tylko w języku angielskim.</str> + <str id="betaWarning.proceed">Naciśnij {0} aby kontynuować</str> + </group> + + <group name="Updater"> + <group name="UpdateAvailable"> + <str id="updateAvailable.title">NOWA AKTUALIZACJA</str> + <str id="updateAvailable.install">INSTALUJ</str> + <str id="updateAvailable.skip">POMIŃ</str> + </group> + <group name="UpdateScreen"> + <str id="updateScreen.general-1">Przygotowanie instalacji aktualizacji...</str> + <str id="updateScreen.part-1">Tworzenie folderu instalacji i czyszczenie starych plików...</str> + + <str id="updateScreen.general-2">Pobieranie plików aktualizacji...</str> + <str id="updateScreen.part-2">Pobieranie pliku {0}</str> + + <str id="updateScreen.general-3">Pobieranie nowego pliku exe silnika...</str> + <str id="updateScreen.part-3">Pobieranie {0}</str> + + <str id="updateScreen.general-4">Instalowanie nowych plików...</str> + <str id="updateScreen.part-4">Instalowanie {0}</str> + </group> + </group> + + <group name="Difficulties"> + <str id="diff.easy">Łatwy</str> + <str id="diff.normal">Normalny</str> + <str id="diff.hard">Trudny</str> + <str id="diff.erect">Erekcja</str> + <str id="diff.nightmare">Koszmarny</str> + </group> +</language> diff --git a/assets/languages/pl/Options.xml b/assets/languages/pl/Options.xml new file mode 100644 index 00000000..0549951f --- /dev/null +++ b/assets/languages/pl/Options.xml @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="utf-8" ?> +<language> + <str id="optionsMenu.desc-missing">Brak Opisu</str> + + <group name="OptionsMenu"> + <str id="optionsMenu.advanced">Zaawansowane</str> + <str id="optionsMenu.header.title">Menu Opcji</str> + <str id="optionsMenu.header.desc">Wybierz kategorię aby kontynuować.</str> + + <str id="optionsTree.controls-name">Klawisze</str> + <str id="optionsTree.controls-desc">Zmień klawisze dla Gracza 1 i Gracza 2!</str> + + <str id="optionsTree.gameplay-name">Rozgrywka</str> + <str id="optionsTree.gameplay-desc">Zmień ustawienia rozgrywki takie jak Przewijanie w dół, Prędkość Przewijania, Niegrzeczność...</str> + <str id="optionsTree.gameplay.advanced-desc">Zmień bardziej zaawansowane opcje takie jak Rorzmiar Okna, Strumieniowanie Audio...</str> + + <str id="optionsTree.appearance-name">Wygląd</str> + <str id="optionsTree.appearance-desc">Zmień ustawienia wyglądu takie jak Migające Światła...</str> + + <str id="optionsTree.language-name">Język</str> + <str id="optionsTree.language-desc">Wybierz język którego używa silnik.</str> + + <str id="optionsTree.miscellaneous-name">Różnorodne</str> + <str id="optionsTree.miscellaneous-desc">Użyj tej kategorii aby usunąć zapis lub zmienić ustawienia silnika.</str> + + <!-- cant have debug here --> + </group> + + <group name="KeybindOptions" prefix="KeybindsOptions."> + <str id="left">Lewo</str> + <str id="down">Dół</str> + <str id="up">Góra</str> + <str id="right">Prawo</str> + + <str id="category.notes">Nuty</str> + + <str id="category.ui">Interfejs</str> + <group name="ui" prefix="ui."> + <str id="accept">Akceptuj</str> + <str id="back">Wstecz</str> + <str id="reset">Reset</str> + <str id="pause">Pauza</str> + </group> + + <str id="category.engine">Silnik</str> + <group name="engine" prefix="engine."> + <str id="switchMod">Zmień Moda</str> + </group> + + <str id="category.volume">Głośność</str> + <str id="volume.mute">Wycisz</str> + + <str id="category.developer">Deweloper</str> + <group name="developer" prefix="developer."> + <str id="devMenus">Edytory</str> + <str id="openConsole">Konsola</str> + <str id="reloadState">Odświerz</str> + </group> + </group> + + <group name="AppearanceOptions" prefix="AppearanceOptions."> + <str id="framerate-name">Klatki</str> + <str id="framerate-desc">Ilość Klatek na sekundę ktore gra renderuje</str> + + <str id="antialiasing-name">Wygładzanie</str> + <str id="antialiasing-desc">Jeśli wyłączone, Wygładzanie nie będzie widoczne na Sprite'ach. Może poprawić wydajność na koszt ostrzejszego, pikselowatego wyglądu Sprite'ów.</str> + + <str id="colorHealthBar-name">Kolorowe Zdrowie</str> + <str id="colorHealthBar-desc">Jeśli wyłączone, Pasek zdrowia będzie tylko używał czerwonego i zielonego koloru, takiego jak z oryginalnej gry.</str> + + <str id="week6PixelPerfect-name">Perfekcyjne Piksele</str> + <str id="week6PixelPerfect-desc">Jeśli włączone, Tydzień 6 będzie miał efekt perfekcyjnych pikseli, ustawiający każdy piksel na ekranie.</str> + + <str id="gameplayShaders-name">Shadery Rozgrywki</str> + <str id="gameplayShaders-desc">Jeśli wyłączone, Shadery Rozgrywki (efekty wizualne jak Aberacja Chromatyczna z Thorns) nie będą ładowane; może być to pomocne na słabszych urządzeniach.</str> + + <str id="flashingMenu-name">Migające Menu</str> + <str id="flashingMenu-desc">Jeśli wyłączone, tło menu głównego nie będzie migać kolorów po wybraniu opcji. Inne migania będą znacznie wolniejsze.</str> + + <str id="lowMemoryMode-name">Niskie zużycie pamięci</str> + <str id="lowMemoryMode-desc">Jeśli włączone, usunie niektóre elementy tła, co użyje mniej pamięci.</str> + + <str id="gpuOnlyBitmaps-name">Sprite'y w VRAM</str> + <str id="gpuOnlyBitmaps-desc">Jeśli włączone, utrzyma obrazy tylko w GPU, uwalniając więcej pamięci (EKSPERYMENTALNE). Wyłaczenie tego użyje o wiele więcej pamięci, szczególnie przy większych duszkach. Zostaw włączone w wypadku wątpliwości.</str> + </group> + + <group name="GameplayOptions" prefix="GameplayOptions."> + <str id="downscroll-name">Przewijanie w dół</str> + <str id="downscroll-desc">Jeśli włączone, nuty będą spadać w dół zamiast lecieć w górę.</str> + + <str id="ghostTapping-name">Wolne stukanie</str> + <str id="ghostTapping-desc">Jeśli wyłączone, próba kliknięcia struny która nie ma nuty sprawi chybienie.</str> + + <str id="naughtyness-name">Niegrzeczność</str> + <str id="naughtyness-desc">Jeśli wyłączone, cenzuruje scenki 7 Tygodnia.</str> + + <str id="camZoomOnBeat-name">Bicie Kamery do Bitu</str> + <str id="camZoomOnBeat-desc">Jeśli wyłączone, kamera nie będzie się zbliżać co 4 bity.</str> + + <str id="autoPause-name">Auto Pauza</str> + <str id="autoPause-desc">Jeśli włączone, opuszczając okno gry zatrzyma ją.</str> + + <str id="volumeSFX-name">Głośność SFX</str> + <str id="volumeSFX-desc">Zmienia wyłącznie głośność efektów dzwiękowych.</str> + + <str id="songOffset-name">Opóźnienie Ścieżki</str> + <str id="songOffset-desc">Wyrównuje opóźnienie w jakim ścieżki się zaczną.</str> + + <group name="GameplayOptionsAdvanced" prefix="Advanced."> + <str id="windowSize-name">Rozmiar Okna</str> + <str id="windowSize-desc">...</str> + + <str id="streamedMusic-name">Strumieniowana Muzyka</str> + <str id="streamedMusic-desc">Jeśli włączone, tylko muzyka będzie strumieniować audio, uwalniając więcej pamięci, na możliwy koszt większego użycia CPU. (EKSPERYMENTALNE)</str> +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. + <str id="streamedVocals-name">Strumieniowane Wokale</str> + <str id="streamedVocals-desc">Jeśli włączone, tylko wokale będą strumieniować audio, uwalniając więcej pamięci, na możliwy koszt większego użycia CPU. (EKSPERYMENTALNE)</str> + </group> + </group> + + <group name="LanguageOptions" prefix="LanguageOptions."> + <str id="language-name">Język (Language)</str> + <str id="language-desc">Język stworzony przez: {0} (Wersja: {1})</str> + </group> + + <group name="MiscOptions" prefix="MiscOptions."> + <str id="forceCrash-name">Zmuś Zawieszenie</str> + <str id="forceCrash-desc">Wybierz by zmusić grę do zawieszenia.</str> + + <str id="betaUpdates-name">Nocne Aktualizacje</str> + <str id="betaUpdates-desc">Jeśli włączone, uwzględni nocne budowy gry przy sprawdzaniu aktualizacji.</str> + + <str id="checkForUpdates-name">Sprawdź Aktualizacje</str> + <str id="checkForUpdates-desc">Wybierz by sprawdzić czy nie ma nowszej wersji silnika.</str> + <str id="checkForUpdates-noUpdateFound">Nie znaleziono aktualizacji.</str> + + <str id="devMode-name">Tryb Dewelopera</str> + <str id="devMode-desc">Jeśli włączone, otrzymasz dostęp do funkcji deweloperskich, na przykład niektóre skróty klawiszowe, edytory, konsola, itd.</str> + + <str id="resetSaveData-name">Usuń Zapis Gry</str> + <str id="resetSaveData-desc">Wybierz aby usunąć zapis gry. To usunie wszystkie twoje wyniki.</str> + </group> + + <group name="DebugOptions" prefix="DebugOptions."> + <str id="title">Opcje Debugowania</str> + <str id="desc">Użyj tego menu aby ustawić opcje związane z debugowaniem.</str> + + <str id="showConsole-name">Pokaż Konsolę</str> + <str id="showConsole-desc">Wybierz aby pokazać konsolę gry, która pokazuje zapisy debugowe. (tylko po angielsku.)</str> + + <str id="editorsResizable-name">Zmienny Rozmiar Edytorów</str> + <str id="editorsResizable-desc">Jeśli włączone, pozwoli edytorom na renderowanie poza bazową rozdzielczością 1280x720 oryginalnej gry, pozwalając na więcej detali i miejsca.</str> + + <str id="bypassEditorsResize-name">Objedź Zmienny Rozmiar</str> + <str id="bypassEditorsResize-desc">Pozwala obejść minimalną rodzielczość edytorów (Wymaga aby opcja "Zmienny Rozmiar Edytorów" była włączona).</str> + + <str id="editorSFX-name">Dźwięki Edytorów</str> + <str id="editorSFX-desc">Jeśli włączone, niektóre elementy edytorów będą grać dźwięki. (Na przykład: dźwięki pól wyboru...)</str> + + <str id="editorPrettyPrint-name">Ładny Druk</str> + <str id="editorPrettyPrint-desc">Jeśli włączone, zapisane pliki z edytorów będą używać formatowania łatwiejszego do odczytania. (opcja nie dotyczy plików xml, tylko po angielsku...)</str> + + <str id="intensiveBlur-name">Mocniejszy Rozmaz</str> + <str id="intensiveBlur-desc">Jeśli włączone, gra użyje mocniejszego rozmazu, który kosztuje wydajność, ale wygląda lepiej.</str> + + <str id="charterAutoSaves-name">Autozapisy Edytorów</str> + <str id="charterAutoSaves-desc">Jeśli włączone, edytory automatycznie będą zapisywać twój progres, używając poniższych opcji.</str> + + <str id="charterAutoSaveTime-name">Częstość Autozapisu</str> + <str id="charterAutoSaveTime-desc">Kontroluje co jaki czas edytor będzie automatycznie zapisywać. (w sekundach...)</str> + + <str id="charterAutoSaveWarningTime-name">Czas Ostrzeżenia</str> + <str id="charterAutoSaveWarningTime-desc">Kontroluje czas przed którym edytor cię ostrzeże o automatycznym zapisie. (w sekundach..., 0 by wyłączyć.)</str> + + <str id="charterAutoSavesSeparateFolder-name">Folder Autozapisów</str> + <str id="charterAutoSavesSeparateFolder-desc">Jeśli włączone, Edytor będzie zapisywać pliki w osobnym folderze, zamiast nadpisywania aktualnego pliku. (song/autosaves/)</str> + </group> + +</language> diff --git a/assets/languages/pl/config.ini b/assets/languages/pl/config.ini new file mode 100644 index 00000000..9eb7ec99 --- /dev/null +++ b/assets/languages/pl/config.ini @@ -0,0 +1,9 @@ +name="Polski" +credits="GlitchDotSmh & Kolze" +version="1.0.0" + +; Moving these in the wiki when releasing +; Shows missing ID:s as {id} and prints in the console Missing Translation IDs: id +; otherwhise uses the other languages (based on the chosen order) for the missing translations +; NOTE: it is recommended to use this feature for debug only, but do as you wish +showMissingIds="true" \ No newline at end of file diff --git a/assets/languages/ru/assets/images/menus/titlescreen/titleEnter.png b/assets/languages/ru/assets/images/menus/titlescreen/titleEnter.png new file mode 100644 index 00000000..b6232003 Binary files /dev/null and b/assets/languages/ru/assets/images/menus/titlescreen/titleEnter.png differ diff --git a/assets/languages/ru/assets/images/menus/titlescreen/titleEnter.xml b/assets/languages/ru/assets/images/menus/titlescreen/titleEnter.xml new file mode 100644 index 00000000..eefa1b85 --- /dev/null +++ b/assets/languages/ru/assets/images/menus/titlescreen/titleEnter.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ +--> +<TextureAtlas imagePath="titleEnter.png" width="1258" height="1991"> + <SubTexture name="ENTER PRESSED0001" x="1" y="1" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0002" x="1" y="77" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0003" x="1" y="1" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0004" x="1" y="77" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0005" x="1" y="1" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0006" x="1" y="77" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0007" x="1" y="1" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="ENTER PRESSED0008" x="1" y="77" width="1256" height="73" frameX="-105" frameY="-0" frameWidth="1478" frameHeight="78"/> + <SubTexture name="Press Enter to Begin0001" x="1" y="153" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0002" x="1" y="233" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0003" x="1" y="313" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0004" x="1" y="393" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0005" x="1" y="473" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0006" x="1" y="553" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0007" x="1" y="633" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0008" x="1" y="713" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0009" x="1" y="793" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0010" x="1" y="873" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0011" x="1" y="953" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0012" x="1" y="1033" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0013" x="1" y="1113" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0014" x="1" y="1193" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0015" x="1" y="1273" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0016" x="1" y="1353" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0017" x="1" y="1433" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0018" x="1" y="1513" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0019" x="1" y="1593" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0020" x="1" y="1673" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0021" x="1" y="1753" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0022" x="1" y="1833" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0023" x="1" y="1913" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0024" x="1" y="1913" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0025" x="1" y="1833" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0026" x="1" y="1753" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0027" x="1" y="1673" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0028" x="1" y="1593" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0029" x="1" y="1513" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0030" x="1" y="1433" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0031" x="1" y="1353" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0032" x="1" y="1273" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0033" x="1" y="1193" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0034" x="1" y="1113" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0035" x="1" y="1033" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0036" x="1" y="953" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0037" x="1" y="873" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0038" x="1" y="793" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0039" x="1" y="713" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0040" x="1" y="633" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0041" x="1" y="553" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0042" x="1" y="473" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0043" x="1" y="393" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0044" x="1" y="313" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0045" x="1" y="233" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> + <SubTexture name="Press Enter to Begin0046" x="1" y="153" width="1255" height="77" frameX="-112" frameY="-3" frameWidth="1494" frameHeight="96"/> +</TextureAtlas> \ No newline at end of file diff --git a/assets/music/breakfast.mp3 b/assets/music/breakfast.mp3 index abefad59..e0b4daae 100644 Binary files a/assets/music/breakfast.mp3 and b/assets/music/breakfast.mp3 differ diff --git a/assets/music/breakfast.ogg b/assets/music/breakfast.ogg index fe682396..4481da0c 100644 Binary files a/assets/music/breakfast.ogg and b/assets/music/breakfast.ogg differ diff --git a/assets/music/freakyMenu.mp3 b/assets/music/freakyMenu.mp3 index 8132bfef..6133ccf5 100644 Binary files a/assets/music/freakyMenu.mp3 and b/assets/music/freakyMenu.mp3 differ diff --git a/assets/music/freakyMenu.ogg b/assets/music/freakyMenu.ogg index f41d8284..3357b22e 100644 Binary files a/assets/music/freakyMenu.ogg and b/assets/music/freakyMenu.ogg differ diff --git a/assets/music/gameOver.mp3 b/assets/music/gameOver.mp3 index e8f76e09..f8bd41cb 100644 Binary files a/assets/music/gameOver.mp3 and b/assets/music/gameOver.mp3 differ diff --git a/assets/music/gameOver.ogg b/assets/music/gameOver.ogg index 0c2bd3a4..b515e1a4 100644 Binary files a/assets/music/gameOver.ogg and b/assets/music/gameOver.ogg differ diff --git a/assets/paths.json b/assets/paths.json deleted file mode 100644 index e69de29b..00000000 diff --git a/assets/songs/roses/dialogue.hx b/assets/songs/roses/dialogue.hx index c4329664..24472e61 100644 --- a/assets/songs/roses/dialogue.hx +++ b/assets/songs/roses/dialogue.hx @@ -1,4 +1,5 @@ -function next(first:Bool) { +function next(event:DialogueNextLineEvent) { + var first = event.playFirst; if(first && canProceed) { canProceed = false; dialogueLines[0].playSound.play(); diff --git a/assets/songs/test/song/Inst.mp3 b/assets/songs/test/song/Inst.mp3 index 87d99324..ad220314 100644 Binary files a/assets/songs/test/song/Inst.mp3 and b/assets/songs/test/song/Inst.mp3 differ diff --git a/assets/songs/test/song/Inst.ogg b/assets/songs/test/song/Inst.ogg index 35483ca5..f8b40108 100644 Binary files a/assets/songs/test/song/Inst.ogg and b/assets/songs/test/song/Inst.ogg differ diff --git a/assets/songs/test/song/Voices.mp3 b/assets/songs/test/song/Voices.mp3 index 70ea05e0..5323ab93 100644 Binary files a/assets/songs/test/song/Voices.mp3 and b/assets/songs/test/song/Voices.mp3 differ diff --git a/assets/songs/test/song/Voices.ogg b/assets/songs/test/song/Voices.ogg index eaefa49d..8d19ec35 100644 Binary files a/assets/songs/test/song/Voices.ogg and b/assets/songs/test/song/Voices.ogg differ 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/editors/autosave.ogg b/assets/sounds/editors/autosave.ogg new file mode 100644 index 00000000..e010c369 Binary files /dev/null and b/assets/sounds/editors/autosave.ogg differ diff --git a/assets/sounds/editors/buttonClick.ogg b/assets/sounds/editors/buttonClick.ogg new file mode 100644 index 00000000..9b07779a Binary files /dev/null and b/assets/sounds/editors/buttonClick.ogg differ diff --git a/assets/sounds/editors/character/ghostDisable.ogg b/assets/sounds/editors/character/ghostDisable.ogg new file mode 100644 index 00000000..d33b0ea1 Binary files /dev/null and b/assets/sounds/editors/character/ghostDisable.ogg differ diff --git a/assets/sounds/editors/character/ghostEnable.ogg b/assets/sounds/editors/character/ghostEnable.ogg new file mode 100644 index 00000000..113ae0dd Binary files /dev/null and b/assets/sounds/editors/character/ghostEnable.ogg differ diff --git a/assets/sounds/editors/charter/hitsound.mp3 b/assets/sounds/editors/charter/hitsound.mp3 deleted file mode 100644 index 67cbc251..00000000 Binary files a/assets/sounds/editors/charter/hitsound.mp3 and /dev/null differ diff --git a/assets/sounds/editors/charter/hitsound.ogg b/assets/sounds/editors/charter/hitsound.ogg index c037ff13..e4bf6586 100644 Binary files a/assets/sounds/editors/charter/hitsound.ogg and b/assets/sounds/editors/charter/hitsound.ogg differ diff --git a/assets/sounds/editors/charter/metronome.mp3 b/assets/sounds/editors/charter/metronome.mp3 deleted file mode 100644 index 4a823020..00000000 Binary files a/assets/sounds/editors/charter/metronome.mp3 and /dev/null differ diff --git a/assets/sounds/editors/charter/noteDelete.ogg b/assets/sounds/editors/charter/noteDelete.ogg new file mode 100644 index 00000000..b7c6eb17 Binary files /dev/null and b/assets/sounds/editors/charter/noteDelete.ogg differ diff --git a/assets/sounds/editors/charter/notePlace.ogg b/assets/sounds/editors/charter/notePlace.ogg new file mode 100644 index 00000000..4f4ef1f6 Binary files /dev/null and b/assets/sounds/editors/charter/notePlace.ogg differ diff --git a/assets/sounds/editors/charter/scroll.ogg b/assets/sounds/editors/charter/scroll.ogg new file mode 100644 index 00000000..39642512 Binary files /dev/null and b/assets/sounds/editors/charter/scroll.ogg differ diff --git a/assets/sounds/editors/charter/snappingChange.ogg b/assets/sounds/editors/charter/snappingChange.ogg new file mode 100644 index 00000000..22937aa5 Binary files /dev/null and b/assets/sounds/editors/charter/snappingChange.ogg differ diff --git a/assets/sounds/editors/charter/strumLock.ogg b/assets/sounds/editors/charter/strumLock.ogg new file mode 100644 index 00000000..80bc24ab Binary files /dev/null and b/assets/sounds/editors/charter/strumLock.ogg differ diff --git a/assets/sounds/editors/charter/strumUnlock.ogg b/assets/sounds/editors/charter/strumUnlock.ogg new file mode 100644 index 00000000..860cd7a8 Binary files /dev/null and b/assets/sounds/editors/charter/strumUnlock.ogg differ diff --git a/assets/sounds/editors/charter/sustainAdd.ogg b/assets/sounds/editors/charter/sustainAdd.ogg new file mode 100644 index 00000000..6e40b4a7 Binary files /dev/null and b/assets/sounds/editors/charter/sustainAdd.ogg differ diff --git a/assets/sounds/editors/charter/sustainDelete.ogg b/assets/sounds/editors/charter/sustainDelete.ogg new file mode 100644 index 00000000..4e18b2ad Binary files /dev/null and b/assets/sounds/editors/charter/sustainDelete.ogg differ diff --git a/assets/sounds/editors/click.ogg b/assets/sounds/editors/click.ogg new file mode 100644 index 00000000..29813c8d Binary files /dev/null and b/assets/sounds/editors/click.ogg differ diff --git a/assets/sounds/editors/copy.ogg b/assets/sounds/editors/copy.ogg new file mode 100644 index 00000000..4c220661 Binary files /dev/null and b/assets/sounds/editors/copy.ogg differ diff --git a/assets/sounds/editors/cut.ogg b/assets/sounds/editors/cut.ogg new file mode 100644 index 00000000..eb439166 Binary files /dev/null and b/assets/sounds/editors/cut.ogg differ diff --git a/assets/sounds/editors/delete.ogg b/assets/sounds/editors/delete.ogg new file mode 100644 index 00000000..d1a3c5b1 Binary files /dev/null and b/assets/sounds/editors/delete.ogg differ diff --git a/assets/sounds/editors/dropdownAppear.ogg b/assets/sounds/editors/dropdownAppear.ogg new file mode 100644 index 00000000..689ea982 Binary files /dev/null and b/assets/sounds/editors/dropdownAppear.ogg differ diff --git a/assets/sounds/editors/offsetDrag.ogg b/assets/sounds/editors/offsetDrag.ogg new file mode 100644 index 00000000..3e2349a1 Binary files /dev/null and b/assets/sounds/editors/offsetDrag.ogg differ diff --git a/assets/sounds/editors/paste.ogg b/assets/sounds/editors/paste.ogg new file mode 100644 index 00000000..c78eed35 Binary files /dev/null and b/assets/sounds/editors/paste.ogg differ diff --git a/assets/sounds/editors/redo.ogg b/assets/sounds/editors/redo.ogg new file mode 100644 index 00000000..300540ac Binary files /dev/null and b/assets/sounds/editors/redo.ogg differ diff --git a/assets/sounds/editors/save.ogg b/assets/sounds/editors/save.ogg new file mode 100644 index 00000000..f72f58e7 Binary files /dev/null and b/assets/sounds/editors/save.ogg differ diff --git a/assets/sounds/editors/textRemove.ogg b/assets/sounds/editors/textRemove.ogg new file mode 100644 index 00000000..26e15b3f Binary files /dev/null and b/assets/sounds/editors/textRemove.ogg differ diff --git a/assets/sounds/editors/textType.ogg b/assets/sounds/editors/textType.ogg new file mode 100644 index 00000000..5036e8e4 Binary files /dev/null and b/assets/sounds/editors/textType.ogg differ diff --git a/assets/sounds/editors/undo.ogg b/assets/sounds/editors/undo.ogg new file mode 100644 index 00000000..c4001606 Binary files /dev/null and b/assets/sounds/editors/undo.ogg differ diff --git a/assets/sounds/editors/windowAppear.ogg b/assets/sounds/editors/windowAppear.ogg new file mode 100644 index 00000000..9df7b3ea Binary files /dev/null and b/assets/sounds/editors/windowAppear.ogg differ diff --git a/assets/sounds/editors/windowClose.ogg b/assets/sounds/editors/windowClose.ogg new file mode 100644 index 00000000..36ee3d44 Binary files /dev/null and b/assets/sounds/editors/windowClose.ogg differ diff --git a/assets/sounds/sfxTodo.md b/assets/sounds/sfxTodo.md index 74fdf30e..614245dd 100644 --- a/assets/sounds/sfxTodo.md +++ b/assets/sounds/sfxTodo.md @@ -6,49 +6,47 @@ Putting this txt in here so others can edit and commit this to suggest or add mi - Cass (GlitchDotSmh) [] = Not done -[!] = Priority/In progress +[!] = Priority +[-] = In Progress [x] = Done # Editor SFX ## Global: -- Opening/Closing Context/Dropdown Menus [] -- Opening/Closing Pop-up Windows [] +- Opening/Closing Pop-up Windows [x] - 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 [] +- UI Button being clicked [x] +- Focusing/Unfocusing a text field [x] +- Typing in a text field [x] +- Deleting from text field [x] +- Saving [x] +- Auto-Saving [x] +- Copying [x] +- Pasting [x] +- Cutting [x] +- Undoing [x] +- Redoing [x] +- Deleting (more specifically using "delete" buttons) [x] ## Chart Editor: -- Placing Notes [] -- Deleting Notes [] -- Subtracting Sustain [] -- Adding Sustain [] +- Placing Notes [x] +- Deleting Notes [x] +- Subtracting Sustain [x] +- Adding Sustain [x] - Picking Up Notes [] - Moving Notes [] - Dropping Notes [] - Selecting Notes (via `CTRL + Drag` or `CTRL + A`) [] -- Hitsounds [] +- Hitsounds [x] - 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 [] +- Scrolling Up/Down (via `Scroll Wheel` or `W/S`) [x] +- Note Snapping Change [x] +- Locking Strums [x] +- Unlocking Strums [x] - Creating Strum [] ## Character/Stage Editor -- Selecting a Stage asset [] -- Switching Stage [] -- Changing Offset (via `Arrows`, `WASD`, or `Mouse Drag`) [] -- Enabling/Disabling Ghost Animation [] +- Changing Offset (via `Arrows`, `WASD`, or `Mouse Drag`) [x] +- Enabling/Disabling Ghost Animation [x] diff --git a/commandline/commands/Setup.hx b/commandline/commands/Setup.hx index 2ddac7eb..7d4255f6 100644 --- a/commandline/commands/Setup.hx +++ b/commandline/commands/Setup.hx @@ -73,7 +73,7 @@ class Setup { function handleLib(libNode:Access) { switch(libNode.name) { - case "lib" | "git": + case "lib" | "git" | "custom": final lib:Library = { name: libNode.att.name, type: libNode.name, @@ -191,6 +191,8 @@ class Setup { } else { command('haxelib$commandPrefix git ${lib.name} ${lib.url}${lib.ref != null ? ' ${lib.ref}' : ''}'); } + case "custom": + command('haxelib$commandPrefix dev ${lib.name} "./libraries/${lib.name}"'); default: prettyPrint('Cannot resolve library of type "${lib.type}"'); } diff --git a/libs.xml b/libs.xml index e4247aca..403f646d 100644 --- a/libs.xml +++ b/libs.xml @@ -16,8 +16,8 @@ <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" /> - <lib name="funkin-modchart" /> <!-- Documentation and other features --> <git name="away3d" url="https://github.com/CodenameCrew/away3d" /> diff --git a/project.xml b/project.xml index bb7a17cf..df54e556 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="1.0.0" company="Yoshman29" /> <!-- CHANGE THE SAVE PATH & NAME FOR YOUR MOD HERE! @@ -113,7 +113,7 @@ </section> <!-- Uncomment this to achive faster compile times, will strip extraneous and commonly unused features. --> - <define name="STRIPPED_COMPILE" unless="debug"/> + <!-- <define name="STRIPPED_COMPILE" unless="debug"/> --> <!-- Comment this out to disable modcharting features --> <define name="MODCHARTING_FEATURES" unless="STRIPPED_COMPILE" /> @@ -135,6 +135,9 @@ <!-- Comment this out to disable mods folder !--> <haxedef name="MOD_SUPPORT" unless="web"/> + <!-- Comment this out to disable translations folders !--> + <haxedef name="TRANSLATIONS_SUPPORT"/> + <!-- Comment this out to disable global scripts !--> <haxedef name="GLOBAL_SCRIPT" /> @@ -211,7 +214,7 @@ <haxedef name="FM_ENGINE_VERSION" value="1.0"/> <haxelib name="funkin-modchart" /> - <haxeflag name="--macro" value="modchart.core.macros.Macro.includeFiles()"/> + <haxeflag name="--macro" value="modchart.backend.macros.Macro.includeFiles()"/> </section> <section if="COMPILE_ALL_CLASSES"> @@ -232,6 +235,5 @@ <haxedef name="hscriptPos" /> <!-- for logging --> <!-- EXPERMENTAL FEATURES --> - <!-- <haxedef name="EXPERMENTAL_SCRIPT_RELOADING" /> --> <haxedef name="EXPERIMENTAL_FLXGRAPHIC_DESTROY_FIX" /> </project> diff --git a/setup-dev.xml b/setup-dev.xml index e68ef224..09bc60dd 100644 --- a/setup-dev.xml +++ b/setup-dev.xml @@ -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> diff --git a/source/flixel/sound/FlxSound.hx b/source/flixel/sound/FlxSound.hx new file mode 100644 index 00000000..146cdf1a --- /dev/null +++ b/source/flixel/sound/FlxSound.hx @@ -0,0 +1,916 @@ +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(); + onFinish.removeAll(); + + 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) { + 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 { + _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/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 ff00f55b..90ac68c9 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -85,25 +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.zoomFactor = source.zoomFactor; - 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; } @@ -148,7 +157,6 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I 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") diff --git a/source/funkin/backend/MusicBeatState.hx b/source/funkin/backend/MusicBeatState.hx index 5ff8b6df..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 @@ -153,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(); @@ -223,12 +229,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver override function update(elapsed:Float) { - // TODO: DEBUG MODE!! - #if EXPERMENTAL_SCRIPT_RELOADING - if (FlxG.keys.justPressed.F5) { - loadScript(); - } - #end call("update", [elapsed]); super.update(elapsed); diff --git a/source/funkin/backend/MusicBeatSubstate.hx b/source/funkin/backend/MusicBeatSubstate.hx index 6cb0d33a..5ad900d4 100644 --- a/source/funkin/backend/MusicBeatSubstate.hx +++ b/source/funkin/backend/MusicBeatSubstate.hx @@ -136,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() { @@ -181,12 +185,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver override function update(elapsed:Float) { - #if EXPERMENTAL_SCRIPT_RELOADING - // TODO: DEBUG MODE!! - if (FlxG.keys.justPressed.F5) { - loadScript(); - } - #end call("update", [elapsed]); super.update(elapsed); } diff --git a/source/funkin/backend/assets/AssetSource.hx b/source/funkin/backend/assets/AssetSource.hx new file mode 100644 index 00000000..969c6b42 --- /dev/null +++ b/source/funkin/backend/assets/AssetSource.hx @@ -0,0 +1,40 @@ +package funkin.backend.assets; + +enum abstract AssetSource(Null<Int>)/* from Null<Bool>*/ from Null<Int> to Null<Int> { + var SOURCE = 0; + var MODS = 1; + var BOTH = -1; + + @:from public static function fromString(str:String):AssetSource + { + return switch (StringTools.trim(str).toLowerCase()) + { + case "source": SOURCE; + case "mods": MODS; + case "both": BOTH; + default: MODS; + } + } + + /*@:from public static function fromBool(b:Null<Bool>):AssetSource + { + return switch (b) + { + case true: SOURCE; + case false: MODS; + case null: BOTH; + default: MODS; + } + }*/ + + @:to public inline function toString():String + { + return switch (this) + { + case SOURCE: "source"; + case MODS: "mods"; + case BOTH: "both"; + default: "mods"; + } + } +} \ No newline at end of file diff --git a/source/funkin/backend/assets/AssetsLibraryList.hx b/source/funkin/backend/assets/AssetsLibraryList.hx index d7ff2737..d031ae2c 100644 --- a/source/funkin/backend/assets/AssetsLibraryList.hx +++ b/source/funkin/backend/assets/AssetsLibraryList.hx @@ -1,5 +1,8 @@ package funkin.backend.assets; +#if TRANSLATIONS_SUPPORT +import funkin.backend.assets.TranslatedAssetLibrary; +#end import funkin.backend.assets.IModsAssetLibrary; import lime.utils.AssetLibrary; @@ -11,18 +14,38 @@ class AssetsLibraryList extends AssetLibrary { private var __defaultLibraries:Array<AssetLibrary> = []; public var base:AssetLibrary; + #if TRANSLATIONS_SUPPORT + public var transLib:TranslatedAssetLibrary; + #end + public function removeLibrary(lib:AssetLibrary) { - if (lib == null) return lib; - libraries.remove(lib); + if (lib != null) { + libraries.remove(lib); + #if TRANSLATIONS_SUPPORT + // TODO: improve this code + for(k=>l in libraries) { + if(l == null) continue; + if(l is TranslatedAssetLibrary) { + var tlib = cast(l, TranslatedAssetLibrary); + var lib:Dynamic = lib; + if(tlib.forLibrary == lib) { + libraries.remove(tlib); + break; + } + } + } + #end + } return lib; } public function existsSpecific(id:String, type:String, source:AssetSource = BOTH) { - if (!id.startsWith("assets/") && exists('assets/$id', type)) + if (!id.startsWith("assets/") && existsSpecific('assets/$id', type, source)) return true; - for(k=>e in libraries) { - if (shouldSkipLib(k, source)) continue; - if (e.exists(id, type)) + for(k=>l in libraries) { + if (shouldSkipLib(l, source)) continue; + if (l.exists(id, type)) { return true; + } } return false; } @@ -31,7 +54,7 @@ class AssetsLibraryList extends AssetLibrary { public function getSpecificPath(id:String, source:AssetSource = BOTH) { for(k=>e in libraries) { - if (shouldSkipLib(k, source)) continue; + if (shouldSkipLib(e, source)) continue; @:privateAccess if (e.exists(id, e.types.get(id))) { @@ -48,22 +71,17 @@ class AssetsLibraryList extends AssetLibrary { public function getFiles(folder:String, source:AssetSource = BOTH):Array<String> { var content:Array<String> = []; - for(k=>e in libraries) { - if (shouldSkipLib(k, source)) continue; + for(k=>l in libraries) { + if (shouldSkipLib(l, source)) continue; - var l = e; - - if (l is openfl.utils.AssetLibrary) { - @:privateAccess - l = cast(l, openfl.utils.AssetLibrary).__proxy; - } + l = getCleanLibrary(l); // TODO: do base folder scanning #if MOD_SUPPORT if (l is IModsAssetLibrary) { var lib = cast(l, IModsAssetLibrary); for(e in lib.getFiles(folder)) - content.push(e); + content.pushOnce(e); } #end } @@ -72,15 +90,10 @@ class AssetsLibraryList extends AssetLibrary { public function getFolders(folder:String, source:AssetSource = BOTH):Array<String> { var content:Array<String> = []; - for(k=>e in libraries) { - if (shouldSkipLib(k, source)) continue; + for(k=>l in libraries) { + if (shouldSkipLib(l, source)) continue; - var l = e; - - if (l is openfl.utils.AssetLibrary) { - @:privateAccess - l = cast(l, openfl.utils.AssetLibrary).__proxy; - } + l = getCleanLibrary(l); // TODO: do base folder scanning #if MOD_SUPPORT @@ -102,12 +115,12 @@ class AssetsLibraryList extends AssetLibrary { return ass; } } - for(k=>e in libraries) { - if (shouldSkipLib(k, source)) continue; + for(k=>l in libraries) { + if (shouldSkipLib(l, source)) continue; @:privateAccess - if (e.exists(id, e.types.get(id))) { - var asset = e.getAsset(id, type); + if (l.exists(id, l.types.get(id))) { + var asset = l.getAsset(id, type); if (asset != null) { return asset; } @@ -115,17 +128,15 @@ class AssetsLibraryList extends AssetLibrary { } return null; } catch(e) { + // TODO: trace the error throw e; } return null; } - private function shouldSkipLib(k:Int, source:AssetSource) { - return switch(source) { - case BOTH: false; - case SOURCE: k < libraries.length - __defaultLibraries.length; - case MODS: k >= libraries.length - __defaultLibraries.length; - }; + private function shouldSkipLib(lib:AssetLibrary, source:AssetSource) { + if (source == BOTH || lib.tag == BOTH) return false; + return source != lib.tag; } public override inline function getAsset(id:String, type:String):Dynamic return getSpecificAsset(id, type, BOTH); @@ -136,10 +147,7 @@ class AssetsLibraryList extends AssetLibrary { public function new(?base:AssetLibrary) { super(); - if (base == null) - base = Assets.getLibrary("default"); - addLibrary(this.base = base); - __defaultLibraries.push(base); + __defaultLibraries.push(addLibrary(this.base = (base == null ? Assets.getLibrary("default") : base), SOURCE)); } public function unloadLibraries() { @@ -158,14 +166,35 @@ class AssetsLibraryList extends AssetLibrary { addLibrary(d); } - public function addLibrary(lib:AssetLibrary) { + public function addLibrary(lib:AssetLibrary, ?tag:AssetSource, ?addTransLib:Bool = true) { libraries.insert(0, lib); + if(tag != null) + lib.tag = tag; + + if(lib.tag == null) { // if tag is null, set it to MODS, so it doesnt set the tag on the libs that exists + lib.tag = MODS; + //trace('AssetLibrary ${getCleanLibrary(lib)} tag not set, defaulting to MODS'); + } + #if TRANSLATIONS_SUPPORT + if(addTransLib) { + var cleanLib = getCleanLibrary(lib); + if(cleanLib != null && (cleanLib is IModsAssetLibrary)) { + var transLib = new TranslatedAssetLibrary(cast(cleanLib, IModsAssetLibrary)); + transLib.tag = cleanLib.tag; + libraries.insert(0, transLib); + } + } + #end return lib; } -} -enum abstract AssetSource(Null<Bool>) from Bool from Null<Bool> to Null<Bool> { - var SOURCE = true; - var MODS = false; - var BOTH = null; + public static function getCleanLibrary(e:AssetLibrary):AssetLibrary { + var l = e; + if (l is openfl.utils.AssetLibrary) { + var al = cast(l, openfl.utils.AssetLibrary); + @:privateAccess + if (al.__proxy != null) l = al.__proxy; + } + return l; + } } \ No newline at end of file diff --git a/source/funkin/backend/assets/IModsAssetLibrary.hx b/source/funkin/backend/assets/IModsAssetLibrary.hx index 809a2143..b86ca7ce 100644 --- a/source/funkin/backend/assets/IModsAssetLibrary.hx +++ b/source/funkin/backend/assets/IModsAssetLibrary.hx @@ -1,11 +1,10 @@ package funkin.backend.assets; -using StringTools; - interface IModsAssetLibrary { public var prefix:String; public var modName:String; public var libName:String; + public var basePath:String; #if MOD_SUPPORT public var _parsedAsset:String; diff --git a/source/funkin/backend/assets/ModdableAssetsFolder.hx b/source/funkin/backend/assets/ModdableAssetsFolder.hx deleted file mode 100644 index c9ca2e16..00000000 --- a/source/funkin/backend/assets/ModdableAssetsFolder.hx +++ /dev/null @@ -1,41 +0,0 @@ -package funkin.backend.assets; - -import lime.utils.AssetLibrary; - -#if sys -/** - * Used to prevent crashes - */ -class ModdableAssetsFolder extends ModsFolderLibrary { - public var oldLibrary:AssetLibrary; - - public override function exists(id:String, type:String) { - if (!id.startsWith("assets/") && !exists('assets/$id', type)) - return oldLibrary.exists(id, type); - return super.exists(id, type); - } - - public override function getAsset(id:String, type:String):Dynamic { - if (!id.startsWith("assets/")) { - var possibleReplacement = getAsset('assets/$id', type); - if (possibleReplacement != null) - return possibleReplacement; - return oldLibrary.getAsset(id, type); - } - return super.getAsset(id, type); - } - - public function new(folder:String, libName:String, oldLib:AssetLibrary) { - super(folder, libName); - oldLibrary = oldLib; - trace(Type.getClassName(Type.getClass(oldLibrary))); - } - - private override function __parseAsset(asset:String):Bool { - var prefix = 'assets/'; - if (!asset.startsWith(prefix)) return false; - _parsedAsset = asset.substr(prefix.length); - return true; - } -} -#end \ No newline at end of file diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx index cd5cf515..81460a99 100644 --- a/source/funkin/backend/assets/ModsFolder.hx +++ b/source/funkin/backend/assets/ModsFolder.hx @@ -8,11 +8,11 @@ import openfl.text.Font as OpenFLFont; import openfl.utils.AssetLibrary; import openfl.utils.AssetManifest; +using StringTools; #if MOD_SUPPORT import sys.FileSystem; #end -using StringTools; class ModsFolder { /** @@ -47,8 +47,11 @@ class ModsFolder { * Initializes `mods` folder. */ public static function init() { - if(!getModsList().contains(Options.lastLoadedMod)) + if(!getModsList().contains(Options.lastLoadedMod)) { + if(Options.lastLoadedMod != null) + Logs.warn("Mod \"" + Options.lastLoadedMod + "\" not found in mods list, switching to base game!"); Options.lastLoadedMod = null; + } } /** @@ -58,6 +61,13 @@ class ModsFolder { public static function switchMod(mod:String) { Options.lastLoadedMod = currentModFolder = mod; reloadMods(); + if(mod == null) { + mod = "(default)"; + } + Logs.traceColored([ + Logs.logText('Switched to mod: '), + Logs.logText(mod, GREEN) + ], VERBOSE); } public static function reloadMods() { @@ -86,6 +96,11 @@ 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) @@ -106,22 +121,20 @@ class ModsFolder { #end return mods; } - public static function getLoadedMods():Array<String> { + public static function getLoadedModsLibs(skipTranslated:Bool = false):Array<IModsAssetLibrary> { var libs = []; for (i in Paths.assetsTree.libraries) { - var l = i; - if (l is openfl.utils.AssetLibrary) { - var al = cast(l, openfl.utils.AssetLibrary); - @:privateAccess - if (al.__proxy != null) l = al.__proxy; - } - var libString:String; - if (l is ScriptedAssetLibrary || l is IModsAssetLibrary) libString = cast(l, IModsAssetLibrary).modName; - else continue; - libs.push(libString); + var l = AssetsLibraryList.getCleanLibrary(i); + #if TRANSLATIONS_SUPPORT + if(skipTranslated && (l is TranslatedAssetLibrary)) continue; + #end + if (l is ScriptedAssetLibrary || l is IModsAssetLibrary) libs.push(cast(l, IModsAssetLibrary)); } return libs; } + public static function getLoadedMods(skipTranslated:Bool = false):Array<String> + return [for (modLib in getLoadedModsLibs(skipTranslated)) modLib.modName]; + public static function prepareLibrary(libName:String, force:Bool = false) { var assets:AssetManifest = new AssetManifest(); assets.name = libName; @@ -140,21 +153,25 @@ class ModsFolder { return font; } - public static function prepareModLibrary(libName:String, lib:IModsAssetLibrary, force:Bool = false) { + public static function prepareModLibrary(libName:String, lib:IModsAssetLibrary, force:Bool = false, ?tag:AssetSource = MODS) { var openLib = prepareLibrary(libName, force); lib.prefix = 'assets/'; @:privateAccess openLib.__proxy = cast(lib, lime.utils.AssetLibrary); + if(tag != null) { + openLib.tag = tag; + cast(lib, lime.utils.AssetLibrary).tag = tag; + } return openLib; } #if MOD_SUPPORT - public static function loadLibraryFromFolder(libName:String, folder:String, force:Bool = false, ?modName:String) { - return prepareModLibrary(libName, new ModsFolderLibrary(folder, libName, modName), force); + public static function loadLibraryFromFolder(libName:String, folder:String, force:Bool = false, ?modName:String, ?tag:AssetSource = MODS) { + return prepareModLibrary(libName, new ModsFolderLibrary(folder, libName, modName), force, tag); } - public static function loadLibraryFromZip(libName:String, zipPath:String, force:Bool = false, ?modName:String) { - return prepareModLibrary(libName, new ZipFolderLibrary(zipPath, libName, modName), force); + public static function loadLibraryFromZip(libName:String, zipPath:String, force:Bool = false, ?modName:String, ?tag:AssetSource = MODS) { + return prepareModLibrary(libName, new ZipFolderLibrary(zipPath, libName, modName), force, tag); } #end } diff --git a/source/funkin/backend/assets/ModsFolderLibrary.hx b/source/funkin/backend/assets/ModsFolderLibrary.hx index c30b7270..43db98e7 100644 --- a/source/funkin/backend/assets/ModsFolderLibrary.hx +++ b/source/funkin/backend/assets/ModsFolderLibrary.hx @@ -1,10 +1,9 @@ package funkin.backend.assets; -import lime.graphics.Image; +import openfl.utils.AssetLibrary; import lime.media.AudioBuffer; +import lime.graphics.Image; import lime.text.Font; -import lime.utils.AssetLibrary; -import lime.utils.Assets as LimeAssets; import lime.utils.Bytes; #if MOD_SUPPORT @@ -15,23 +14,24 @@ import sys.FileSystem; using StringTools; class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary { - public var folderPath:String; + public var basePath:String; public var modName:String; public var libName:String; //public var useImageCache:Bool = true; public var prefix = 'assets/'; - public function new(folderPath:String, libName:String, ?modName:String) { - this.folderPath = folderPath; + public function new(basePath:String, libName:String, ?modName:String) { + this.basePath = basePath; this.libName = libName; this.prefix = 'assets/$libName/'; - if(modName == null) - this.modName = libName; - else - this.modName = modName; + this.modName = modName == null ? libName : modName; super(); } + function toString():String { + return '(ModsFolderLibrary: $modName)'; + } + #if MOD_SUPPORT private var editedTimes:Map<String, Float> = []; public var _parsedAsset:String = null; @@ -110,22 +110,23 @@ class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary { } private function getAssetPath() { - return '$folderPath/$_parsedAsset'; + return '$basePath/$_parsedAsset'; } private function __isCacheValid(cache:Map<String, Dynamic>, asset:String, isLocalCache:Bool = false) { if (!editedTimes.exists(asset)) return false; - if (editedTimes[asset] == null || editedTimes[asset] < FileSystem.stat(getPath(asset)).mtime.getTime()) { + var editedTime = editedTimes[asset]; + if (editedTime == null || editedTime < FileSystem.stat(getPath(asset)).mtime.getTime()) { // destroy already existing to prevent memory leak!!! - var asset = cache[asset]; + /*var asset = cache[asset]; if (asset != null) { switch(Type.getClass(asset)) { case lime.graphics.Image: trace("getting rid of image cause replaced"); cast(asset, lime.graphics.Image); } - } + }*/ return false; } @@ -149,4 +150,14 @@ class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary { return true; } #end + + // Backwards compat + + @:noCompletion public var folderPath(get, set):String; + @:noCompletion private inline function get_folderPath():String { + return basePath; + } + @:noCompletion private inline function set_folderPath(value:String):String { + return basePath = value; + } } \ No newline at end of file diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 227edb89..9a2b9a97 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -32,37 +32,37 @@ class Paths public static inline function ndll(key:String) return getPath('ndlls/$key.ndll'); - inline static public function file(file:String, ?library:String) + public static inline function file(file:String, ?library:String) return getPath(file, library); - inline static public function txt(key:String, ?library:String) + public static inline function txt(key:String, ?library:String) return getPath('data/$key.txt', library); - inline static public function pack(key:String, ?library:String) + public static inline function pack(key:String, ?library:String) return getPath('data/$key.pack', library); - inline static public function ini(key:String, ?library:String) + public static inline function ini(key:String, ?library:String) return getPath('data/$key.ini', library); - inline static public function fragShader(key:String, ?library:String) + public static inline function fragShader(key:String, ?library:String) return getPath('shaders/$key.frag', library); - inline static public function vertShader(key:String, ?library:String) + public static inline function vertShader(key:String, ?library:String) return getPath('shaders/$key.vert', library); - inline static public function xml(key:String, ?library:String) + public static inline function xml(key:String, ?library:String) return getPath('data/$key.xml', library); - inline static public function json(key:String, ?library:String) + public static inline function json(key:String, ?library:String) return getPath('data/$key.json', library); - inline static public function ps1(key:String, ?library:String) + public static inline function ps1(key:String, ?library:String) return getPath('data/$key.ps1', library); static public function sound(key:String, ?library:String, ?ext:String) return getPath('sounds/$key.${ext != null ? ext : Flags.SOUND_EXT}', library); - inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String) + public static inline function soundRandom(key:String, min:Int, max:Int, ?library:String) return sound(key + FlxG.random.int(min, max), library); inline static public function music(key:String, ?library:String, ?ext:String) @@ -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); } @@ -95,7 +93,7 @@ class Paths return getPath('images/$key.$ext', library); } - static public function script(key:String, ?library:String, isAssetsPath:Bool = false) { + public static inline function script(key:String, ?library:String, isAssetsPath:Bool = false) { var scriptPath = isAssetsPath ? key : getPath(key, library); if (!OpenFlAssets.exists(scriptPath)) { var p:String; @@ -111,14 +109,12 @@ 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); } - inline static public function character(character:String):String - { + public static function character(character:String):String { return getPath('data/characters/$character.xml', null); } @@ -126,33 +122,31 @@ class Paths * Gets the name of a registered font. * @param font The font's path (if it's already passed as a font name, the same name will be returned) */ - inline static public function getFontName(font:String) - { + inline static public function getFontName(font:String) { return OpenFlAssets.exists(font, FONT) ? OpenFlAssets.getFont(font).fontName : font; } - inline static public function font(key:String) - { + public static inline function font(key:String) { return getPath('fonts/$key'); } - inline static public function obj(key:String) { + public static inline function obj(key:String) { return getPath('models/$key.obj'); } - inline static public function dae(key:String) { + public static inline function dae(key:String) { return getPath('models/$key.dae'); } - inline static public function md2(key:String) { + public static inline function md2(key:String) { return getPath('models/$key.md2'); } - inline static public function md5(key:String) { + public static inline function md5(key:String) { return getPath('models/$key.md5'); } - inline static public function awd(key:String) { + public static inline function awd(key:String) { return getPath('models/$key.awd'); } @@ -193,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. @@ -238,7 +255,8 @@ class Paths return null; return graph.imageFrame; } - static public function getFolderDirectories(key:String, addPath:Bool = false, source:AssetsLibraryList.AssetSource = BOTH):Array<String> { + + public static function getFolderDirectories(key:String, addPath:Bool = false, source:AssetSource = BOTH):Array<String> { if (!key.endsWith("/")) key += "/"; var content = assetsTree.getFolders('assets/$key', source); if (addPath) { @@ -247,13 +265,13 @@ class Paths } return content; } - static public function getFolderContent(key:String, addPath:Bool = false, source:AssetsLibraryList.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/assets/ScriptedAssetLibrary.hx b/source/funkin/backend/assets/ScriptedAssetLibrary.hx index e0fd2e33..e7ca6721 100644 --- a/source/funkin/backend/assets/ScriptedAssetLibrary.hx +++ b/source/funkin/backend/assets/ScriptedAssetLibrary.hx @@ -20,9 +20,9 @@ class ScriptedAssetLibrary extends ModsFolderLibrary { public var scriptName:String; private static var nullValue:Dynamic = {}; - public function new(scriptName:String, args:Array<Dynamic> = null, folderPath:String="./assets/", libName:String="assets", ?modName:String) { + public function new(scriptName:String, args:Array<Dynamic> = null, basePath:String="./assets/", libName:String="assets", ?modName:String) { if(modName == null) modName = scriptName; - super(folderPath, libName, modName); + super(basePath, libName, modName); this.scriptName = scriptName; script = Script.create(Paths.script("data/library/" + scriptName)); script.setParent(this); @@ -30,7 +30,10 @@ class ScriptedAssetLibrary extends ModsFolderLibrary { script.load(); if(args == null) args = []; script.call("create", args); - trace(script); + } + + override function toString():String { + return '(ScriptedAssetLibrary: $libName/$modName/$scriptName)'; } #if MOD_SUPPORT diff --git a/source/funkin/backend/assets/TranslatedAssetLibrary.hx b/source/funkin/backend/assets/TranslatedAssetLibrary.hx new file mode 100644 index 00000000..a90da269 --- /dev/null +++ b/source/funkin/backend/assets/TranslatedAssetLibrary.hx @@ -0,0 +1,172 @@ +package funkin.backend.assets; + +import lime.utils.AssetLibrary as LimeAssetLibrary; +import openfl.utils.AssetLibrary; +import lime.media.AudioBuffer; +import lime.graphics.Image; +import lime.text.Font; +import lime.utils.Bytes; + +class TranslatedAssetLibrary extends AssetLibrary implements IModsAssetLibrary { + public var libName:String; + public var modName:String; + public var basePath:String; + public var prefix:String = TranslationUtil.LANG_FOLDER + "/"; + + public var forLibrary:IModsAssetLibrary; + + public var langFolder(get, set):String; + @:noCompletion private inline function get_langFolder():String { + return libName; + } + @:noCompletion private inline function set_langFolder(value:String):String { + basePath = prefix + (libName = modName = (value != null ? value : Flags.DEFAULT_LANGUAGE)) + "/"; + return libName; + } + + public function new(lib:IModsAssetLibrary, ?langFolder:String) { + super(); + this.forLibrary = lib; + this.langFolder = langFolder; + } + + function toString():String + return '(TranslatedAssetLibrary: Lang: $libName | For: ${forLibrary})'; + + private inline function getAssetPath():String // because of the IModsAssetLibrary - Nex + return basePath; + + public inline function formatPath(mainPath:String, ?asset:String):String { + if(!mainPath.endsWith('/')) mainPath += '/'; + return mainPath + getAssetPath() + (asset == null ? "" : asset); + } + + public override function getAudioBuffer(id:String):AudioBuffer + { + // TODO: rewrite this, once it works + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = cast(lib, AssetLibrary).getAudioBuffer(formatPath(lib.prefix, id)); + if(val != null) return val; + } + return null; + } + + public override function getBytes(id:String):Bytes + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = cast(lib, AssetLibrary).getBytes(formatPath(lib.prefix, id)); + if(val != null) return val; + } + return null; + } + + public override function getText(id:String):String + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = cast(lib, AssetLibrary).getText(formatPath(lib.prefix, id)); + if(val != null) return val; + } + return null; + } + + public override function getFont(id:String):Font + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = cast(lib, AssetLibrary).getFont(formatPath(lib.prefix, id)); + if(val != null) return val; + } + return null; + } + + public override function getImage(id:String):Image + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = cast(lib, AssetLibrary).getImage(formatPath(lib.prefix, id)); + if(val != null) return val; + } + return null; + } + + public override function getPath(id:String):String + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = cast(lib, AssetLibrary).getPath(formatPath(lib.prefix, id)); + if(val != null) return val; + } + return null; + } + + #if MOD_SUPPORT + public var _parsedAsset:String = null; // Theres no need to actually make this work - Nex + + public function getFiles(folder:String):Array<String> + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = lib.getFiles(formatPath(lib.prefix, folder)); + if(val != null && val.length > 0) return val; + } + return []; + } + + public function getFolders(folder:String):Array<String> + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + var val = lib.getFolders(formatPath(lib.prefix, folder)); + if(val != null && val.length > 0) return val; + } + return []; + } + + private function __isCacheValid(cache:Map<String, Dynamic>, asset:String, isLocal:Bool = false):Bool + { + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + + // are you fucking serious (no the fucking switch doesnt work here) - Nex + var _lib = cast(lib, AssetLibrary); + var libCache = (cache == cachedAudioBuffers) ? _lib.cachedAudioBuffers : + (cache == cachedBytes) ? _lib.cachedBytes : + (cache == cachedFonts) ? _lib.cachedFonts : + (cache == cachedImages) ? _lib.cachedImages : + (cache == cachedText) ? _lib.cachedText : cache; + + @:privateAccess if(lib.__isCacheValid(libCache, formatPath(lib.prefix, asset), isLocal)) return true; + } + return false; + } + + private function __parseAsset(asset:String):Bool + { + @:privateAccess + var libs = [forLibrary]; + for(lib in libs) { + if(!(lib is AssetLibrary)) continue; + if(lib.__parseAsset(formatPath(lib.prefix, asset))) return true; + } + return false; + } + #end + + public override function exists(id:String, type:String):Bool + { + for(lib in ModsFolder.getLoadedModsLibs(true)) if(lib is AssetLibrary && cast(lib, AssetLibrary).exists(formatPath(lib.prefix, id), type)) return true; + return false; + } +} \ No newline at end of file diff --git a/source/funkin/backend/assets/ZipFolderLibrary.hx b/source/funkin/backend/assets/ZipFolderLibrary.hx index f213403e..ea4dab25 100644 --- a/source/funkin/backend/assets/ZipFolderLibrary.hx +++ b/source/funkin/backend/assets/ZipFolderLibrary.hx @@ -1,29 +1,18 @@ package funkin.backend.assets; +import haxe.io.Path; +import lime.graphics.Image; +import lime.media.AudioBuffer; +import lime.text.Font; +import lime.utils.Bytes; +import openfl.utils.AssetLibrary; + #if MOD_SUPPORT import funkin.backend.utils.SysZip.SysZipEntry; import funkin.backend.utils.SysZip; -import haxe.io.Path; -import haxe.zip.Reader; -import lime.app.Event; -import lime.app.Future; -import lime.app.Promise; -import lime.graphics.Image; -import lime.media.AudioBuffer; -import lime.text.Font; -import lime.utils.AssetLibrary; -import lime.utils.AssetManifest; -import lime.utils.AssetType; -import lime.utils.Assets as LimeAssets; -import lime.utils.Bytes; -import lime.utils.Log; -import openfl.text.Font as OpenFLFont; -import sys.FileStat; -import sys.FileSystem; -import sys.io.File; class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { - public var zipPath:String; + public var basePath:String; public var modName:String; public var libName:String; public var useImageCache:Bool = false; @@ -34,16 +23,17 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { public var lowerCaseAssets:Map<String, SysZipEntry> = []; public var nameMap:Map<String, String> = []; - public function new(zipPath:String, libName:String, ?modName:String) { - this.zipPath = zipPath; + public function new(basePath:String, libName:String, ?modName:String) { this.libName = libName; + this.basePath = basePath; + if(modName == null) this.modName = libName; else this.modName = modName; - zip = SysZip.openFromFile(zipPath); + zip = SysZip.openFromFile(basePath); zip.read(); for(entry in zip.entries) { lowerCaseAssets[entry.fileName.toLowerCase()] = assets[entry.fileName.toLowerCase()] = assets[entry.fileName] = entry; @@ -53,6 +43,10 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { super(); } + function toString():String { + return '(ZipFolderLibrary: $libName/$modName)'; + } + public var _parsedAsset:String; public override function getAudioBuffer(id:String):AudioBuffer { @@ -113,8 +107,8 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { } private function getAssetPath() { - trace('[ZIP]$zipPath/$_parsedAsset'); - return '[ZIP]$zipPath/$_parsedAsset'; + trace('[ZIP]$basePath/$_parsedAsset'); + return '[ZIP]$basePath/$_parsedAsset'; } // TODO: rewrite this to 1 function, like ModsFolderLibrary @@ -162,5 +156,15 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary { } return content; } + + // Backwards compat + + @:noCompletion public var zipPath(get, set):String; + @:noCompletion private inline function get_zipPath():String { + return basePath; + } + @:noCompletion private inline function set_zipPath(value:String):String { + return basePath = value; + } } #end \ No newline at end of file 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 dace2cfd..1adcb9a4 100644 --- a/source/funkin/backend/chart/Chart.hx +++ b/source/funkin/backend/chart/Chart.hx @@ -72,7 +72,7 @@ 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 { @@ -83,31 +83,29 @@ class Chart { 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.trace('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}', ERROR); - } - 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); @@ -118,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; } } @@ -161,15 +154,13 @@ class Chart { var valid:Bool = true; if (!Assets.exists(chartPath)) { - Logs.trace('Chart for song ${songName} ($difficulty) at "$chartPath" was not found.', ERROR, RED); + Logs.error('Chart for song ${songName} ($difficulty) at "$chartPath" was not found.'); valid = false; } var data:Dynamic = null; - try { - if (valid) - data = Json.parse(Assets.getText(chartPath)); - } catch(e) { - Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED); + 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); } /** @@ -178,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", @@ -196,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 @@ -238,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; } @@ -275,9 +265,8 @@ 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; } @@ -306,13 +295,19 @@ class Chart { 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 = { diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx index 399b9ee2..ba1b7ae7 100644 --- a/source/funkin/backend/chart/ChartData.hx +++ b/source/funkin/backend/chart/ChartData.hx @@ -10,6 +10,7 @@ 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; @@ -22,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 = { @@ -57,6 +54,12 @@ 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; diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index e19523ed..9ef2e4f2 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -9,21 +9,76 @@ import openfl.Assets; using StringTools; -final 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"]; +class EventsData { + 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 @@ final 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 @@ final 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(); @@ -129,13 +196,13 @@ typedef EventInfoFile = { typedef EventInfo = { var params:Array<EventParamInfo>; - var paramValues:Array<Dynamic>; } 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/GlobalScript.hx b/source/funkin/backend/scripting/GlobalScript.hx index a7586c70..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,19 +50,13 @@ class GlobalScript { }); FlxG.signals.postUpdate.add(function() { call("postUpdate", [FlxG.elapsed]); - #if EXPERMENTAL_SCRIPT_RELOADING - 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; } - #end - if (FlxG.keys.justPressed.F2) + + if (PlayerSettings.solo.controls.DEV_CONSOLE) NativeAPI.allocConsole(); }); FlxG.signals.preDraw.add(function() { @@ -76,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 { @@ -95,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 1727750d..81ea4669 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -27,7 +27,7 @@ class HScript extends Script { try { if(Assets.exists(rawPath)) code = Assets.getText(rawPath); - } catch(e) Logs.trace('Error while reading $path: ${Std.string(e)}', ERROR); + } catch(e) Logs.error('Error while reading $path: ${Std.string(e)}'); parser = initParser(); //folderlessPath = Path.directory(path); @@ -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 1f5c3ddc..1e0db90d 100644 --- a/source/funkin/backend/scripting/Script.hx +++ b/source/funkin/backend/scripting/Script.hx @@ -98,6 +98,11 @@ class Script extends FlxBasic implements IFlxDestroyable { "EngineUtil" => funkin.backend.utils.EngineUtil, "MemoryUtil" => funkin.backend.utils.MemoryUtil, "BitmapUtil" => funkin.backend.utils.BitmapUtil, + + #if TRANSLATIONS_SUPPORT + "TranslationUtil" => funkin.backend.utils.TranslationUtil, + "translate" => funkin.backend.utils.TranslationUtil.get, + #end ]; } @@ -208,7 +213,7 @@ class Script extends FlxBasic implements IFlxDestroyable { var arr = Assets.getText(path).split("________PACKSEP________"); fromString(arr[1], arr[0]); case "lua": - Logs.trace("Lua is not supported in this engine. Use HScript instead.", ERROR); + Logs.error("Lua is not supported in this engine. Use HScript instead."); new DummyScript(path); default: new DummyScript(path); @@ -227,7 +232,7 @@ class Script extends FlxBasic implements IFlxDestroyable { case "hx" | "hscript" | "hsc" | "hxs": new HScript(path).loadFromString(code); case "lua": - Logs.trace("Lua is not supported in this engine. Use HScript instead.", ERROR); + Logs.error("Lua is not supported in this engine. Use HScript instead."); new DummyScript(path).loadFromString(code); default: new DummyScript(path).loadFromString(code); 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/CustomShader.hx b/source/funkin/backend/shaders/CustomShader.hx index 0a53df92..62308e69 100644 --- a/source/funkin/backend/shaders/CustomShader.hx +++ b/source/funkin/backend/shaders/CustomShader.hx @@ -33,7 +33,7 @@ class CustomShader extends FunkinShader { path = fragShaderPath+vertShaderPath; if (fragCode == null && vertCode == null) - Logs.trace('Shader "$name" couldn\'t be found.', ERROR); + Logs.error('Shader "$name" couldn\'t be found.'); super(fragCode, vertCode, glslVersion); } 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/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 d247217d..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,24 @@ 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> = []; diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx index 56ae3e92..f60fc621 100644 --- a/source/funkin/backend/system/Flags.hx +++ b/source/funkin/backend/system/Flags.hx @@ -1,20 +1,41 @@ 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_AUTHOR:String = ""; + public static var MOD_API_VERSION:Int = 1; + public static var MOD_DOWNLOAD_LINK:String = ""; + public static var MOD_DEPENDENCIES:Array<String> = []; + + @:noCompletion public static var MOD_ICON64:String = ""; + @:noCompletion public static var MOD_ICON32:String = ""; + @:noCompletion 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_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)'; @@ -28,10 +49,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. @@ -41,7 +58,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) @@ -58,24 +75,41 @@ class Flags { public static var SONGS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override"; public static var WEEKS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override"; + // Translations system // + public static var DEFAULT_LANGUAGE:String = "en"; + public static var DEFAULT_LANGUAGE_NAME:String = "English"; + /** + * **NOTICE:** This will only contain the id of the language, not the full name. + * If you blacklist the default language, you will need to change DEFAULT_LANGUAGE and DEFAULT_LANGUAGE_NAME. + */ + public static var BLACKLISTED_LANGUAGES:Array<String> = []; + /** + * **NOTICE:** This will only contain the id of the language, not the full name. + * If this list is not empty, the languages listed will be the only ones able to be used. + */ + public static var WHITELISTED_LANGUAGES:Array<String> = []; + + // 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; @@ -93,12 +127,24 @@ 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 + // Restart Song -> pause.restart + // Change Controls -> pause.changeControls + // Change Options -> pause.changeOptions + // Exit to menu -> pause.exitToMenu + // Exit to charter -> pause.exitToCharter + // Resume Cutscene -> pause.resumeCutscene + // Skip Cutscene -> pause.skipCutscene + // Restart Cutscene -> pause.restartCutscene public static var DEFAULT_PAUSE_ITEMS:Array<String> = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"]; public static var DEFAULT_CUTSCENE_PAUSE_ITEMS:Array<String> = ['Resume Cutscene', 'Skip Cutscene', 'Restart Cutscene', 'Exit to menu']; public static var DEFAULT_GITAROO:Bool = true; @@ -137,7 +183,7 @@ class Flags { public static var JSON_PRETTY_PRINT:String = "\t"; public static var DISABLE_EDITORS:Bool = false; - public static var DISABLE_BETA_WARNING_SCREEN:Bool = false; + public static var DISABLE_WARNING_SCREEN:Bool = true; public static var DISABLE_TRANSITIONS:Bool = false; @:also(funkin.backend.MusicBeatTransition.script) @@ -151,46 +197,57 @@ class Flags { public static var URL_EDITOR_FALLBACK:String = "https://www.youtube.com/watch?v=9Youam7GYdQ"; public static var URL_FNF_ITCH:String = "https://ninja-muffin24.itch.io/funkin"; + /** + * Default editor sound paths + */ + public static var DEFAULT_EDITOR_AUTOSAVE_SOUND = "editors/autosave"; + public static var DEFAULT_EDITOR_BUTTONCLICK_SOUND = "editors/buttonClick"; + public static var DEFAULT_EDITOR_CLICK_SOUND = "editors/click"; + public static var DEFAULT_EDITOR_COPY_SOUND = "editors/copy"; + public static var DEFAULT_EDITOR_CUT_SOUND = "editors/cut"; + public static var DEFAULT_EDITOR_DELETE_SOUND = "editors/delete"; + public static var DEFAULT_EDITOR_OFFSETDRAG_SOUND = "editors/offsetDrag"; + public static var DEFAULT_EDITOR_PASTE_SOUND = "editors/paste"; + public static var DEFAULT_EDITOR_REDO_SOUND = "editors/redo"; + public static var DEFAULT_EDITOR_SAVE_SOUND = "editors/save"; + public static var DEFAULT_EDITOR_TEXTREMOVE_SOUND = "editors/textRemove"; + public static var DEFAULT_EDITOR_TEXTTYPE_SOUND = "editors/textType"; + public static var DEFAULT_EDITOR_UNDO_SOUND = "editors/undo"; + public static var DEFAULT_EDITOR_WINDOWAPPEAR_SOUND = "editors/windowAppear"; + public static var DEFAULT_EDITOR_WINDOWCLOSE_SOUND = "editors/windowClose"; + public static var DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND = "editors/dropdownAppear"; + public static var DEFAULT_CHARTER_HITSOUND_SOUND = "editors/charter/hitsound"; + public static var DEFAULT_CHARTER_METRONOME_SOUND = "editors/charter/metronome"; + public static var DEFAULT_CHARTER_NOTEDELETE_SOUND = "editors/charter/noteDelete"; + public static var DEFAULT_CHARTER_NOTEPLACE_SOUND = "editors/charter/notePlace"; + public static var DEFAULT_CHARTER_SCROLL_SOUND = "editors/charter/scroll"; + public static var DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND = "editors/charter/snappingChange"; + public static var DEFAULT_CHARTER_STRUMLOCK_SOUND = "editors/charter/strumLock"; + public static var DEFAULT_CHARTER_STRUMUNLOCK_SOUND = "editors/charter/strumUnlock"; + public static var DEFAULT_CHARTER_SUSTAINADD_SOUND = "editors/charter/sustainAdd"; + public static var DEFAULT_CHARTER_SUSTAINDELETE_SOUND = "editors/charter/sustainDelete"; + public static var DEFAULT_CHARACTER_GHOSTDISABLE_SOUND = "editors/character/ghostDisable"; + public static var DEFAULT_CHARACTER_GHOSTENABLE_SOUND = "editors/character/ghostEnable"; + public static var DEFAULT_GLSL_VERSION:String = "120"; @:also(funkin.backend.utils.HttpUtil.userAgent) public static var USER_AGENT:String = 'request'; // -- 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); } @@ -209,16 +266,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(lib.exists(flagsPath, AssetType.TEXT)) - lib.getAsset(flagsPath, AssetType.TEXT) - ]; + 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 + 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 44aebb6b..1913e4ab 100644 --- a/source/funkin/backend/system/Logs.hx +++ b/source/funkin/backend/system/Logs.hx @@ -82,16 +82,13 @@ final class Logs { logText(' |'), switch (level) { - case WARNING: - logText(' WARNING ', DARKYELLOW); - case ERROR: - logText(' ERROR ', DARKRED); - case TRACE: - logText(' TRACE ', GRAY); - case VERBOSE: - logText(' VERBOSE ', DARKMAGENTA); - default: - logText(' INFORMATION ', CYAN); + case WARNING: logText(' WARNING ', DARKYELLOW); + case ERROR: logText(' ERROR ', DARKRED); + case TRACE: logText(' TRACE ', GRAY); + case VERBOSE: logText(' VERBOSE ', DARKMAGENTA); + case SUCCESS: logText(' SUCCESS ', GREEN); + case FAILURE: logText(' FAILURE ', RED); + default: logText(' INFORMATION ', CYAN); }, logText('] ') ]; @@ -165,6 +162,8 @@ enum abstract Level(Int) { var ERROR = 2; var TRACE = 3; var VERBOSE = 4; + var SUCCESS = 5; + var FAILURE = 6; } typedef LogText = { diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx index da72adb9..f5844278 100644 --- a/source/funkin/backend/system/Main.hx +++ b/source/funkin/backend/system/Main.hx @@ -61,6 +61,12 @@ class Main extends Sprite public static var gameThreads:Array<Thread> = []; #end + public static function preInit() { + funkin.backend.utils.NativeAPI.registerAsDPICompatible(); + funkin.backend.system.CommandLineHandler.parseCommandLine(Sys.args()); + funkin.backend.system.Main.fixWorkingDirectory(); + } + public function new() { super(); @@ -81,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; @@ -132,17 +138,16 @@ class Main extends Sprite #end #if (sys && TEST_BUILD) - trace("Used cne test / cne build. Switching into source assets."); + Logs.infos("Used cne test / cne build. Switching into source assets."); #if MOD_SUPPORT ModsFolder.modsPath = './${pathBack}mods/'; ModsFolder.addonsPath = './${pathBack}addons/'; #end - Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './${pathBack}assets/', true)); + Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './${pathBack}assets/', true, SOURCE)); #elseif USE_ADAPTED_ASSETS - Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './assets/', true)); + Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './assets/', true, SOURCE)); #end - var lib = new AssetLibrary(); @:privateAccess lib.__proxy = Paths.assetsTree; @@ -180,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; @@ -238,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 4cf6b972..ba58479c 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) { @@ -36,28 +43,69 @@ class MainState extends FlxState { 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(); + TranslationUtil.findAllLanguages(); + TranslationUtil.setLanguage(); ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder); // Loads global.hx MusicBeatTransition.script = Flags.DEFAULT_TRANSITION_SCRIPT; WindowUtils.resetTitle(); @@ -67,17 +115,27 @@ class MainState extends FlxState { ControlsUtil.loadCustomControls(); TitleState.initialized = false; - if(Framerate.isLoaded) + if (Framerate.isLoaded) Framerate.instance.reload(); - 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; + } + } + } + + if (!Flags.DISABLE_WARNING_SCREEN) FlxG.switchState(new funkin.menus.WarningState()); + else FlxG.switchState(new TitleState()); } } \ No newline at end of file diff --git a/source/funkin/backend/system/framerate/AssetTreeInfo.hx b/source/funkin/backend/system/framerate/AssetTreeInfo.hx index b886abb4..93ebd9ff 100644 --- a/source/funkin/backend/system/framerate/AssetTreeInfo.hx +++ b/source/funkin/backend/system/framerate/AssetTreeInfo.hx @@ -1,5 +1,9 @@ package funkin.backend.system.framerate; +#if TRANSLATIONS_SUPPORT +import funkin.backend.assets.TranslatedAssetLibrary; +#end +import funkin.backend.assets.AssetsLibraryList; import funkin.backend.assets.IModsAssetLibrary; import funkin.backend.assets.ScriptedAssetLibrary; @@ -13,20 +17,25 @@ class AssetTreeInfo extends FramerateCategory { var text = 'Not initialized yet\n'; if (Paths.assetsTree != null){ text = ""; - for(e in Paths.assetsTree.libraries) { - var l = e; - if (l is openfl.utils.AssetLibrary) { - var al = cast(l, openfl.utils.AssetLibrary); - @:privateAccess - if (al.__proxy != null) l = al.__proxy; - } + for(l in Paths.assetsTree.libraries) { + var l = AssetsLibraryList.getCleanLibrary(l); - if (l is ScriptedAssetLibrary) - text += '${Type.getClassName(Type.getClass(l))} - ${cast(l, ScriptedAssetLibrary).scriptName} (${cast(l, ScriptedAssetLibrary).modName} | ${cast(l, ScriptedAssetLibrary).libName} | ${cast(l, ScriptedAssetLibrary).prefix})\n'; + var tag = l.tag.toString().toUpperCase(); + + text += '[$tag] '; + + var className = Type.getClassName(Type.getClass(l)); + className = className.substr(className.lastIndexOf(".") + 1); + + #if TRANSLATIONS_SUPPORT + if (l is TranslatedAssetLibrary) + text += '${className} - ${cast(l, TranslatedAssetLibrary).langFolder} for (${cast(l, TranslatedAssetLibrary).forLibrary.modName})\n'; + else #end if (l is ScriptedAssetLibrary) + text += '${className} - ${cast(l, ScriptedAssetLibrary).scriptName} (${cast(l, ScriptedAssetLibrary).modName} | ${cast(l, ScriptedAssetLibrary).libName} | ${cast(l, ScriptedAssetLibrary).prefix})\n'; else if (l is IModsAssetLibrary) - text += '${Type.getClassName(Type.getClass(l))} - ${cast(l, IModsAssetLibrary).modName} - ${cast(l, IModsAssetLibrary).libName} (${cast(l, IModsAssetLibrary).prefix})\n'; + text += '${className} - ${cast(l, IModsAssetLibrary).modName} - ${cast(l, IModsAssetLibrary).libName} (${cast(l, IModsAssetLibrary).prefix})\n'; else - text += Std.string(e) + "\n"; + text += Std.string(l) + '\n'; } } if (text != "") diff --git a/source/funkin/backend/system/framerate/CodenameBuildField.hx b/source/funkin/backend/system/framerate/CodenameBuildField.hx index aa7a9b7b..57238298 100644 --- a/source/funkin/backend/system/framerate/CodenameBuildField.hx +++ b/source/funkin/backend/system/framerate/CodenameBuildField.hx @@ -12,6 +12,10 @@ class CodenameBuildField extends TextField { reload(); } - public function reload() - text = 'Codename Engine ${Flags.RELEASE_CYCLE}\n${Flags.COMMIT_MESSAGE}'; + public function reload() { + text = '${Flags.VERSION_MESSAGE}'; + #if debug + text += '\n${Flags.COMMIT_MESSAGE}'; + #end + } } 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 0528ffee..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; diff --git a/source/funkin/backend/system/framerate/SystemInfo.hx b/source/funkin/backend/system/framerate/SystemInfo.hx index ec44f05f..9bf44f41 100644 --- a/source/funkin/backend/system/framerate/SystemInfo.hx +++ b/source/funkin/backend/system/framerate/SystemInfo.hx @@ -24,7 +24,7 @@ class SystemInfo extends FramerateCategory { public static function init() { #if linux var process = new HiddenProcess("cat", ["/etc/os-release"]); - if (process.exitCode() != 0) Logs.trace('Unable to grab OS Label', ERROR, RED); + if (process.exitCode() != 0) Logs.error('Unable to grab OS Label'); else { var osName = ""; var osVersion = ""; @@ -53,19 +53,35 @@ 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}'; else - Logs.trace('Unable to grab OS Label', ERROR, RED); + Logs.error('Unable to grab OS Label'); #end 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'; @@ -83,10 +99,10 @@ class SystemInfo extends FramerateCategory { } #end } catch (e) { - Logs.trace('Unable to grab CPU Name: $e', ERROR, RED); + 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 @@ -104,19 +120,19 @@ class SystemInfo extends FramerateCategory { } } } else - Logs.trace('Unable to grab GPU Info', ERROR, RED); + Logs.error('Unable to grab GPU Info'); } #if cpp totalMem = Std.string(MemoryUtil.getTotalMem() / 1024) + " GB"; #else - Logs.trace('Unable to grab RAM Amount', ERROR, RED); + Logs.error('Unable to grab RAM Amount'); #end try { memType = MemoryUtil.getMemType(); } catch (e) { - Logs.trace('Unable to grab RAM Type: $e', ERROR, RED); + Logs.error('Unable to grab RAM Type: $e'); } formatSysInfo(); } diff --git a/source/funkin/backend/system/macros/BuildInfo.hx b/source/funkin/backend/system/macros/BuildInfo.hx index 37c0f197..a22511b8 100644 --- a/source/funkin/backend/system/macros/BuildInfo.hx +++ b/source/funkin/backend/system/macros/BuildInfo.hx @@ -5,8 +5,10 @@ using StringTools; #if macro class BuildInfo { public static function printBuildInfo() { - if(haxe.macro.Context.defined("display")) return; + 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/macros/HashLinkFixer.hx b/source/funkin/backend/system/macros/HashLinkFixer.hx index 27742c60..fc530608 100644 --- a/source/funkin/backend/system/macros/HashLinkFixer.hx +++ b/source/funkin/backend/system/macros/HashLinkFixer.hx @@ -5,147 +5,69 @@ import haxe.macro.Expr; import haxe.macro.*; using StringTools; -using haxe.macro.PositionTools; class HashLinkFixer { - public static var buildMacroString = '@:build(funkin.backend.system.macros.HashLinkFixer.build())'; - - public static var applyOn:Array<String> = [ - "lime", - "std", - "Math", - "", - ]; - - public static var modifiedClasses:Array<String> = []; - - public static function init() { - #if !display - if(Context.defined("hl")) { - for(apply in applyOn) { - compile(apply); - } - } - #end - } - - public static function compile(name:String) { - #if !display - Compiler.addGlobalMetadata(name, buildMacroString); - #end - } - public static function build():Array<Field> { - var fields = Context.getBuildFields(); - var clRef = Context.getLocalClass(); + final fields = Context.getBuildFields(), clRef = Context.getLocalClass(); + if (clRef == null) return fields; - var cl = clRef.get(); + final cl = clRef.get(); - if (cl.isAbstract || cl.isExtern || cl.isInterface) return fields; - if (!cl.name.endsWith("_Impl_") && !cl.name.endsWith("_HSX") && !cl.name.endsWith("_HSC")) { - if(cl.params.length > 0) - return fields; + if ( + cl.isAbstract || cl.isExtern || cl.isInterface || cl.params.length > 0 || + cl.name.endsWith("_Impl_") || cl.name.endsWith("_HSX") || cl.name.endsWith("_HSC") + ) return fields; - var definedFields = []; + final pos = Context.currentPos(); + for (i in 0...fields.length) { + final f = fields[i]; + if (f == null || f.name == 'new') continue; - for(f in fields.copy()) { - if (f == null) - continue; - if (f.name == "new") - continue; + switch (f.kind) { + case FFun(func): + if (func == null) continue; - if(definedFields.contains(f.name)) continue; // no duplicate fields + var hlNativeMeta = null; + final cleanMeta = f.meta.filter((m) -> { + if (m.name != ':hlNative') return true; + else { + hlNativeMeta = m; + return false; + } + }); + if (hlNativeMeta == null) continue; - var hlNativeMeta = null; - var hasHlNative = false; - for(m in f.meta) - if (m.name == ":hlNative") { - hasHlNative = true; - hlNativeMeta = m; - break; + switch (hlNativeMeta.params) { + case []: hlNativeMeta.params = [macro "std", macro $v{f.name}]; + case [_.expr => EConst(CString(name))]: hlNativeMeta.params = [macro "std", macro $v{name}]; + case [_.expr => EConst(CFloat(version))]: + final curVersion = Context.definedValue("hl_ver"); + if (curVersion != null && version > curVersion) { + if (f.meta.filter((m) -> return m.name == ":noExpr").length > 0) + Context.error("Missing function body", hlNativeMeta.pos); + } + else + hlNativeMeta.params = [macro "std", macro $v{f.name}]; + default: } - if(!hasHlNative) continue; + final name = 'hlf_${f.name}'; + fields.push({ + name: name, + pos: pos, + kind: FFun({ret: func.ret, params: func.params, expr: func.expr, args: func.args}), + access: f.access.filter((a) -> return a != APublic && a != APrivate).concat([APrivate]), + meta: [hlNativeMeta] + }); - switch(f.kind) { - case FFun(fun): - if (fun == null) - continue; - if (fun.params != null && fun.params.length > 0) // TODO: Support for this maybe? - continue; + final args = func.args == null ? [] : [for (a in func.args) macro $i{a.name}]; - if(fun.params == null) - fun.params = []; - - var overrideExpr:Expr; - var returns:Bool = !fun.ret.match(TPath({name: "Void"})); - - var printer = new haxe.macro.Printer(); - if(cl.module == "hl.Gc" && fun.ret == null) returns = false; // fix since they don't explicitly set :Void - if(cl.module == "hl.Format" && fun.ret == null) returns = false; // fix since they don't explicitly set :Void - - var name = 'hlf_${f.name}'; - - var arguments = fun.args == null ? [] : [for(a in fun.args) macro $i{a.name}]; - - var funcExpr:Expr = macro @:privateAccess $i{name}($a{arguments}); - if(returns) funcExpr = macro return $funcExpr; - - var cleanMeta = f.meta.copy().filter(function(m) return m.name != ":hlNative"); - var hasBareMeta = hlNativeMeta.params.length == 0; - - var meta = f.meta.copy(); - switch hlNativeMeta { - case {params: []}: - meta = [{name: ":hlNative", params: [macro "std", macro $v{f.name}], pos: Context.currentPos()}].concat(cleanMeta); - case {params: [_.expr => EConst(CString(name))]}: - meta = [{name: ":hlNative", params: [macro "std", macro $v{name}], pos: Context.currentPos()}].concat(cleanMeta); - case {params: [_.expr => EConst(CFloat(version))]}: - var curVersion = Context.definedValue("hl_ver"); - if(curVersion == null) curVersion = ""; - if(version > curVersion) { - meta = cleanMeta; - if(f.meta.filter((m) -> m.name == ":noExpr").length > 0) - Context.error("Missing function body", f.pos); - funcExpr = fun.expr; // restore to default behaviour - } else { - meta = [{name: ":hlNative", params: [macro "std", macro $v{f.name}], pos: Context.currentPos()}].concat(cleanMeta); - } - default: - } - - var field:Field = { - name: name, - pos: Context.currentPos(), - kind: FFun({ - ret: fun.ret, - params: fun.params.copy(), - expr: funcExpr, - args: fun.args.copy() - }), - access: f.access.copy().filter(function(a) return a != APublic && a != APrivate).concat([APrivate]), - meta: meta - }; - fields.push(field); - definedFields.push(f.name); - - // Remove meta from original function - for(m in f.meta.copy()) - if (m.name == ":hlNative") { - f.meta.remove(m); - } - - default: - } + f.meta = cleanMeta; + func.expr = args.length == 0 ? macro $i{name}() : macro $i{name}($a{args}); + if (func.ret != null && !func.ret.match(TPath({name: 'Void'}))) + func.expr = macro return ${func.expr}; + default: } - - /*if(definedFields.length > 0) { - trace(cl.module); - - var printer = new haxe.macro.Printer(); - for(field in fields) if(field.name.startsWith("hlf_")) - Sys.println(printer.printField(field)); - }*/ } return fields; diff --git a/source/funkin/backend/system/macros/Macros.hx b/source/funkin/backend/system/macros/Macros.hx index 5b51a6cd..f3bf4adb 100644 --- a/source/funkin/backend/system/macros/Macros.hx +++ b/source/funkin/backend/system/macros/Macros.hx @@ -39,9 +39,9 @@ class Macros { if(Context.defined("sys")) { for(inc in ["sys", "openfl.net", "funkin.backend.system.net"]) { - if(!isHl) - Compiler.include(inc, compathx4); + if(!isHl) Compiler.include(inc, compathx4); else { + // TODO: Hashlink //Compiler.include(inc, compathx4.concat(["sys.net.UdpSocket", "openfl.net.DatagramSocket"]); // fixes FATAL ERROR : Failed to load function std@socket_set_broadcast } @@ -52,8 +52,20 @@ class Macros { } public static function initMacros() { - if(Context.defined("hl")) - HashLinkFixer.init(); + if (Context.defined("hl")) { + for (c in ["lime", "std", "Math", ""]) Compiler.addGlobalMetadata(c, "@:build(funkin.backend.system.macros.HashLinkFixer.build())"); + } + + final macroPath = 'funkin.backend.system.macros.Macros'; + Compiler.addMetadata('@:build($macroPath.buildLimeAssetLibrary())', 'lime.utils.AssetLibrary'); + } + + public static function buildLimeAssetLibrary():Array<Field> { + final fields:Array<Field> = Context.getBuildFields(), pos:Position = Context.currentPos(); + + fields.push({name: 'tag', access: [APublic], pos: pos, kind: FVar(macro :funkin.backend.assets.AssetSource)}); + + return fields; } } #end \ No newline at end of file diff --git a/source/funkin/backend/system/modules/AudioSwitchFix.hx b/source/funkin/backend/system/modules/AudioSwitchFix.hx index 47eea017..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 @@ -11,59 +13,57 @@ import lime.media.AudioManager; * hi gray itsa me yoshicrafter29 i fixed it hehe */ @:dox(hide) -final class AudioSwitchFix { - @:noCompletion - private static function onStateSwitch(state:FlxState):Void { - #if windows - if (Main.audioDisconnected) { +class AudioSwitchFix { + 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/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 c788cdf9..fd13b2cc 100644 --- a/source/funkin/backend/system/updating/UpdateAvailableScreen.hx +++ b/source/funkin/backend/system/updating/UpdateAvailableScreen.hx @@ -41,7 +41,9 @@ class UpdateAvailableScreen extends MusicBeatState { bg.screenCenter(); add(bg); - title = new Alphabet(0, 10, "NEW UPDATE", true); + // which file should i put this in? + // Misc.xml? + title = new Alphabet(0, 10, TU.translate("updateAvailable.title"), true); title.screenCenter(X); title.scrollFactor.set(); @@ -61,8 +63,8 @@ class UpdateAvailableScreen extends MusicBeatState { changeLogText.borderColor = 0xFF000000; MarkdownUtil.applyMarkdownText(changeLogText, check.updates.last().body); - installButton = new FunkinText(0, FlxG.height - 25, Std.int(FlxG.width / 2), "> INSTALL <", 32); - skipButton = new FunkinText(Std.int(FlxG.width / 2), FlxG.height - 25, Std.int(FlxG.width / 2), "SKIP", 32); + installButton = new FunkinText(0, FlxG.height - 25, Std.int(FlxG.width / 2), "PLACEHOLDER", 32); + skipButton = new FunkinText(Std.int(FlxG.width / 2), FlxG.height - 25, Std.int(FlxG.width / 2), "PLACEHOLDER", 32); skipButton.y -= skipButton.height; installButton.y -= installButton.height; @@ -91,6 +93,8 @@ class UpdateAvailableScreen extends MusicBeatState { oldPos = FlxG.mouse.getScreenPosition(); + changeSelection(false); + DiscordUtil.call("onMenuLoaded", ["Update Available Screen"]); } @@ -132,16 +136,18 @@ class UpdateAvailableScreen extends MusicBeatState { } } + var installText = TU.translate("updateAvailable.install"); + var skipText = TU.translate("updateAvailable.skip"); - public function changeSelection() { + public function changeSelection(playSFX:Bool = true) { CoolUtil.playMenuSFX(SCROLL, 0.7); - if (installSelected) { - installButton.text = "> INSTALL <"; - skipButton.text = "SKIP"; - } else { - installButton.text = "INSTALL"; - skipButton.text = "> SKIP <"; - } + + // keeping this local just incase we wanna change this later + inline function wrap(str:String, shouldWrap:Bool) + return shouldWrap ? "> " + str + " <" : str; + + installButton.text = wrap(installText, installSelected); + skipButton.text = wrap(skipText, !installSelected); } public override function destroy() { diff --git a/source/funkin/backend/system/updating/UpdateScreen.hx b/source/funkin/backend/system/updating/UpdateScreen.hx index 51af11ab..60ed6506 100644 --- a/source/funkin/backend/system/updating/UpdateScreen.hx +++ b/source/funkin/backend/system/updating/UpdateScreen.hx @@ -82,20 +82,20 @@ class UpdateScreen extends MusicBeatState { switch(prog.step) { case PREPARING: progressBar.value = 0; - generalProgress.text = "Preparing update installation... (1/4)"; - partProgress.text = "Creating installation folder and cleaning old update files..."; + generalProgress.text = TU.translate("updateScreen.general-1") + " (1/4)"; + partProgress.text = TU.translate("updateScreen.part-1"); case DOWNLOADING_ASSETS: progressBar.value = 1 + ((prog.curFile-1+(prog.bytesLoaded/prog.bytesTotal)) / prog.files); - generalProgress.text = "Downloading update assets... (2/4)"; - partProgress.text = 'Downloading file ${prog.curFileName}\n(${prog.curFile+1}/${prog.files} | ${CoolUtil.getSizeString(prog.bytesLoaded)} / ${CoolUtil.getSizeString(prog.bytesTotal)} | ${CoolUtil.getSizeString(lerpSpeed)}/s)'; + generalProgress.text = TU.translate("updateScreen.general-2") + " (2/4)"; + partProgress.text = TU.translate("updateScreen.part-2", [prog.curFileName]) + '\n(${prog.curFile+1}/${prog.files} | ${CoolUtil.getSizeString(prog.bytesLoaded)} / ${CoolUtil.getSizeString(prog.bytesTotal)} | ${CoolUtil.getSizeString(lerpSpeed)}/s)'; case DOWNLOADING_EXECUTABLE: progressBar.value = 2 + (prog.bytesLoaded/prog.bytesTotal); - generalProgress.text = "Downloading new engine executable... (3/4)"; - partProgress.text = 'Downloading ${prog.curFileName}\n(${CoolUtil.getSizeString(prog.bytesLoaded)} / ${CoolUtil.getSizeString(prog.bytesTotal)} | ${CoolUtil.getSizeString(lerpSpeed)}/s)'; + generalProgress.text = TU.translate("updateScreen.general-3") + " (3/4)"; + partProgress.text = TU.translate("updateScreen.part-3", [prog.curFileName]) + '\n(${CoolUtil.getSizeString(prog.bytesLoaded)} / ${CoolUtil.getSizeString(prog.bytesTotal)} | ${CoolUtil.getSizeString(lerpSpeed)}/s)'; case INSTALLING: progressBar.value = 3 + ((prog.curFile-1+(prog.curZipProgress.curFile/prog.curZipProgress.fileCount))/prog.files); - generalProgress.text = "Installing new files... (4/4)"; - partProgress.text = 'Installing ${prog.curFileName}\n(${prog.curFile}/${prog.files})'; + generalProgress.text = TU.translate("updateScreen.general-4") + " (4/4)"; + partProgress.text = TU.translate("updateScreen.part-4", [prog.curFileName]) + '\n(${prog.curFile}/${prog.files})'; } var rect = new FlxRect(0, (1 - (progressBar.value / 4)) * bf.frameHeight, bf.frameWidth, 0); rect.height = bf.frameHeight - rect.y; diff --git a/source/funkin/backend/utils/AudioAnalyzer.hx b/source/funkin/backend/utils/AudioAnalyzer.hx index df818d03..0ad9af3d 100644 --- a/source/funkin/backend/utils/AudioAnalyzer.hx +++ b/source/funkin/backend/utils/AudioAnalyzer.hx @@ -3,43 +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 bitsPerSample = buffer.bitsPerSample; - var multi = __timeMulti / 4000 / bitsPerSample; - var bytesStartPos:Int = Math.floor(startPos * multi) * bitsPerSample; - var bytesEndPos:Int = Math.floor(endPos * multi) * 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 % bitsPerSample; - bytesEndPos -= bytesEndPos % 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 dataBuffer = buffer.data.buffer; - - var maxByte:Int = 0; - for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / bitsPerSample)) { - var bytePos = bytesStartPos + (i * bitsPerSample); - // What if bitsPerSample is 8? or 32? or 64? -Ne_Eo - var byte:Int = dataBuffer.get(bytePos) | (dataBuffer.get(bytePos + 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/ControlsUtil.hx b/source/funkin/backend/utils/ControlsUtil.hx index 9d48c25c..a3e2d5f8 100644 --- a/source/funkin/backend/utils/ControlsUtil.hx +++ b/source/funkin/backend/utils/ControlsUtil.hx @@ -97,7 +97,7 @@ class ControlsUtil { public static function loadCustomControls() { var xmlPath = Paths.xml("config/controls"); - for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.AssetSource.MODS]) { + 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 { diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 5482df6c..d24e153a 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -83,6 +83,38 @@ final class CoolUtil return (v == null || isNaN(v)) ? defaultValue : v; } + /** + * For use when using Std.parseFloat, if not using that then use `getDefault`. + * @param v The value + * @param defaultValue The default value + * @return The return value + */ + public static inline function getDefaultFloat(v:Float, defaultValue:Float):Float { + return (Math.isNaN(v)) ? defaultValue : v; + } + + /** + * Applies the % operator, but without any negatives. + * + * @param dividend The intial value. The left side of the equation. + * @param divisor What to modulo by. The right side of the equation. + * @return A positive reminder of `dividend / divisor`. + */ + public static inline function positiveModulo(dividend:Float, divisor:Float) { + return ((dividend % divisor) + divisor) % divisor; + } + + /** + * Applies the % operator, but without any negatives. But it's an int. + * + * @param dividend The intial value. The left side of the equation. + * @param divisor What to modulo by. The right side of the equation. + * @return A positive reminder of `dividend / divisor`. + */ + public static inline function positiveModuloInt(dividend:Int, divisor:Int) { + return ((dividend % divisor) + divisor) % divisor; + } + /** * Shortcut to parse JSON from an Asset path * @param assetPath Path to the JSON asset. @@ -105,7 +137,7 @@ final class CoolUtil FileSystem.deleteDirectory(delete + "/" + file); } else { try FileSystem.deleteFile(delete + "/" + file) - catch(e) Logs.trace("Could not delete " + delete + "/" + file, WARNING); + catch(e) Logs.warn("Could not delete " + delete + "/" + file); } } #end @@ -124,7 +156,7 @@ final class CoolUtil else sys.io.File.saveContent(path, content); } catch(e) { var errMsg:String = 'Error while trying to save the file: ${Std.string(e).replace('\n', ' ')}'; - Logs.traceColored([Logs.logText(errMsg, RED)], ERROR); + Logs.error(errMsg); if(showErrorBox) funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING); } #end @@ -145,7 +177,7 @@ final 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 } @@ -183,7 +215,7 @@ final 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; @@ -249,7 +281,16 @@ final class CoolUtil } /** - * Returns the last of an Array + * Returns the first element of an Array + * @param array Array + * @return T Last element + */ + public static inline function first<T>(array:Array<T>):T { + return array[0]; + } + + /** + * Returns the last element of an Array * @param array Array * @return T Last element */ @@ -367,7 +408,7 @@ final class CoolUtil * @return FPS-Modified Ratio */ @:noUsing public static inline function getFPSRatio(ratio:Float):Float { - return CoolUtil.bound(ratio * 60 * FlxG.elapsed, 0.0, 1.0); + return 1.0 - Math.pow(1.0 - ratio, FlxG.elapsed * 60); } /** * Tries to get a color from a `Dynamic` variable. @@ -414,7 +455,6 @@ final 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); } @@ -444,32 +484,34 @@ final 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.exists) 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"]).getDefault(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(); } /** @@ -478,14 +520,14 @@ final class CoolUtil * @param volume At which volume it should play */ @:noUsing public static inline function playMenuSFX(menuSFX:CoolSfx = SCROLL, volume:Float = 1) { - FlxG.sound.play(Paths.sound(switch(menuSFX) { - case CONFIRM: 'menu/confirm'; - case CANCEL: 'menu/cancel'; - case SCROLL: 'menu/scroll'; - case CHECKED: 'menu/checkboxChecked'; - case UNCHECKED: 'menu/checkboxUnchecked'; - case WARNING: 'menu/warningMenu'; - default: 'menu/scroll'; + FlxG.sound.play(Paths.sound('menu/' + switch(menuSFX) { + case CONFIRM: 'confirm'; + case CANCEL: 'cancel'; + case SCROLL: 'scroll'; + case CHECKED: 'checkboxChecked'; + case UNCHECKED: 'checkboxUnchecked'; + case WARNING: 'warningMenu'; + default: 'scroll'; }), volume); } @@ -712,11 +754,11 @@ final 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"); } @@ -849,11 +891,7 @@ final class CoolUtil * Stops a sound, set its time to 0 then play it again. * @param sound Sound to replay. */ - public static inline function replay(sound:FlxSound) { - sound.stop(); - sound.time = 0; - sound.play(); - } + public static inline function replay(sound:FlxSound) sound.play(true, 0); /** * Equivalent of `Math.max`, except doesn't require a Int -> Float -> Int conversion. @@ -904,10 +942,11 @@ final 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); } /** @@ -915,8 +954,8 @@ final 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. @@ -1021,6 +1060,13 @@ final class CoolUtil } #end + public static inline function repeat(str:String, times:Int) { + var r = new StringBuf(); + for(i in 0...times) + 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; @@ -1156,6 +1202,15 @@ final 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 @@ -1323,6 +1378,124 @@ final class CoolUtil 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 374768e8..282e5b2b 100644 --- a/source/funkin/backend/utils/DiscordUtil.hx +++ b/source/funkin/backend/utils/DiscordUtil.hx @@ -80,7 +80,7 @@ final class DiscordUtil try config = Json.parse(Assets.getText(jsonPath)) catch (e) - Logs.trace('Couldn\'t load Discord RPC configuration: ${e.toString()}', ERROR); + Logs.error('Couldn\'t load Discord RPC configuration: ${e.toString()}'); } if (config == null) @@ -89,6 +89,10 @@ final 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 } @@ -364,7 +368,7 @@ final class DiscordUtil Logs.traceColored([ Logs.getPrefix("Discord"), Logs.logText("Someone has just requested to join", YELLOW) - ], WARNING); + ], INFO); var req:DUser = DUser.initRaw(request); call("onJoinRequest", [req]); diff --git a/source/funkin/backend/utils/HttpUtil.hx b/source/funkin/backend/utils/HttpUtil.hx index cfd34005..be9d8ac3 100644 --- a/source/funkin/backend/utils/HttpUtil.hx +++ b/source/funkin/backend/utils/HttpUtil.hx @@ -63,7 +63,7 @@ final class HttpUtil { // 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex case 301 | 302 | 307 | 308: - Logs.traceColored([Logs.logText('[Connection Status] ', BLUE), Logs.logText('Redirected with status code: ', YELLOW), Logs.logText('$status', GREEN)], WARNING); + Logs.traceColored([Logs.logText('[Connection Status] ', BLUE), Logs.logText('Redirected with status code: ', YELLOW), Logs.logText('$status', GREEN)], VERBOSE); return true; } return false; diff --git a/source/funkin/backend/utils/IniUtil.hx b/source/funkin/backend/utils/IniUtil.hx index ce1cc6e8..22cecee4 100644 --- a/source/funkin/backend/utils/IniUtil.hx +++ b/source/funkin/backend/utils/IniUtil.hx @@ -1,39 +1,33 @@ package funkin.backend.utils; -/** - * DOESN'T SUPPORT CATEGORIES YET!! - */ -final class IniUtil { - public static inline function parseAsset(assetPath:String, ?defaultVariables:Map<String, String>) - return parseString(Assets.getText(assetPath), defaultVariables); +class IniUtil { + public static inline function parseAsset(assetPath:String) + return parseString(Assets.getText(assetPath)); - public static function parseString(data:String, ?defaultVariables:Map<String, String>) { - 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:Map<String, String> = []; - 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; - // categories; 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/MathUtil.hx b/source/funkin/backend/utils/MathUtil.hx index 06b4e83a..2ca2f5a2 100644 --- a/source/funkin/backend/utils/MathUtil.hx +++ b/source/funkin/backend/utils/MathUtil.hx @@ -73,6 +73,84 @@ final class MathUtil { 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 * diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx index 6614b828..2de56184 100644 --- a/source/funkin/backend/utils/MemoryUtil.hx +++ b/source/funkin/backend/utils/MemoryUtil.hx @@ -127,7 +127,7 @@ final 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", @@ -166,9 +166,9 @@ final 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/NdllUtil.hx b/source/funkin/backend/utils/NdllUtil.hx index 9bada658..58db3e64 100644 --- a/source/funkin/backend/utils/NdllUtil.hx +++ b/source/funkin/backend/utils/NdllUtil.hx @@ -41,7 +41,7 @@ final class NdllUtil { //return Reflect.callMethod(null, func, a); // wouldn't work for some reason, maybe cause like c++ functions doesn't have reflection enabled }); #else - Logs.trace('NDLLs are not supported on this platform.', WARNING); + Logs.warn('NDLLs are not supported on this platform.'); return noop; #end } @@ -56,20 +56,20 @@ final class NdllUtil { public static function getFunctionFromPath(ndll:String, name:String, args:Int):Dynamic { #if NDLLS_SUPPORTED if (!Assets.exists(ndll)) { - Logs.trace('Couldn\'t find ndll at ${ndll}.', WARNING); + Logs.warn('Couldn\'t find ndll at ${ndll}.'); return noop; } var func = lime.system.CFFI.load(Assets.getPath(ndll), name, args); if (func == null) { - Logs.trace('Method ${name} in ndll ${ndll} with ${args} args was not found.', ERROR); + Logs.error('Method ${name} in ndll ${ndll} with ${args} args was not found.'); return noop; } return func; #else - Logs.trace('NDLLs are not supported on this platform.', WARNING); - #end + Logs.warn('NDLLs are not supported on this platform.'); return noop; + #end } @:dox(hide) @:noCompletion static function noop() {} 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/SortedArrayUtil.hx b/source/funkin/backend/utils/SortedArrayUtil.hx index e637d74f..1eda2ddb 100644 --- a/source/funkin/backend/utils/SortedArrayUtil.hx +++ b/source/funkin/backend/utils/SortedArrayUtil.hx @@ -12,7 +12,7 @@ final class SortedArrayUtil { * @param getVal Function that returns the position value of T * @return The index of the element */ - public static inline function binarySearch<T>(array:Array<T>, val:Float, getVal:T->Float):Int { + public static function binarySearch<T>(array:Array<T>, val:Float, getVal:T->Float):Int { if (array.length <= 0) return 0; // if the array is empty, it should be equal to zero (the beginning) if (getVal(array[0]) > val) return 0; // in case its the minimum if (getVal(array[array.length-1]) < val) return array.length; // in case its the maximum @@ -56,7 +56,8 @@ final class SortedArrayUtil { * @param getVal Function that returns the value that needs to be sorted */ public static inline function removeSorted<T>(array:Array<T>, val:T, getVal:T->Float) { - if (val != null) - array.splice(binarySearch(array, getVal(val), getVal), 1); + var index = binarySearch(array, getVal(val), getVal); + if (index != -1) + array.splice(index, 1); } } \ No newline at end of file diff --git a/source/funkin/backend/utils/TranslationUtil.hx b/source/funkin/backend/utils/TranslationUtil.hx new file mode 100644 index 00000000..185d306a --- /dev/null +++ b/source/funkin/backend/utils/TranslationUtil.hx @@ -0,0 +1,370 @@ +package funkin.backend.utils; + +import funkin.backend.utils.IniUtil; +import funkin.backend.assets.TranslatedAssetLibrary; +import funkin.backend.assets.ModsFolder; +import openfl.utils.Assets; +import haxe.io.Path; +import haxe.xml.Access; +import haxe.Exception; +import funkin.backend.utils.translations.FormatUtil; + +/** + * The class used for translations based on the XMLs inside the translations folders. + * + * Made by @NexIsDumb originally for the Poldhub mod and improved thanks to him and @NeeEoo. + */ +@:allow(funkin.backend.assets.TranslatedAssetLibrary) +final class TranslationUtil +{ + /** + * The current language selected translation map. + * + * It'll never be `null`. + */ + public static var stringMap(default, set):Map<String, IFormatInfo> = []; + + /** + * The alternative language selected translation map. + * It is filled with the default language. + * + * This is filled in if the current language is not the same as the default language. + * Its used when showMissingIds in the config is false (or is not present). + * + * It'll never be `null`. + **/ + public static var alternativeStringMap(default, set):Map<String, IFormatInfo> = []; + + /** + * Returns the current language config. + */ + public static var config:Map<String, String> = []; + /** + * Returns the current language. + */ + public static var curLanguage(get, set):String; + /** + * Returns the current language name. + */ + public static var curLanguageName(get, set):String; + + /** + * Returns if the current language is the default one (`DEFAULT_LANGUAGE`). + */ + public static var isDefaultLanguage(get, never):Bool; + + /** + * Returns if any translation are loaded. + */ + public static var isLanguageLoaded(get, never):Bool; + + /** + * Returns an array has a list of the languages that were found. + */ + public static var foundLanguages:Array<String> = []; + + // Private + private static inline var LANG_FOLDER:String = "languages"; + 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, Map<String, String>> { + return [Flags.DEFAULT_LANGUAGE => getDefaultConfig(Flags.DEFAULT_LANGUAGE)]; + } + @:noUsing private static inline function getDefaultConfig(name:String):Map<String, String> { + return ["name" => getLanguageName(name), "credits" => "", "version" => "1.0.0"]; + } + + /** + * Updates the language. + * Also changes the translations map. + * + * If `name` is `null`, its gonna use the current language. + * If `name` is not `null`, it will load the translations for the given language. + */ + public static function setLanguage(?name:String):Void { + #if TRANSLATIONS_SUPPORT + if(name == null) name = curLanguage; + if(!langConfigs.exists(name)) name = Flags.DEFAULT_LANGUAGE; + if(!langConfigs.exists(name)) name = foundLanguages[0]; + + if(curLanguage != name) { + Logs.traceColored([ + Logs.getPrefix("Language"), + Logs.logText("Changing saved language to: "), + Logs.logText('${getLanguageName(name)} ($name)', GREEN) + ], INFO); + // todo: make this into a notification system for the user + // notification would be in previous language + // but only if the user didnt manually change it. + curLanguage = name; + } else { + Logs.traceColored([ + Logs.getPrefix("Language"), + Logs.logText("Setting Language To: "), + Logs.logText('${getLanguageName(name)} ($name)', GREEN) + ], VERBOSE); + } + + for(mod in ModsFolder.getLoadedModsLibs(false)) + if(mod is TranslatedAssetLibrary) + cast(mod, TranslatedAssetLibrary).langFolder = name; + + config = getConfig(name); + stringMap = loadLanguage(name); + alternativeStringMap = name == Flags.DEFAULT_LANGUAGE || config.get("showMissingIds").getDefault("false") == "true" ? [] : loadLanguage(Flags.DEFAULT_LANGUAGE); + #end + } + + /** + * This is for checking and getting a translation, `defString` it's just the string that gets returned just in case it won't find the translation OR the current language selected is ``DEFAULT_LANGUAGE``. + * + * If `id` is `null` then it's gonna search using `defString`. + */ + public static inline function get(?id:String, ?params:Array<Dynamic>, ?def:String):String + return getRaw(id, def).format(params); + + public static inline function translate(?id:String, ?params:Array<Dynamic>, ?def:String):String + return get(id, params, def); + + public static inline function translateDiff(?id:String, ?params:Array<Dynamic>):String + return get("diff." + id.toLowerCase(), params, id); + + public static function exists(id:String):Bool { + #if TRANSLATIONS_SUPPORT + for (map in [stringMap, alternativeStringMap]) if (map.exists(id)) return true; + #end + return false; + } + + public static function getRaw(id:String, ?def:String):IFormatInfo + { + #if TRANSLATIONS_SUPPORT + for (map in [stringMap, alternativeStringMap]) if (map.exists(id)) + return map.get(id); + #end + + if(def != null) + return FormatUtil.get(def); + + return FormatUtil.getStr("{" + id + "}"); + + /*if(curLanguage == Flags.DEFAULT_LANGUAGE) { + return FormatUtil.getStr("{" + id + "}"); + } else { + + } + + return FormatUtil.get(showMissingIds ? "{" + id + "}" : id);*/ + } + + /** + * Formats a normal string into an ID for translations. + * + * Example: `Resume Song` => `resumeSong` + */ + public static function raw2Id(str:String):String + { + str = str.trim().toLowerCase(); + return [for(i => s in str.split(" ")) + i != 0 ? s.charAt(0).toUpperCase() + s.substr(1) : s + ].join(""); + } + + public static inline function isBlacklisted(lang:String):Bool + return Flags.BLACKLISTED_LANGUAGES.contains(lang); + + public static inline function isWhitelisted(lang:String):Bool + return Flags.WHITELISTED_LANGUAGES.length == 0 || Flags.WHITELISTED_LANGUAGES.contains(lang); + + public static inline function isAllowed(lang:String):Bool + return isWhitelisted(lang) && !isBlacklisted(lang); + + /** + * Returns an array that specifies which languages were found. + */ + public static function findAllLanguages():Void + { + #if TRANSLATIONS_SUPPORT + foundLanguages = []; + nameMap = getDefaultNameMap(); + langConfigs = getDefaultLangConfigs(); + var mainPath:String = translationsMain(""); + var langName:String = null; + for (lang in Paths.assetsTree.getFolders("assets/" + mainPath)) { + if (!isAllowed(lang)) continue; + + var path:String = Path.join([mainPath, lang, "config.ini"]); + var config = getDefaultConfig(lang); + + if(Assets.exists(path)) { + var c = IniUtil.parseAsset(path); + for (i => v in c) + for (key => value in v) + 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") { + config["name"] = Path.withoutExtension(file); + break; + } + } + } + langName = config["name"]; + nameMap.set(lang, langName); + langConfigs.set(lang, config); + foundLanguages.push('$lang/$langName'); + } + + // Ensure that the default language is always first + var defaultName = Flags.DEFAULT_LANGUAGE + "/" + getLanguageName(Flags.DEFAULT_LANGUAGE); + if(foundLanguages.contains(defaultName)) foundLanguages.remove(defaultName); + foundLanguages.insert(0, defaultName); + + if(!nameMap.exists(curLanguage)) curLanguage = Flags.DEFAULT_LANGUAGE; + Logs.trace("Found languages: " + foundLanguages.join(", "), "Language"); + #end + } + + /** + * Returns a map of translations based on its XML. + */ + public static function loadLanguage(lang:String):Map<String, IFormatInfo> + { + #if TRANSLATIONS_SUPPORT + FormatUtil.clear(); // Clean up the format cache + var mainPath:String = translationsMain(lang); + var leMap:Map<String, IFormatInfo> = []; + var translations:Array<TranslationPair> = []; + + final NODE_NAMES = ["text", "trans", "lang", "string", "str"]; + function parseXml(xml:Access, prefix:String = "") { + for(node in xml.elements) { + if (node.name == "group") // Cosmetic name + parseXml(node, prefix + (node.has.prefix ? node.att.prefix : "")); + else if(NODE_NAMES.contains(node.name)) + translations.push({ + prefix: prefix, + node: node + }); + } + } + + // todo make it load the default languages in a second string map + + for(mod in ModsFolder.getLoadedModsLibs(true)) for(file in mod.getFiles("assets/" + mainPath).sortAlphabetically().map((v)->'$mainPath/$v')) { + if(Path.extension(file).toLowerCase() != "xml") continue; + + // Parse the XML + var xml:Access = null; + try xml = new Access(Xml.parse(Assets.getText("assets/" + file))) + catch(e) Logs.error('Error while parsing $file: ${Std.string(e)}', "Language"); + + if (xml == null) continue; + if (!xml.hasNode.language) { + Logs.warn('File $file requires a <language> root element.', "Language"); + continue; + } + + var langNode = xml.node.language; + var prefix = langNode.getAtt("prefix").getDefault(""); + + //if (langNode.has.name) { + // getConfig(lang).set("name", langNode.att.name); + //} + + parseXml(langNode, prefix); + } + + for(pair in translations) { + var node = pair.node; + if (!node.has.id) { + Logs.warn('A <${node.name}> node requires an ID attribute.', "Language"); + continue; + } + var prefix = pair.prefix; + + var id = prefix + node.att.id; + + if(leMap.exists(id)) continue; + var value:String = node.has.string ? node.att.string : node.innerData; + if(node.getAtt("notrim").getDefault("true") != "true") value = value.trim(); + // make it so you can escape the backslash + value = value.replace("\\n", "\n").replace("\r", ""); // remove stupid windows line breaks and convert newline literals to newlines + leMap.set(id, FormatUtil.get(value)); + //leMap.set(id, FormatUtil.getStr("{" + id + "}")); + //Logs.trace("Added " + id + " -> `" + value + "`", "Language"); + } + + return leMap; + #else + return []; + #end + } + + // Utils + + public static function getLanguageName(lang:String):String { + return nameMap.exists(lang) ? nameMap.get(lang) : lang; + } + + public static function getLanguageFromName(name:String):String { + var reverseMap = new Map<String, String>(); + for(key => val in nameMap) reverseMap.set(val, key); + return reverseMap.exists(name) ? reverseMap.get(name) : name; + } + + public static function getConfig(lang:String):Map<String, String> { + return langConfigs.exists(lang) ? langConfigs.get(lang) : getDefaultConfig(lang); + } + + public static inline function isShowingMissingIds():Bool { + return Lambda.count(alternativeStringMap) > 0; + } + + public static inline function translationsMain(key:String):String + return '$LANG_FOLDER/$key'; + + public static inline function translations(key:String):String + return translationsMain('$curLanguage/$key'); + + // getters & setters + + @:noCompletion private static function set_stringMap(value:Map<String, IFormatInfo>):Map<String, IFormatInfo> { + if (value == null) value = []; + return stringMap = value; + } + + @:noCompletion private static function set_alternativeStringMap(value:Map<String, IFormatInfo>):Map<String, IFormatInfo> { + if (value == null) value = []; + return alternativeStringMap = value; + } + + @:noCompletion private static function get_curLanguage():String { + return Options.language; + } + @:noCompletion private static function set_curLanguage(value:String):String { + return Options.language = value; + } + + @:noCompletion private static function get_curLanguageName():String { + return getLanguageName(Options.language); + } + @:noCompletion private static function set_curLanguageName(value:String):String { + return Options.language = getLanguageFromName(value); + } + + @:noCompletion private static function get_isDefaultLanguage():Bool + return Options.language == Flags.DEFAULT_LANGUAGE; + + @:noCompletion private static function get_isLanguageLoaded():Bool + return Lambda.count(stringMap) > 0 || isShowingMissingIds(); +} + +@:structInit +class TranslationPair { + public var prefix:String; + public var node:Access; +} \ No newline at end of file diff --git a/source/funkin/backend/utils/WindowUtils.hx b/source/funkin/backend/utils/WindowUtils.hx index 065a1573..706c36e4 100644 --- a/source/funkin/backend/utils/WindowUtils.hx +++ b/source/funkin/backend/utils/WindowUtils.hx @@ -57,6 +57,17 @@ final class WindowUtils { updateTitle(); } + /** + * Sets the window title and icon. + * @param title The title to set. + * @param image The image to set as the icon. + **/ + public static inline function setWindow(?name:String, ?image:String) + { + Lib.application.window.setIcon(lime.graphics.Image.fromBytes(Assets.getBytes(Flags.MOD_ICON != null ? Flags.MOD_ICON : image))); + title = Flags.MOD_NAME != null ? Flags.MOD_NAME : title; + } + /** * Updates the window title to have the current title and prefix/suffix. **/ diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx index 8fbcc793..2199b966 100644 --- a/source/funkin/backend/utils/XMLUtil.hx +++ b/source/funkin/backend/utils/XMLUtil.hx @@ -67,7 +67,7 @@ final class XMLUtil { */ public static function applyXMLProperty(object:Dynamic, property:Access):ErrorCode { if (!property.has.name || !property.has.type || !property.has.value) { - Logs.trace('Failed to apply XML property: XML Element is missing name, type, or value attributes.', WARNING); + Logs.warn('Failed to apply XML property: XML Element is missing name, type, or value attributes.'); return MISSING_PROPERTY; } @@ -101,7 +101,7 @@ final class XMLUtil { if(isPath) { str += ' (Path: ${property.att.name})'; } - Logs.trace(str, WARNING); + Logs.warn(str); return REFLECT_ERROR; } return OK; @@ -114,12 +114,13 @@ final 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) { @@ -192,10 +193,10 @@ final class XMLUtil { if (node.has.updateHitbox && node.att.updateHitbox == "true") spr.updateHitbox(); if (node.has.zoomfactor) - spr.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(spr.zoomFactor); + spr.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefaultFloat(spr.zoomFactor); if (node.has.alpha) - spr.alpha = Std.parseFloat(node.getAtt("alpha")).getDefault(spr.alpha); + spr.alpha = Std.parseFloat(node.getAtt("alpha")).getDefaultFloat(spr.alpha); if(node.has.color) spr.color = FlxColor.fromString(node.getAtt("color")).getDefault(0xFFFFFFFF); @@ -239,10 +240,10 @@ final 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); } /** @@ -266,9 +267,9 @@ final class XMLUtil { if (anim.has.name) animData.name = anim.att.name; if (anim.has.type) animData.animType = XMLAnimType.fromString(anim.att.type, animData.animType); if (anim.has.anim) animData.anim = anim.att.anim; - if (anim.has.fps) animData.fps = Std.parseFloat(anim.att.fps).getDefault(animData.fps); - if (anim.has.x) animData.x = Std.parseFloat(anim.att.x).getDefault(animData.x); - if (anim.has.y) animData.y = Std.parseFloat(anim.att.y).getDefault(animData.y); + if (anim.has.fps) animData.fps = Std.parseFloat(anim.att.fps).getDefaultFloat(animData.fps); + if (anim.has.x) animData.x = Std.parseFloat(anim.att.x).getDefaultFloat(animData.x); + if (anim.has.y) animData.y = Std.parseFloat(anim.att.y).getDefaultFloat(animData.y); if (anim.has.loop) animData.loop = anim.att.loop == "true"; if (anim.has.forced) animData.forced = anim.att.forced == "true"; if (anim.has.indices) animData.indices = CoolUtil.parseNumberRange(anim.att.indices); diff --git a/source/funkin/backend/utils/ZipUtil.hx b/source/funkin/backend/utils/ZipUtil.hx index 0a8bca20..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; @@ -135,12 +136,12 @@ final 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 @@ final 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/translations/FormatUtil.hx b/source/funkin/backend/utils/translations/FormatUtil.hx new file mode 100644 index 00000000..300eef1e --- /dev/null +++ b/source/funkin/backend/utils/translations/FormatUtil.hx @@ -0,0 +1,110 @@ +package funkin.backend.utils.translations; + +/** + * The class used to format strings based on parameters. + * + * For example if the parameter list is just an `Int` which is `9`, `You have been blue balled {0} times` becomes `You have been blue balled 9 times`. + */ +final class FormatUtil { + private static var cache:Map<String, IFormatInfo> = new Map(); + private static var cacheStr:Map<String, IFormatInfo> = new Map(); + + public static function get(id:String):IFormatInfo { + if (cache.exists(id)) + return cache.get(id); + + var fi:IFormatInfo = ParamFormatInfo.returnOnlyIfValid(id); + if(fi == null) fi = new StrFormatInfo(id); + cache.set(id, fi); + return fi; + } + + public static function getStr(id:String):IFormatInfo { + if (cacheStr.exists(id)) + return cacheStr.get(id); + + var fi:IFormatInfo = new StrFormatInfo(id); + cacheStr.set(id, fi); + return fi; + } + + public inline static function clear():Void { + cache.clear(); + cacheStr.clear(); + } +} + +class StrFormatInfo implements IFormatInfo { + public var string:String; + + public function new(str:String) { + this.string = str; + } + + public function format(params:Array<Dynamic>):String { + return string; + } + + public function toString():String { + return "StrFormatInfo(" + string + ")"; + } +} + +// TODO: add support for @:({0}==1?(Hello):(World)) +class ParamFormatInfo implements IFormatInfo { + public var strings:Array<String> = []; + public var indexes:Array<Int> = []; + + public function new(str:String) { + var i = 0; + + while (i < str.length) { + var fi = str.indexOf("{", i); // search from the start of i + + if (fi == -1) { + // if there are no more parameters, just add the rest of the string + this.strings.push(str.substring(i)); + break; + } + + var fe = str.indexOf("}", fi); + + this.strings.push(str.substring(i, fi)); + this.indexes.push(Std.parseInt(str.substring(fi+1, fe))); + i = fe + 1; + } + } + + public static function isValid(str:String):Bool { + var fi = new ParamFormatInfo(str); + return fi.indexes.length > 0; + } + public static function returnOnlyIfValid(str:String):IFormatInfo { + var fi = new ParamFormatInfo(str); + return fi.indexes.length > 0 ? fi : null; + } + + public function format(params:Array<Dynamic>):String { + if (params == null) params = []; + + var lenStr = strings.length; + var lenInd = indexes.length; + + var arr = new haxe.ds.Vector<String>(lenStr + ((lenStr < lenInd) ? lenStr : lenInd)); + var i = 0; + for (idx=>s in strings) { + arr[i++] = s; + if (idx < lenInd) + arr[i++] = Std.string(params[indexes[idx]]); + } + return arr.join(""); + } + + public function toString():String { + return 'ParamFormatInfo([${strings.join(", ")}] [${indexes.join(", ")}])'; + } +} + +interface IFormatInfo { + public function format(params:Array<Dynamic>):String; +} \ No newline at end of file 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 7a38ef7b..93d85992 100644 --- a/source/funkin/editors/DebugOptions.hx +++ b/source/funkin/editors/DebugOptions.hx @@ -26,50 +26,55 @@ class DebugOptions extends TreeMenu { class DebugOptionsScreen extends OptionsScreen { public override function new() { - super("Debug Options", "Use this menu to change debug options."); + prefix = "DebugOptions."; + super(translate("title"), translate("desc"), prefix); #if windows add(new TextOption( - "Show Console", - "Select this to show the debug console, which contains log information about the game.", + getName("showConsole"), + getDesc("showConsole"), function() { 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...)", + getName("editorsResizable"), + getDesc("editorsResizable"), "editorsResizable")); add(new Checkbox( - "Editor SFXs", - "If checked, will play sound effects when working on editors (ex: will play SFXs when checking checkboxes...)", + getName("bypassEditorsResize"), + getDesc("bypassEditorsResize"), + "bypassEditorsResize")); + add(new Checkbox( + getName("editorSFX"), + getDesc("editorSFX"), "editorSFX")); add(new Checkbox( - "Pretty Print", - "If checked, the saved files from the editor will be formatted to be easily viewable (does not apply to XMLs...)", + getName("editorPrettyPrint"), + getDesc("editorPrettyPrint"), "editorPrettyPrint")); add(new Checkbox( - "Intensive Blur", - "If checked, will use more intensive blur that may be laggier but look better.", + getName("intensiveBlur"), + getDesc("intensiveBlur"), "intensiveBlur")); add(new Checkbox( - "Editor Autosaves", - "If checked, this will autosave your files in the editor, with the settings listed below.", + getName("charterAutoSaves"), + getDesc("charterAutoSaves"), "charterAutoSaves")); add(new NumOption( - "Autosaving Time", - "This controls how often the editor will autosave your file (in seconds...)", + getName("charterAutoSaveTime"), + getDesc("charterAutoSaveTime"), 60, 60*10, 30, "charterAutoSaveTime" )); add(new NumOption( - "Save Warning Time", - "This controls how long the editor will warn you before it autosaves (in seconds..., 0 to disable)", + getName("charterAutoSaveWarningTime"), + getDesc("charterAutoSaveWarningTime"), 0, 15, 1, "charterAutoSaveWarningTime" )); add(new Checkbox( - "Autosaves Folder", - "If checked, this will autosave your file in a separate folder with a time stamp instead of overriding your current file. (song/autosaves/)", + getName("charterAutoSavesSeparateFolder"), + getDesc("charterAutoSavesSeparateFolder"), "charterAutoSavesSeparateFolder")); } } \ No newline at end of file diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx index b77f7226..ff349203 100644 --- a/source/funkin/editors/EditorPicker.hx +++ b/source/funkin/editors/EditorPicker.hx @@ -6,7 +6,13 @@ import flixel.math.FlxPoint; class EditorPicker extends MusicBeatSubstate { public var bg:FlxSprite; + // Name is for backwards compatibility, don't use it, use id instead public var options:Array<Editor> = [ + { + name: "Alphabet Editor", + id: "alphabet", + state: funkin.editors.alphabet.AlphabetSelection + }, { name: "Chart Editor", id: "chart", @@ -22,7 +28,7 @@ class EditorPicker extends MusicBeatSubstate { id: "stage", state: funkin.editors.stage.StageSelection }, - #if debug + #if (debug || debug_ui) { name: "UI Debug State", id: "uiDebug", @@ -74,7 +80,8 @@ class EditorPicker extends MusicBeatSubstate { optionHeight = FlxG.height / options.length; for(k=>o in options) { - var spr = new EditorPickerOption(o.name, o.id, optionHeight); + var visualName = (o.id != null) ? TU.translate("editor." + o.id + ".name") : o.name; + var spr = new EditorPickerOption(visualName, o.id, optionHeight); spr.y = k * optionHeight; add(spr); sprites.push(spr); @@ -176,13 +183,16 @@ class EditorPickerOption extends FlxTypedSpriteGroup<FlxSprite> { FlxG.mouse.visible = true; iconSpr = new FlxSprite(); - iconSpr.loadGraphic(Paths.image('editors/icons/$iconID')); + if(iconID != null) + iconSpr.loadGraphic(Paths.image('editors/icons/$iconID')); + else + iconSpr.exists = false; iconSpr.antialiasing = true; iconSpr.setUnstretchedGraphicSize(110, 110, false); iconSpr.x = 25 + ((height - iconSpr.width) / 2); iconSpr.y = (height - iconSpr.height) / 2; - label = new Alphabet(25 + iconSpr.width + 25, 0, name, true); + label = new Alphabet(25 + iconSpr.width + 25, 0, name, "bold"); label.y = (height - label.height) / 2; selectionBG = new FlxSprite().makeGraphic(1, 1, -1); diff --git a/source/funkin/editors/EditorTreeMenu.hx b/source/funkin/editors/EditorTreeMenu.hx index 6a22e54e..7bf5a0fb 100644 --- a/source/funkin/editors/EditorTreeMenu.hx +++ b/source/funkin/editors/EditorTreeMenu.hx @@ -7,6 +7,7 @@ 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(); @@ -18,16 +19,16 @@ class EditorTreeMenu extends TreeMenu { 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..3a903480 --- /dev/null +++ b/source/funkin/editors/ModConfigWarning.hx @@ -0,0 +1,78 @@ +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" +MOD_AUTHOR="YOU/YOUR TEAM HERE" +MOD_VERSION="YOUR MOD\'S VERSION 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.png" + +[Flags] +DISABLE_WARNING_SCREEN=true +# Set this to false if you want to bring back the warning state (prior to 1.0.0) +# NOTE: Beta warning state has been renamed from BetaWarningState.hx to WarningState.hx + +[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 b475e3e2..df67a974 100644 --- a/source/funkin/editors/SaveWarning.hx +++ b/source/funkin/editors/SaveWarning.hx @@ -21,15 +21,15 @@ class SaveWarning { if (warningFunc != null) warningFunc(closingWindow); if (FlxG.state != null && FlxG.state is UIState) { - FlxG.state.openSubState(new UIWarningSubstate("Unsaved Changes!", "Your changes will be lost if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?", + FlxG.state.openSubState(new UIWarningSubstate(TU.translate("saveWarning.warningTitle"), TU.translate("saveWarning.warningDesc"), [ { - label: "Cancel", + label: TU.translate("saveWarning.cancel"), color: 0x969533, onClick: function (_) {if (closingWindow) WindowUtils.resetClosing();} }, { - label: closingWindow ? "Exit Game" : "Exit To Menu", + label: closingWindow ? TU.translate("saveWarning.exitGame") : TU.translate("saveWarning.exitToMenu"), color: 0x969533, onClick: function(_) { if (!closingWindow) { @@ -37,12 +37,12 @@ class SaveWarning { if (closingWindow) WindowUtils.resetClosing(); } else { WindowUtils.preventClosing = false; WindowUtils.resetClosing(); - Sys.exit(0); + openfl.system.System.exit(0); } } }, { - label: closingWindow ? "Save & Exit Game" : "Save & Exit To Menu", + label: closingWindow ? TU.translate("saveWarning.saveAndExitGame") : TU.translate("saveWarning.saveAndExitToMenu"), color: 0x969533, onClick: function(_) { if (saveFunc != null) saveFunc(); @@ -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/alphabet/AlphabetEditor.hx b/source/funkin/editors/alphabet/AlphabetEditor.hx new file mode 100644 index 00000000..630fada1 --- /dev/null +++ b/source/funkin/editors/alphabet/AlphabetEditor.hx @@ -0,0 +1,499 @@ +package funkin.editors.alphabet; + +import flixel.math.FlxPoint; +import flixel.util.FlxColor; +import funkin.backend.system.framerate.Framerate; +import funkin.backend.utils.XMLUtil.AnimData; +import funkin.editors.ui.UIContextMenu.UIContextMenuOption; +import funkin.game.Character; +import haxe.xml.Access; +import haxe.xml.Printer; + +@:access(funkin.menus.ui.Alphabet) +class AlphabetEditor extends UIState { + static var __typeface:String; + + public static var instance(get, null):AlphabetEditor; + + private static inline function get_instance() + return FlxG.state is AlphabetEditor ? cast FlxG.state : null; + + public var topMenu:Array<UIContextMenuOption>; + public var topMenuSpr:UITopMenu; + + public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>(); + + var editorCamera:FlxCamera; + var uiCamera:FlxCamera; + + public function new(typeface:String) { + super(); + if (typeface != null) __typeface = typeface; + } + + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("editor.alphabet." + id, args); + + public var brokenWarning:UIText; + public var tape:Alphabet; + public var bigLetter:Alphabet; + public var curLetter:Int = 0; + public var targetX:Float = 0; + + public var queueReorder:Bool = false; + public var componentList:UIButtonList<ComponentButton>; + + public var infoWindow:GlyphInfoWindow; + public var curSelectedComponent:AlphabetComponent = null; + public var curSelectedData:AlphabetLetterData = null; + public var outlineIdx:Int = -1; + + public var defaultTmr:Float = 0.0; + public var charsForDefault:Array<Array<String>> = []; + + public override function create() { + super.create(); + + WindowUtils.suffix = " (" + translate("name") + ")"; + SaveWarning.selectionClass = AlphabetSelection; + SaveWarning.saveFunc = () -> {_file_save(null);}; + + topMenu = [ + { + label: translate("topBar.file"), + childs: [ + { + label: translate("file.save"), + keybind: [CONTROL, S], + onSelect: _file_save + }, + { + label: translate("file.saveAs"), + keybind: [CONTROL, SHIFT, S], + onSelect: _file_saveas + }, + null, + { + label: translate("file.exit"), + onSelect: _file_exit + } + ] + // TODO: add more options + }, + { + label: translate("topBar.edit"), + childs: [ + { + label: translate("edit.undo"), + // TODO: add undo + }, + { + label: translate("edit.redo"), + // TODO: add redo + }, + { + label: "Edit Main Data", // TODO: add translations + onSelect: _edit_main + } + ] + }, + { + label: translate("topBar.glyph"), + childs: [ + { + label: translate("glyph.newGlyph"), + //onSelect: _glyph_new + }, + { + label: translate("glyph.editGlyph"), + //onSelect: _glyph_edit + }, + { + label: translate("glyph.deleteGlyph"), + //onSelect: _glyph_delete + } + ] + }, + { + label: translate("topBar.view"), + childs: [ + { + label: translate("view.zoomIn"), + keybind: [CONTROL, NUMPADPLUS], + //onSelect: _view_zoomin + }, + { + label: translate("view.zoomOut"), + keybind: [CONTROL, NUMPADMINUS], + //onSelect: _view_zoomout + }, + { + label: translate("view.resetZoom"), + keybind: [CONTROL, NUMPADZERO], + //onSelect: _view_zoomreset + }, + ] + }, + { + label: "Tape", + childs: [ + { + label: "Move Tape Left", + keybind: [LEFT], + onSelect: _tape_left + }, + { + label: "Move Tape Right", + keybind: [RIGHT], + onSelect: _tape_right + } + ] + } + ]; + + editorCamera = FlxG.camera; + + uiCamera = new FlxCamera(); + uiCamera.bgColor = 0; + + FlxG.cameras.add(uiCamera, false); + + var bg = new FlxSprite(0, 0).makeSolid(Std.int(FlxG.width + 100), Std.int(FlxG.height + 100), 0xFF7f7f7f); + bg.cameras = [editorCamera]; + bg.scrollFactor.set(); + bg.screenCenter(); + add(bg); + + topMenuSpr = new UITopMenu(topMenu); + topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; + + tape = new Alphabet(0, 70, "", __typeface); + tape.alignment = CENTER; + tape.renderMode = MONOSPACE; + add(tape); + + bigLetter = new Alphabet(0, 0, "", "<SKIP>"); + bigLetter.copyData(tape); + bigLetter.alignment = CENTER; + bigLetter.scale.set(4, 4); + // TODO: fix the offset issues, when not using updateHitbox + bigLetter.updateHitbox(); + bigLetter.screenCenter(); + add(bigLetter); + + tape.x = targetX; + + brokenWarning = new UIText(0, 550, FlxG.width, "", 32); + brokenWarning.alignment = CENTER; + brokenWarning.color = 0xFFFF6969; + add(brokenWarning); + + infoWindow = new GlyphInfoWindow(); + uiGroup.add(infoWindow); + + componentList = new UIButtonList<ComponentButton>(0, 720 - 170 - 30, 230, 170, "Components:", FlxPoint.get(230, 50), FlxPoint.get(0, 0), 0); + componentList.dragCallback = (button, oldID, newID) -> { + queueReorder = true; // not do it for every button reordered + } + componentList.addButton.callback = () -> { + curSelectedComponent = { + anim: "", + x: 0, + y: 0, + + shouldRotate: false, + angle: 0, + sin: 0, + cos: 1, + + scaleX: 1, + scaleY: 1, + + hasColorMode: false, + colorMode: bigLetter.colorMode + }; + curSelectedData.components.push(curSelectedComponent); + var newButton = new ComponentButton(curSelectedComponent); + componentList.add(newButton); + newButton.ID = componentList.buttons.members.length - 1; // readjust it because UIButtonList doesnt generate the id properly + findOutline(); + infoWindow.updateInfo(); + } + + updateTape(); + uiGroup.add(componentList); + + 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", ["Alphabet Editor", __typeface]); + } + + override function destroy() { + super.destroy(); + if(Framerate.isLoaded) { + Framerate.fpsCounter.alpha = 1; + Framerate.memoryCounter.alpha = 1; + Framerate.codenameBuildField.alpha = 1; + } + } + + public override function update(elapsed:Float) { + super.update(elapsed); + if (queueReorder) { + queueReorder = false; + var outlines = []; + var newList = []; + for (button in componentList.buttons.members) { + var com = button.component; + if (com.outIndex != null) { + var out = curSelectedData.components[com.outIndex]; + out.refIndex = newList.length; + com.outIndex = outlines.length; + outlines.push(out); + } + newList.push(com); + } + curSelectedData.components = outlines.concat(newList); + + findOutline(); + for (i in 0...curSelectedData.components.length) { + var anim = bigLetter.text + i; + bigLetter.animation.remove(anim); + tape.animation.remove(anim); + } + } + + if (currentFocus == null) { + if(FlxG.keys.justPressed.ANY) + UIUtil.processShortcuts(topMenu); + + if(curSelectedComponent != null) { + if(FlxG.keys.pressed.K) { + curSelectedComponent.y -= 100 * elapsed; + } + if(FlxG.keys.pressed.I) { + curSelectedComponent.y += 100 * elapsed; + } + if(FlxG.keys.pressed.L) { + curSelectedComponent.x -= 100 * elapsed; + } + if(FlxG.keys.pressed.J) { + curSelectedComponent.x += 100 * elapsed; + } + } + } + + + var lastIdx = Math.floor(defaultTmr); + defaultTmr += elapsed * 0.5; + if (lastIdx != (lastIdx = Math.floor(defaultTmr))) { + tape.text = ""; + for (def in charsForDefault) + tape.text += def[Std.int(lastIdx % def.length)] + " "; + tape.text += tape.manualLetters.join(" "); + } + tape.x = lerp(tape.x, targetX, 0.25); + } + + function updateTape() { + charsForDefault = [[], [], []]; + + for (i in 33...127) { + var letter = String.fromCharCode(i); + if (tape.manualLetters.contains(letter)) continue; + + var data = tape.fastGetData(letter); + for (i => com in data.components) { + if (tape.fastGetLetterAnim(letter, data, com, i) == null) + continue; + } + + if (i >= 'A'.code && i <= 'Z'.code && tape.defaults[CaseMode.UPPER] != null) + charsForDefault[CaseMode.UPPER].push(letter); + else if (i >= 'a'.code && i <= 'z'.code && tape.defaults[CaseMode.LOWER] != null) + charsForDefault[CaseMode.LOWER].push(letter); + else if (tape.defaults[CaseMode.NONE] != null) + charsForDefault[CaseMode.NONE].push(letter); + } + + var i = charsForDefault.length - 1; + while (i >= 0) { + if (charsForDefault[i].length <= 0) + charsForDefault.splice(i, 1); + --i; + } + + tape.text = ""; + for (def in charsForDefault) + tape.text += def[Math.floor(defaultTmr % def.length)] + " "; + tape.text += tape.manualLetters.join(" "); + changeLetter(0); + } + + function changeLetter(inc:Int) { + curLetter = CoolUtil.positiveModuloInt(curLetter + inc, tape.manualLetters.length + charsForDefault.length); + targetX = FlxG.width * 0.5 - tape.defaultAdvance * (0.5 + curLetter * 2); + bigLetter.text = (curLetter < charsForDefault.length) ? charsForDefault[curLetter][0] : tape.manualLetters[curLetter - charsForDefault.length]; + bigLetter.updateHitbox(); + bigLetter.screenCenter(); + + checkForFailed(); + + while (componentList.buttons.members.length > 0) + componentList.remove(componentList.buttons.members[0]); + for (i in curSelectedData.startIndex...curSelectedData.components.length) { + var newButton = new ComponentButton(curSelectedData.components[i]); + componentList.add(newButton); + newButton.ID = componentList.buttons.members.length - 1; // readjust it because UIButtonList doesnt generate the id properly + } + curSelectedComponent = (curSelectedData.components.length > 0) ? curSelectedData.components[curSelectedData.startIndex] : null; + findOutline(); + infoWindow.updateInfo(); + } + + public function findOutline() { + outlineIdx = -1; + for (i in curSelectedData.startIndex...curSelectedData.components.length) { + var com = curSelectedData.components[i]; + if (com.outIndex != null) + outlineIdx = com.outIndex; + if (com == curSelectedComponent) + break; + } + ++outlineIdx; + } + + public function checkForFailed() { + bigLetter.failedLetters.remove(bigLetter.text); + curSelectedData = bigLetter.fastGetData(bigLetter.text); + brokenWarning.text = ""; + bigLetter.draw(); + if (!bigLetter.failedLetters.contains(bigLetter.text)) + return false; + + for (i in 0...curSelectedData.components.length) { + var com = curSelectedData.components[i]; + if (!bigLetter.animation.exists(bigLetter.text + Std.string(i))) + brokenWarning.text += " Unable to find: " + com.anim + "\n"; + } + return true; + } + + function _tape_left(_) { + changeLetter(-1); + } + function _tape_right(_) { + changeLetter(1); + } + + function _file_save(_) { + #if sys + CoolUtil.safeSaveFile( + '${Paths.getAssetsRoot()}/data/alphabet/${__typeface}.xml', + ""//alphabet.buildXML() + ); + #else + _file_saveas(_); + #end + } + + function _file_saveas(_) { + openSubState(new SaveSubstate(""/*alphabet.buildXML()*/, { + defaultSaveFile: '${__typeface}.xml' + })); + } + + function _file_exit(_) { + /*if (undos.unsaved) SaveWarning.triggerWarning(); + else */FlxG.switchState(new AlphabetSelection()); + } + + function _edit_main(_) { + FlxG.state.openSubState(new AlphabetMainDataScreen()); + } +} + +/* + +/===============\ +| Components | +|===============| +|[ Component 1 ]| +|[ Component 2 ]| +|[ Component 3 ]| +|[ Component 4 ]| +|[ Component 5 ]| +|[ Add component]| +\===============/ + +*/ + +class ComponentButton extends UIButton { + public var component:AlphabetComponent; + + public var selected:Bool = false; + + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + + public function new(component:AlphabetComponent) { + super(0, 0, component.anim, function() { + AlphabetEditor.instance.curSelectedComponent = component; + AlphabetEditor.instance.findOutline(); + AlphabetEditor.instance.infoWindow.updateInfo(); + }, 230, 50); + this.component = component; + + deleteButton = new UIButton(bWidth - 32 - 10, 0, "", function() { + var data = AlphabetEditor.instance.curSelectedData; + + var outlineDrop = (component.outIndex != null) ? 1 : 0; + var componentIndex = data.components.indexOf(component); + for (i in 0...data.components.length) { + var nextCompon = data.components[i]; + var anim = AlphabetEditor.instance.bigLetter.text + i; + AlphabetEditor.instance.bigLetter.animation.remove(anim); + AlphabetEditor.instance.tape.animation.remove(anim); + + if (i > componentIndex && nextCompon.outIndex != null) { + --data.components[nextCompon.outIndex].refIndex; + nextCompon.outIndex -= outlineDrop; + } + } + + if (component.outIndex != null) { + data.components.splice(component.outIndex, 1); + --data.startIndex; + } + + data.components.remove(component); + AlphabetEditor.instance.curSelectedComponent = (AlphabetEditor.instance.curSelectedComponent == component) ? null : AlphabetEditor.instance.curSelectedComponent; + AlphabetEditor.instance.findOutline(); + AlphabetEditor.instance.infoWindow.updateInfo(); + + AlphabetEditor.instance.componentList.remove(this); + }, 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); + } + + override function update(elapsed:Float) { + super.update(elapsed); + + deleteButton.y = y + 10; + deleteIcon.x = deleteButton.x + (15/2); + deleteIcon.y = deleteButton.y + 8; + } +} \ No newline at end of file diff --git a/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx b/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx new file mode 100644 index 00000000..2c4b08fa --- /dev/null +++ b/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx @@ -0,0 +1,214 @@ +package funkin.editors.alphabet; + +import flixel.math.FlxRect; +import funkin.editors.alphabet.AlphabetEditor; +import funkin.menus.ui.Alphabet; + +using StringTools; + +// TODO: add translations +class AlphabetMainDataScreen extends UISubstateWindow { + var bigLetter:Alphabet; + var tape:Alphabet; + + public var saveButton:UIButton; + public var closeButton:UIButton; + public var helpButton:UIButton; + + public var fpsStepper:UINumericStepper; + public var advanceStepper:UINumericStepper; + public var lineGapStepper:UINumericStepper; + + public var caseDropdown:UIDropDown; + public var colorDropdown:UIDropDown; + + public var regTextBox:UITextBox; + public var upperTextBox:UITextBox; + public var lowerTextBox:UITextBox; + + public var targetPercent:Float = 0.0; + public var helpBG:UISliceSprite; + public var helpTxt:UIText; + public var helpScroll:UIScrollBar; + + public override function create() { + bigLetter = AlphabetEditor.instance.bigLetter; + tape = AlphabetEditor.instance.tape; + + // temporary until translated + var helpDesc = +"FPS - Frames per second. You get it. +Advance - Actually a backup advance value for missing characters and spaces. +Line Gap - How many pixels to move down when a new line is created. + +Force Casing - Whether to force the text casing to uppercase, lowercase, or neither. +Color Mode - How to color the alphabet. +TINT muliplies the channels, perfect for white letters. +OFFSETS adds to the channels, perfect for black letters. +NONE simply disables coloring. + +Backup Prefixes - What prefix to use if a glyph is not predefined. +Uppercase and Lowercase will default to regular if it is left blank. +LETTER will be substituted with the provided glyph. +UPPERLETTER will be substituted with the uppercase glyph. +LOWERLETTER will be substituted with the lowercase glyph. +UPPERLETTER and LOWERLETTTER can be used in any backup prefix."; + + winTitle = "Alphabet Properties"; + winWidth = 420; winHeight = 520; + + 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, "Edit Main Alphabet Data", 28)); + + fpsStepper = new UINumericStepper(title.x, title.y + title.height + 38, AlphabetEditor.instance.bigLetter.fps, 1, 2, 0.01, null, 120); + add(fpsStepper); + addLabelOn(fpsStepper, "FPS"); + + advanceStepper = new UINumericStepper(fpsStepper.x + 140, fpsStepper.y, AlphabetEditor.instance.bigLetter.defaultAdvance, 1, 2, 0, null, 120); + add(advanceStepper); + addLabelOn(advanceStepper, "Advance"); + + lineGapStepper = new UINumericStepper(advanceStepper.x + 140, advanceStepper.y, AlphabetEditor.instance.bigLetter.lineGap, 1, 2, 0, null, 120); + add(lineGapStepper); + addLabelOn(lineGapStepper, "Line Gap"); + + caseDropdown = new UIDropDown(fpsStepper.x, fpsStepper.y + fpsStepper.bHeight + 50, 206, 32, ["NONE", "UPPER", "LOWER"], tape.forceCase); + add(caseDropdown); + addLabelOn(caseDropdown, "Force Casing"); + + colorDropdown = new UIDropDown(caseDropdown.x + caseDropdown.bWidth + 20, caseDropdown.y, 206, 32, ["TINT", "OFFSETS", "NONE"], tape.colorMode); + add(colorDropdown); + addLabelOn(colorDropdown, "Color Mode"); + + var regPrefix = (tape.defaults[0] != null) ? tape.defaults[0].components[0].anim : ""; + regTextBox = new UITextBox(caseDropdown.x, caseDropdown.y + caseDropdown.bHeight + 50, regPrefix, 368); + add(regTextBox); + addLabelOn(regTextBox, "Backup Prefix (Regular)"); + + var upperPrefix = (tape.defaults[1] != null) ? tape.defaults[1].components[0].anim : ""; + upperTextBox = new UITextBox(regTextBox.x, regTextBox.y + regTextBox.bHeight + 35, upperPrefix, 368); + add(upperTextBox); + addLabelOn(upperTextBox, "Backup Prefix (Uppercase)"); + + var lowerPrefix = (tape.defaults[2] != null) ? tape.defaults[2].components[0].anim : ""; + lowerTextBox = new UITextBox(upperTextBox.x, upperTextBox.y + upperTextBox.bHeight + 35, lowerPrefix, 368); + add(lowerTextBox); + addLabelOn(lowerTextBox, "Backup Prefix (Lowercase)"); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { + saveInfo(); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() { + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + + helpButton = new UIButton(closeButton.x - 20, closeButton.y, "?", function() { + targetPercent = 1.0 - targetPercent; + }, 32); + add(helpButton); + helpButton.x -= helpButton.bWidth; + + helpBG = new UISliceSprite(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y, 340, windowSpr.bHeight, 'editors/ui/context-bg'); + helpBG.alpha = 0.0; + add(helpBG); + + helpTxt = new UIText(helpBG.x + 20, helpBG.y + 20, helpBG.bWidth - 60, helpDesc); + helpTxt.clipRect = new FlxRect(0, 0, helpTxt.width, helpBG.bHeight - 40); + helpTxt.alpha = 0.0; + add(helpTxt); + + helpScroll = new UIScrollBar(helpBG.x + helpBG.bWidth - 30, helpBG.y + 20, (helpTxt.height - helpTxt.clipRect.height), 0, (helpTxt.clipRect.height / helpTxt.height), 20, helpBG.bHeight - 40); + helpScroll.size *= helpScroll.height; + helpScroll.start -= helpScroll.size * 0.5; + helpScroll.alpha = 0.0; + helpScroll.onChange = function(v) { + helpTxt.clipRect.y = v; + helpTxt.offset.y = v; + helpTxt.clipRect = helpTxt.clipRect; + helpScroll.start = v - helpScroll.size * 0.5; + } + add(helpScroll); + } + + override function update(elapsed:Float) { + super.update(elapsed); + + helpBG.alpha = FlxMath.lerp(helpBG.alpha, targetPercent, elapsed * 15); + helpBG.x = windowSpr.x + windowSpr.bWidth - 20 + 40 * helpBG.alpha; + helpTxt.alpha = helpBG.alpha; + helpTxt.x = helpBG.x + 20; + helpScroll.alpha = helpScroll.thumb.alpha = helpScroll.thumbIcon.alpha = helpBG.alpha; + helpScroll.x = helpBG.x + helpBG.bWidth - 30; + } + + public function saveInfo() { + @:privateAccess fpsStepper.__onChange(fpsStepper.label.text); + @:privateAccess advanceStepper.__onChange(advanceStepper.label.text); + @:privateAccess lineGapStepper.__onChange(lineGapStepper.label.text); + + bigLetter.fps = tape.fps = fpsStepper.value; + bigLetter.defaultAdvance = tape.defaultAdvance = advanceStepper.value; + bigLetter.lineGap = tape.lineGap = lineGapStepper.value; + + bigLetter.forceCase = tape.forceCase = caseDropdown.index; + bigLetter.colorMode = tape.colorMode = colorDropdown.index; + + // give the letter loading another chance + tape.animation.destroyAnimations(); + bigLetter.animation.destroyAnimations(); + tape.failedLetters.splice(1, tape.failedLetters.length - 1); + + setDefault(0, regTextBox.label.text); + setDefault(1, upperTextBox.label.text); + setDefault(2, lowerTextBox.label.text); + } + + function setDefault(index:Int, name:String) { + tape.loaded[index].splice(0, tape.loaded[index].length); + if (name.trim() == "") { + tape.defaults[index] = null; + return; + } + + if (tape.defaults[index] != null) { + tape.defaults[index].components[0].anim = name; + trace(tape.defaults[index].components[0].anim); + return; + } + + final newData:AlphabetLetterData = { + isDefault: true, + advance: 0.0, + advanceEmpty: true, + components: [{ + anim: name, + + x: 0.0, + y: 0.0, + scaleX: 1.0, + scaleY: 1.0, + + shouldRotate: false, + angle: 0.0, + cos: 1.0, + sin: 0.0, + + hasColorMode: false, + colorMode: 0 + }], + startIndex: 0 + }; + tape.defaults[index] = newData; + } +} \ No newline at end of file diff --git a/source/funkin/editors/alphabet/AlphabetSelection.hx b/source/funkin/editors/alphabet/AlphabetSelection.hx new file mode 100644 index 00000000..1b8155ee --- /dev/null +++ b/source/funkin/editors/alphabet/AlphabetSelection.hx @@ -0,0 +1,89 @@ +package funkin.editors.alphabet; + +import haxe.xml.Access; +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 haxe.io.Path; + +class AlphabetSelection extends EditorTreeMenu +{ + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("editor.alphabet.selection." + id, args); + + public override function create() + { + bgType = "charter"; + super.create(); + + var modsList:Array<String> = []; + for(file in Paths.getFolderContent('data/alphabet/', true, BOTH)) // mods ? MODS : BOTH + if (Path.extension(file) == "xml") modsList.push(CoolUtil.getFilename(file)); + + var list:Array<OptionType> = [ + for (typeface in modsList) + new AlphabetIconOption(typeface, translate("acceptTypeface"), typeface, function() { + FlxG.switchState(new AlphabetEditor(typeface)); + }) + ]; + + var newChar = translate("newTypeface"); + 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) {}} + ])); + })); + + main = new OptionsScreen(TU.translate("editor.alphabet.name"), translate("desc"), list); + + DiscordUtil.call("onEditorTreeLoaded", ["Alphabet Editor"]); + } + + override function createPost() { + super.createPost(); + + main.changeSelection(1); + } +} + +class AlphabetIconOption extends TextOption { + public var iconSpr:FlxSprite; + + public function new(name:String, desc:String, typeface:String, callback:Void->Void) { + super(name, desc, callback); + + var xml = Xml.parse(Assets.getText(Paths.xml('alphabet/$typeface'))).firstElement(); + var spritesheet = null; + for(node in xml.elements()) { + if (node.nodeName == "spritesheet") { + spritesheet = node.firstChild().nodeValue; + break; + } + } + + var useColorOffsets = xml.get("useColorOffsets").getDefault("false") == "true"; + + + // todo fix crash if invalid spritesheet; + + iconSpr = new FlxSprite(); + iconSpr.frames = Paths.getFrames(spritesheet); + iconSpr.antialiasing = true; + var frameToUse = iconSpr.frames.frames[0]; + for(frame in iconSpr.frames.frames) { + if (frame.name.toUpperCase().startsWith("A")) { + frameToUse = frame; + break; + } + } + iconSpr.frame = frameToUse; + if(useColorOffsets) { + iconSpr.colorTransform.color = -1; + } + iconSpr.setPosition(90 - iconSpr.width - 20, (__text.height - iconSpr.height) / 2); + add(iconSpr); + } +} \ No newline at end of file diff --git a/source/funkin/editors/alphabet/GlyphInfoWindow.hx b/source/funkin/editors/alphabet/GlyphInfoWindow.hx new file mode 100644 index 00000000..766c0bca --- /dev/null +++ b/source/funkin/editors/alphabet/GlyphInfoWindow.hx @@ -0,0 +1,274 @@ +package funkin.editors.alphabet; + +import flixel.math.FlxAngle; + +class GlyphInfoWindow extends UIWindow { + public var prefixBox:UITextBox; + public var xBox:UINumericStepper; // LIVE + public var yBox:UINumericStepper; // MJWF + public var scaleXBox:UINumericStepper; + public var scaleYBox:UINumericStepper; + public var angleBox:UINumericStepper; + public var colorModeDrop:UIDropDown; + + public var outlineTitle:UIText; + public var outlineSep:UISprite; + public var outlineCheck:UICheckbox; + public var outlineBoxTitle:UIText; + public var outlineBox:UITextBox; + public var outlineOffTitle:UIText; + public var outlineXBox:UINumericStepper; + public var outlineYBox:UINumericStepper; + + public var targetPercent:Float = 0; + public var outlinePercent:Float = 0; + public var outlineAlphaMults:Array<Float>; + public var outlineItems:Array<FlxSprite>; + public var initialY:Array<Float> = []; + + var compon(get, never):AlphabetComponent; + var data(get, never):AlphabetLetterData; + + public function new() { + var width = 360; + var height = 255; + var margin = 30; + var itemMargin = 15; // there was already a buncha variables so i thought why not follow + var labelOffset = 24; + super(FlxG.width - width - margin, FlxG.height - height - margin, width, height, "Glyph Info"); + + function addLabelOn(ui:UISprite, text:String) { + var title = new UIText(ui.x, ui.y - labelOffset, 0, text); + members.push(title); + return title; + } + + prefixBox = new UITextBox(x + itemMargin, y + itemMargin + labelOffset * 2, "", 330); + prefixBox.onChange = function(val) { + var index = data.components.indexOf(compon); + compon.anim = val; + var anim = AlphabetEditor.instance.bigLetter.text + Std.string(index); + AlphabetEditor.instance.bigLetter.animation.remove(anim); + AlphabetEditor.instance.tape.animation.remove(anim); + AlphabetEditor.instance.checkForFailed(); + } + members.push(prefixBox); + addLabelOn(prefixBox, "Animation Prefix"); + + xBox = new UINumericStepper(prefixBox.x, prefixBox.y + prefixBox.bHeight + itemMargin + labelOffset, 0, 1, 2, null, null, 80); + xBox.onChange = valueSet.bind(xBox, function(val) { // kinda dumb but blame cne ui + compon.x = val; + }); + members.push(xBox); + + members.push(new UIText(xBox.x + xBox.bWidth - 2, xBox.y + 9, 0, ",", 22)); + + yBox = new UINumericStepper(xBox.x + xBox.bWidth + itemMargin, xBox.y, 0, 1, 2, null, null, 80); + yBox.onChange = valueSet.bind(yBox, function(val) { // kinda dumb but blame cne ui + compon.y = val; + }); + members.push(yBox); + addLabelOn(xBox, "Offset (x,y)"); + + scaleXBox = new UINumericStepper(yBox.x + yBox.bWidth + itemMargin * 2, yBox.y, 1, 0.1, 2, null, null, 80); + scaleXBox.onChange = valueSet.bind(scaleXBox, function(val) { // kinda dumb but blame cne ui + compon.scaleX = val; + if (outlineCheck.checked) + data.components[compon.outIndex].scaleX = val; + }); + members.push(scaleXBox); + + members.push(new UIText(scaleXBox.x + scaleXBox.bWidth - 2, scaleXBox.y + 9, 0, ",", 22)); + + scaleYBox = new UINumericStepper(scaleXBox.x + scaleXBox.bWidth + itemMargin, scaleXBox.y, 1, 0.1, 2, null, null, 80); + scaleYBox.onChange = valueSet.bind(scaleYBox, function(val) { // kinda dumb but blame cne ui + compon.scaleY = val; + if (outlineCheck.checked) + data.components[compon.outIndex].scaleY = val; + }); + members.push(scaleYBox); + addLabelOn(scaleXBox, "Scale (x,y)"); + + angleBox = new UINumericStepper(scaleYBox.x + scaleYBox.bWidth + itemMargin * 2, scaleYBox.y, 0, 1, 2, null, null, 80); + angleBox.onChange = valueSet.bind(angleBox, function(val) { // kinda dumb but blame cne ui + compon.shouldRotate = val != 0; + compon.angle = val; + compon.cos = Math.cos(val * FlxAngle.TO_RAD); + compon.sin = Math.sin(val * FlxAngle.TO_RAD); + + if (outlineCheck.checked) { + data.components[compon.outIndex].shouldRotate = compon.shouldRotate; + data.components[compon.outIndex].angle = compon.angle; + data.components[compon.outIndex].cos = compon.cos; + data.components[compon.outIndex].sin = compon.sin; + } + }); + members.push(angleBox); + addLabelOn(angleBox, "Angle"); + + colorModeDrop = new UIDropDown(xBox.x, xBox.y + xBox.bHeight + itemMargin + labelOffset, 145, 32, ["TINT", "OFFSETS", "NONE"]); + colorModeDrop.onChange = function(mode) { + compon.colorMode = mode; + @:privateAccess compon.hasColorMode = mode != AlphabetEditor.instance.tape.colorMode; + if (outlineCheck.checked) { + data.components[compon.outIndex].colorMode = mode; + @:privateAccess data.components[compon.outIndex].hasColorMode = compon.hasColorMode; + } + }; + members.push(colorModeDrop); + addLabelOn(colorModeDrop, "Color Mode"); + + outlineCheck = new UICheckbox(colorModeDrop.x + colorModeDrop.bWidth + itemMargin * 2, colorModeDrop.y + 4, "Use Outline?"); + outlineCheck.onChecked = function(val) { + targetPercent = val ? 1 : 0; + + for (i in 0...data.components.length) { + var anim = AlphabetEditor.instance.bigLetter.text + i; + AlphabetEditor.instance.bigLetter.animation.remove(anim); + AlphabetEditor.instance.tape.animation.remove(anim); + } + + if (val) { + compon.outIndex = AlphabetEditor.instance.outlineIdx; + + var newOutline:AlphabetComponent = { + refIndex: data.components.indexOf(compon), + anim: outlineBox.label.text, + + x: outlineXBox.value, + y: outlineYBox.value, + scaleX: compon.scaleX, + scaleY: compon.scaleY, + + shouldRotate: compon.shouldRotate, + angle: compon.angle, + cos: compon.cos, + sin: compon.sin, + + hasColorMode: compon.hasColorMode, + colorMode: compon.colorMode + }; + + data.components.insert(AlphabetEditor.instance.outlineIdx, newOutline); + ++data.startIndex; + } else { + compon.outIndex = null; + data.components.splice(AlphabetEditor.instance.outlineIdx - 1, 1); + --data.startIndex; + } + } + members.push(outlineCheck); + + outlineTitle = new UIText(colorModeDrop.x, colorModeDrop.y + colorModeDrop.bHeight + itemMargin, 0, "Outline Data"); + members.push(outlineTitle); + outlineSep = new UISprite(x + itemMargin, colorModeDrop.y + colorModeDrop.bHeight + itemMargin + 19); + outlineSep.makeSolid(width - itemMargin * 2, 1, 0x40FFFFFF); + members.push(outlineSep); + + outlineBox = new UITextBox(colorModeDrop.x, colorModeDrop.y + colorModeDrop.bHeight + itemMargin + labelOffset * 2 + 3, "", 190); + outlineBox.onChange = function(val) { + data.components[compon.outIndex].anim = val; + var anim = AlphabetEditor.instance.bigLetter.text + Std.string(compon.outIndex); + AlphabetEditor.instance.bigLetter.animation.remove(anim); + AlphabetEditor.instance.tape.animation.remove(anim); + AlphabetEditor.instance.checkForFailed(); + } + members.push(outlineBox); + outlineBoxTitle = addLabelOn(outlineBox, "Prefix"); + + outlineXBox = new UINumericStepper(outlineBox.x + outlineBox.bWidth + itemMargin * 2, outlineBox.y, 0, 1, 2, null, null, 80); + outlineXBox.onChange = valueSet.bind(outlineXBox, function(val) { // kinda dumb but blame cne ui + if (outlineCheck.checked) + data.components[compon.outIndex].x = val; + }); + members.push(outlineXBox); + + members.push(new UIText(outlineXBox.x + outlineXBox.bWidth - 2, outlineXBox.y + 9, 0, ",", 22)); + + outlineYBox = new UINumericStepper(outlineXBox.x + outlineXBox.bWidth + itemMargin, outlineXBox.y, 0, 1, 2, null, null, 80); + outlineYBox.onChange = valueSet.bind(outlineYBox, function(val) { // kinda dumb but blame cne ui + if (outlineCheck.checked) + data.components[compon.outIndex].y = val; + }); + members.push(outlineYBox); + outlineOffTitle = addLabelOn(outlineXBox, "Offset (x,y)"); + + for (mem in members) { + if (mem is FlxObject) + initialY.push(cast (mem, FlxObject).y); + } + outlineAlphaMults = [1.0, 0.9, 0.5, 0.15, 0.15, 0.5, 0.15, 0.15, 0.15, 0.15]; + outlineItems = [outlineTitle, outlineSep, outlineBoxTitle, outlineBox, outlineBox.label, outlineOffTitle, outlineXBox, outlineXBox.label, outlineYBox, outlineYBox.label]; + } + + override public function update(elapsed:Float) { + super.update(elapsed); + + outlinePercent = FlxMath.lerp(outlinePercent, targetPercent, elapsed * 10); + bHeight = Std.int(255 + 105 * outlinePercent); + y = FlxG.height - bHeight - 30; + + for (i in 0...outlineItems.length) + outlineItems[i].alpha = outlinePercent * FlxMath.lerp(outlineAlphaMults[i], 1, outlinePercent * outlinePercent) * alpha; + + var yIdxOff = 0; + for (i in 0...members.length) { + if (members[i] is FlxObject) + cast (members[i], FlxObject).y = initialY[i - yIdxOff] - (bHeight - 255); + else + ++yIdxOff; + } + } + + public function updateInfo() { + var com = compon; + if (com == null) { + for (item in members) { + if (item is UISprite) + cast(item, UISprite).selectable = false; + } + colorModeDrop.dropButton.selectable = false; + return; + } + + prefixBox.label.text = com.anim; + xBox.value = com.x; + yBox.value = com.y; + scaleXBox.value = com.scaleX; + scaleYBox.value = com.scaleY; + angleBox.value = com.angle; + @:privateAccess colorModeDrop.index = com.hasColorMode ? com.colorMode : AlphabetEditor.instance.tape.colorMode; + @:privateAccess colorModeDrop.label.text = colorModeDrop.options[colorModeDrop.index]; + + outlineCheck.checked = com.outIndex != null; + outlineBox.label.text = ""; + outlineXBox.value = 0; + outlineYBox.value = 0; + targetPercent = 0; + if (com.outIndex != null) { + var out = data.components[com.outIndex]; + outlineBox.label.text = out.anim; + outlineXBox.value = out.x; + outlineYBox.value = out.y; + targetPercent = 1; + } + + for (item in members) { + if (item is UISprite) + cast(item, UISprite).selectable = true; + } + colorModeDrop.dropButton.selectable = true; + } + + function valueSet(item:UINumericStepper, func:Dynamic, text:String) { + @:privateAccess item.__onChange(text); + func(item.value); + } + + function get_compon() { + return AlphabetEditor.instance.curSelectedComponent; + } + function get_data() { + return AlphabetEditor.instance.curSelectedData; + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimButton.hx b/source/funkin/editors/character/CharacterAnimButton.hx index 61182562..065a0d85 100644 --- a/source/funkin/editors/character/CharacterAnimButton.hx +++ b/source/funkin/editors/character/CharacterAnimButton.hx @@ -48,6 +48,9 @@ class CharacterAnimButton extends UIButton { public var foldableButtons:Array<FlxSprite> = []; public var closed:Bool = true; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("characterEditor.characterAnim." + id, args); + public function new(x:Float, y:Float, animData:AnimData, parent:CharacterAnimsWindow) { this.anim = animData.name; this.data = animData; @@ -76,13 +79,13 @@ class CharacterAnimButton extends UIButton { nameTextBox.onChange = (newName:String) -> {this.changeName(newName);}; members.push(nameTextBox); foldableButtons.push(nameTextBox); - addLabelOn(nameTextBox, "Name", 12); + addLabelOn(nameTextBox, translate("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); + addLabelOn(animTextBox, translate("anim"), 12); positionXStepper = new UINumericStepper(animTextBox.x, animTextBox.y+32+18, animData.x, 0.001, 2, null, null, 64, 22, true); positionXStepper.onChange = (text:String) -> { @@ -91,7 +94,7 @@ class CharacterAnimButton extends UIButton { }; members.push(positionXStepper); foldableButtons.push(positionXStepper); - addLabelOn(positionXStepper, "Position (X,Y)", 12); + addLabelOn(positionXStepper, translate("position"), 12); members.push(XYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 18)); foldableButtons.push(XYComma); @@ -111,7 +114,7 @@ class CharacterAnimButton extends UIButton { }; members.push(fpsStepper); foldableButtons.push(fpsStepper); - addLabelOn(fpsStepper, "FPS", 12); + addLabelOn(fpsStepper, translate("fps"), 12); loopedCheckbox = new UICheckbox(fpsStepper.x + 82 - 32 + 26, fpsStepper.y, "Looping?", animData.loop, 0, true); loopedCheckbox.onChecked = (newLooping:Bool) -> {this.changeLooping(newLooping);}; @@ -126,7 +129,7 @@ class CharacterAnimButton extends UIButton { } members.push(indicesTextBox); foldableButtons.push(indicesTextBox); - addLabelOn(indicesTextBox, "Indices (frames)", 12); + addLabelOn(indicesTextBox, translate("indices"), 12); animIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/character/anim-icons"), true, 16, 12); animIcon.animation.add("play", [0]); @@ -421,10 +424,12 @@ class CharacterAnimButton extends UIButton { public function toggleGhost() { if (valid && parent.ghosts.indexOf(anim) == -1) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTENABLE_SOUND)); parent.ghosts.push(anim); ghostIcon.animation.play("alive", true); ghostIcon.color = 0xFFFFFFFF; } else { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTDISABLE_SOUND)); parent.ghosts.remove(anim); ghostIcon.animation.play("dead", true); ghostIcon.color = 0xFFADADAD; diff --git a/source/funkin/editors/character/CharacterAnimsWindow.hx b/source/funkin/editors/character/CharacterAnimsWindow.hx index a58f790f..ae6822ea 100644 --- a/source/funkin/editors/character/CharacterAnimsWindow.hx +++ b/source/funkin/editors/character/CharacterAnimsWindow.hx @@ -99,6 +99,7 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> { displayAnimsFramesList.remove(name); public function deleteAnimation(button:CharacterAnimButton, addToUndo:Bool = true) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND)); if (buttons.members.length <= 1) return; if (character.getAnimName() == button.anim) @:privateAccess CharacterEditor.instance._animation_down(null); @@ -112,13 +113,14 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> { } public function generateAnimation() { - var animName:String = "New Anim"; + var newAnim = TU.translate("characterEditor.characterAnim.defaultAnimName"); + var animName:String = newAnim; var animNames:Array<String> = character.getNameList(); var newAnimCount:Int = 0; while (animNames.indexOf(animName) != -1) { newAnimCount++; - animName = 'New Anim - $newAnimCount'; + animName = '$newAnim - $newAnimCount'; } if (__autoCompleteAnims.length <= 0) diff --git a/source/funkin/editors/character/CharacterCreationScreen.hx b/source/funkin/editors/character/CharacterCreationScreen.hx index 5bb5062e..3e10082a 100644 --- a/source/funkin/editors/character/CharacterCreationScreen.hx +++ b/source/funkin/editors/character/CharacterCreationScreen.hx @@ -15,13 +15,16 @@ class CharacterCreationScreen extends UISubstateWindow { public var saveButton:UIButton; public var closeButton:UIButton; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("characterCreationScreen." + id, args); + public function new(?onSave:(String, ImageSaveData, Xml)->Void) { super(); if (onSave != null) this.onSave = onSave; } public override function create() { - winTitle = "Creating New Character"; + winTitle = translate("win-title"); winWidth = 360; winHeight = 520; @@ -37,24 +40,24 @@ class CharacterCreationScreen extends UISubstateWindow { 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$", + addLabelOn(characterNameTextBox, "").applyMarkup( + translate("charName"), [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$", + addLabelOn(imageExplorer, "").applyMarkup( + translate("charImgName"), [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() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { close(); createCharacter(); }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() { + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.cancel"), function() { close(); }, 125); add(closeButton); diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index 74e59c04..264f7039 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -65,74 +65,77 @@ class CharacterEditor extends UIState { if (character != null) __character = character; } + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("characterEditor." + id, args); + public override function create() { super.create(); undos = new UndoList<CharacterEditorChange>(); - WindowUtils.suffix = " (Character Editor)"; + WindowUtils.suffix = " (" + TU.translate("editor.character.name") + ")"; SaveWarning.selectionClass = CharacterSelection; SaveWarning.saveFunc = () -> {_file_save(null);}; topMenu = [ { - label: "File", + label: translate("topBar.file"), childs: [ { - label: "New", + label: translate("file.new"), onSelect: _file_new, }, null, { - label: "Save", + label: translate("file.save"), keybind: [CONTROL, S], onSelect: _file_save, }, { - label: "Save As...", + label: translate("file.saveAs"), keybind: [CONTROL, SHIFT, S], onSelect: _file_saveas, }, null, { - label: "Exit", + label: translate("file.exit"), onSelect: _file_exit } ] }, { - label: "Edit", + label: translate("topBar.edit"), childs: [ { - label: "Copy Offset", + label: translate("edit.copyOffset"), keybind: [CONTROL, C], onSelect: _edit_copy_offset }, { - label: "Paste Offset", + label: translate("edit.pasteOffset"), keybind: [CONTROL, V], onSelect: _edit_paste_offset }, null, { - label: "Undo", + label: translate("edit.undo"), keybind: [CONTROL, Z], onSelect: _edit_undo }, { - label: "Redo", + label: translate("edit.redo"), keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]], onSelect: _edit_redo }, null, { - label: "Edit information", + label: translate("edit.editInfo"), color: 0xFF959829, icon: 4, onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, onSelect: _edit_info }, { - label: "Edit sprite", + label: translate("edit.editSprite"), color: 0xFF959829, icon: 4, onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, onSelect: _edit_sprite @@ -140,128 +143,128 @@ class CharacterEditor extends UIState { ] }, { - label: "Offsets", + label: translate("topBar.offset"), childs: [ { - label: "Move Left", + label: translate("offset.moveLeft"), keybind: [LEFT], onSelect: _offsets_left, }, { - label: "Move Up", + label: translate("offset.moveUp"), keybind: [UP], onSelect: _offsets_up, }, { - label: "Move Down", + label: translate("offset.moveDown"), keybind: [DOWN], onSelect: _offsets_down, }, { - label: "Move Right", + label: translate("offset.moveRight"), keybind: [RIGHT], onSelect: _offsets_right, }, null, { - label: "Move Extra Left", + label: translate("offset.moveExtraLeft"), keybind: [SHIFT, LEFT], onSelect: _offsets_extra_left, }, { - label: "Move Extra Up", + label: translate("offset.moveExtraUp"), keybind: [SHIFT, UP], onSelect: _offsets_extra_up, }, { - label: "Move Extra Down", + label: translate("offset.moveExtraDown"), keybind: [SHIFT, DOWN], onSelect: _offsets_extra_down, }, { - label: "Move Extra Right", + label: translate("offset.moveExtraRight"), keybind: [SHIFT, RIGHT], onSelect: _offsets_extra_right, }, null, { - label: "Drag Offsets With Mouse?", + label: translate("offset.dragOffsetsMouse"), onSelect: _offsets_drag_offsets_mouse, icon: Options.characterDragging ? 1 : 0 }, { - label: "Clear Offsets", + label: translate("offset.clearOffsets"), keybind: [CONTROL, R], onSelect: _offsets_clear, } ] }, { - label: "Stage", + label: translate("topBar.stage"), childs: buildStagesUI() }, { - label: "View", + label: translate("topBar.view"), childs: [ { - label: "Zoom in", + label: translate("view.zoomIn"), keybind: [CONTROL, NUMPADPLUS], onSelect: _view_zoomin }, { - label: "Zoom out", + label: translate("view.zoomOut"), keybind: [CONTROL, NUMPADMINUS], onSelect: _view_zoomout }, { - label: "Reset zoom", + label: translate("view.resetZoom"), keybind: [CONTROL, NUMPADZERO], onSelect: _view_zoomreset }, null, { - label: "Character Hitbox?", + label: translate("view.charHitbox"), onSelect: _view_character_show_hitbox, icon: Options.characterHitbox ? 1 : 0 }, { - label: "Character Camera?", + label: translate("view.charCam"), onSelect: _view_character_show_camera, icon: Options.characterCamera ? 1 : 0 }, { - label: "XY Axis?", + label: translate("view.axis"), onSelect: _view_character_show_axis, icon: Options.characterAxis ? 1 : 0 } ] }, { - label: "Animation >", + label: translate("topBar.animation") + " >", childs: [ { - label: "Play Animation", + label: translate("animation.playAnim"), keybind: [SPACE], onSelect: _animation_play, }, { - label: "Stop Animation", + label: translate("animation.stopAnim"), onSelect: _animation_stop }, null, { - label: "Change Animation ↑", + label: translate("animation.changeAnimUp"), keybind: [W], onSelect: _animation_up }, { - label: "Change Animation ↓", + label: translate("animation.changeAnimDown"), keybind: [S], onSelect: _animation_down }, null, { - label: "Play Animation On Offset?", + label: translate("animation.playAnimOnOffset"), onSelect: _animation_toggle_anim_playing_offsets, icon: Options.playAnimOnOffset ? 1 : 0 }, @@ -433,7 +436,7 @@ class CharacterEditor extends UIState { super.onResize(width, height); if (!UIState.resolutionAware) return; - if (width < FlxG.initialWidth || height < FlxG.initialHeight) { + if ((width < FlxG.initialWidth || height < FlxG.initialHeight) && !Options.bypassEditorsResize) { width = FlxG.initialWidth; height = FlxG.initialHeight; } @@ -450,6 +453,7 @@ class CharacterEditor extends UIState { function _file_save(_) { #if sys + FlxG.sound.play(Paths.sound('editors/save')); CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml', buildCharacter() @@ -461,6 +465,7 @@ class CharacterEditor extends UIState { } function _file_saveas(_) { + FlxG.sound.play(Paths.sound('editors/save')); openSubState(new SaveSubstate(buildCharacter(), { defaultSaveFile: '${character.curCharacter}.xml' })); @@ -493,6 +498,7 @@ class CharacterEditor extends UIState { } function _undo(undo:CharacterEditorChange) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND)); switch (undo) { case null: // do nothing case CCharEditPosition(oldPos, newPos): @@ -563,6 +569,7 @@ class CharacterEditor extends UIState { } function _redo(redo:CharacterEditorChange) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND)); switch (redo) { case null: // do nothing case CCharEditPosition(oldPos, newPos): @@ -682,16 +689,17 @@ class CharacterEditor extends UIState { var stageFileList = Stage.getList(true); if (stageFileList.length == 0) stageFileList = Stage.getList(false); + var stageText = TU.getRaw("characterEditor.stage.useStage"); for (stage in stageFileList) newChilds.push({ - label: 'Use "$stage"?', + label: stageText.format([stage]), icon: currentStage == stage ? 1 : 0, onSelect: (_) -> {changeStage(stage);} }); newChilds.push(null); newChilds.push({ - label: "Use No Stage?", + label: translate("stage.useNone"), icon: currentStage == null ? 1 : 0, onSelect: (_) -> {changeStage(null);} }); diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index 8241c198..f085d2c3 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -40,8 +40,11 @@ class CharacterInfoScreen extends UISubstateWindow { super(); } + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("characterInfoScreen." + id, args); + public override function create() { - winTitle = 'Edit Character Properties...'; + winTitle = translate("win-title"); winWidth = 824; winHeight = 400; super.create(); @@ -50,15 +53,15 @@ 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, "Edit Character Info", 28)); + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28)); iconColorPicker = new UIIconColorPicker(title.x, title.y + title.height + 38, character.getIcon(), character.antialiasing, null); add(iconColorPicker); - addLabelOn(iconColorPicker, "Icon"); + addLabelOn(iconColorPicker, translate("icon")); iconColorWheel = new UIColorwheel(iconColorPicker.x+12+125+12+22, iconColorPicker.y, character.iconColor); add(iconColorWheel); - addLabelOn(iconColorWheel, "Icon Color"); + addLabelOn(iconColorWheel, translate("iconColor")); if (character.iconColor != null) iconColorWheel.colorChanged = true; @@ -66,9 +69,9 @@ class CharacterInfoScreen extends UISubstateWindow { 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"); + addLabelOn(durationStepper, translate("singDuration")); - useDurationCheckbox = new UICheckbox(durationStepper.x + durationStepper.bWidth + 20, durationStepper.y+6, "Use Sing Duration?", character.holdTime != -1); + useDurationCheckbox = new UICheckbox(durationStepper.x + durationStepper.bWidth + 20, durationStepper.y+6, translate("useSingDuration"), character.holdTime != -1); useDurationCheckbox.onChecked = (checked:Bool) -> {durationStepper.selectable = checked;}; add(useDurationCheckbox); @@ -86,15 +89,15 @@ class CharacterInfoScreen extends UISubstateWindow { customPropertiesButtonList.add(new PropertyButton(prop, val, customPropertiesButtonList)); add(customPropertiesButtonList); - addLabelOn(customPropertiesButtonList, "Custom Values (Advanced)"); + addLabelOn(customPropertiesButtonList, translate("customValues")); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveCharacterInfo(); close(); }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Close", function() { + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); add(closeButton); diff --git a/source/funkin/editors/character/CharacterPropertiesWindow.hx b/source/funkin/editors/character/CharacterPropertiesWindow.hx index e32c0d9c..fd016b0c 100644 --- a/source/funkin/editors/character/CharacterPropertiesWindow.hx +++ b/source/funkin/editors/character/CharacterPropertiesWindow.hx @@ -1,3 +1,4 @@ + package funkin.editors.character; import funkin.backend.FlxAnimate; @@ -26,6 +27,9 @@ class CharacterPropertiesWindow extends UISliceSprite { public var labels:Map<UISprite, UIText> = []; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("characterEditor.characterProperties." + id, args); + public function new(x:Float, y:Float, character:Character) @:privateAccess { super(x, y, 424+16, 204+20, "editors/ui/inputbox"); @@ -40,7 +44,7 @@ class CharacterPropertiesWindow extends UISliceSprite { this.changePosition(positionXStepper.value, null); }; members.push(positionXStepper); - addLabelOn(positionXStepper, "Position (X,Y)"); + addLabelOn(positionXStepper, translate("position")); members.push(positionXYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 22)); @@ -57,17 +61,17 @@ class CharacterPropertiesWindow extends UISliceSprite { this.changeScale(scaleStepper.value); }; members.push(scaleStepper); - addLabelOn(scaleStepper, "Scale"); + addLabelOn(scaleStepper, translate("scale")); - editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y-20, "Edit Info", editCharacterInfoUI, 120, 24); + editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y-20, translate("editInfo"), 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 = new UIButton(editCharacterButton.x, editCharacterButton.y+24+6, translate("editSprite"), 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 = new UICheckbox(scaleStepper.x+22, scaleStepper.y+32+14, translate("charFlipped"), character.isPlayer ? !character.__baseFlipped : character.__baseFlipped); flipXCheckbox.onChecked = (checked:Bool) -> {this.changeFlipX(checked);}; members.push(flipXCheckbox); @@ -77,7 +81,7 @@ class CharacterPropertiesWindow extends UISliceSprite { this.changeCamPosition(cameraXStepper.value, null); }; members.push(cameraXStepper); - addLabelOn(cameraXStepper, "Camera Position (X,Y)"); + addLabelOn(cameraXStepper, translate("camPosition")); members.push(cameraXYComma = new UIText(cameraXStepper.x + 104-32+0, cameraXStepper.y+9, 0, ",", 22)); @@ -88,7 +92,7 @@ class CharacterPropertiesWindow extends UISliceSprite { }; members.push(cameraYStepper); - antialiasingCheckbox = new UICheckbox(scaleStepper.x+22, flipXCheckbox.y+32, "Antialiased?", character.antialiasing); + antialiasingCheckbox = new UICheckbox(scaleStepper.x+22, flipXCheckbox.y+32, translate("antialiased"), character.antialiasing); antialiasingCheckbox.onChecked = (checked:Bool) -> {this.changeAntialiasing(checked);}; members.push(antialiasingCheckbox); @@ -97,7 +101,7 @@ class CharacterPropertiesWindow extends UISliceSprite { CharacterEditor.instance.changeStagePosition(testAsDropDown.options[index]); }; members.push(testAsDropDown); - addLabelOn(testAsDropDown, "Test Character As..."); + addLabelOn(testAsDropDown, translate("testCharAs")); 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); @@ -105,7 +109,7 @@ class CharacterPropertiesWindow extends UISliceSprite { CharacterEditor.instance.changeCharacterDesginedAs(designedAsDropDown.options[index] == "BOYFRIEND"); }; members.push(designedAsDropDown); - addLabelOn(designedAsDropDown, "Char Desgined As..."); + addLabelOn(designedAsDropDown, translate("charDesign")); designedAsDropDown.bWidth = 193; alpha = 0.7; diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index f298d951..9f009ccb 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -12,6 +12,9 @@ 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() @@ -34,13 +37,13 @@ class CharacterSelection extends EditorTreeMenu if(char.endsWith("/")) { var folderName = CoolUtil.getFilename(char.substr(0, char.length-1)); - list.push(new TextOption(folderName + " >", "Press ACCEPT to edit this folder.", function() { + list.push(new TextOption(folderName + " >", translate("acceptFolder"), function() { var newModsList = Character.getList(isMods, true, char); var newList:Array<OptionType> = generateList(newModsList, isMods, folderPath + folderName + "/"); - optionsTree.add(new OptionsScreen(folderPath + folderName, "Select a character to edit in " + folderPath + folderName + "/", newList)); + optionsTree.add(new OptionsScreen(folderPath + folderName, translate("desc-folder", [folderPath + folderName + "/"]), newList)); })); } else { - list.push(new IconOption(char, "Press ACCEPT to edit this character.", Character.getIconFromCharName(folderPath + char, char), + list.push(new IconOption(char, translate("acceptCharacter"), Character.getIconFromCharName(folderPath + char, char), function() { FlxG.switchState(new CharacterEditor(folderPath + char)); }) @@ -48,7 +51,7 @@ class CharacterSelection extends EditorTreeMenu } } - list.insert(0, new NewOption("New Character", "Press ACCEPT to create a new character.", function() { + list.insert(0, new NewOption(translate("newCharacter"), translate("newCharacterDesc"), function() { openSubState(new CharacterCreationScreen(createCharacter)); })); @@ -57,7 +60,7 @@ class CharacterSelection extends EditorTreeMenu var list = generateList(modsList, isMods); - main = new OptionsScreen("Character Editor", "Select a character to edit", list); + main = new OptionsScreen(TU.translate("editor.character.name"), translate("desc"), list); DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]); } diff --git a/source/funkin/editors/character/CharacterSpriteScreen.hx b/source/funkin/editors/character/CharacterSpriteScreen.hx index a3497127..ceb2ed6d 100644 --- a/source/funkin/editors/character/CharacterSpriteScreen.hx +++ b/source/funkin/editors/character/CharacterSpriteScreen.hx @@ -16,6 +16,9 @@ class CharacterSpriteScreen extends UISubstateWindow { public var saveButton:UIButton; public var closeButton:UIButton; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("characterEditor.characterSpriteScreen." + id, args); + public function new(imagePath:String, ?onSave:(String, Bool)->Void) { super(); this.imagePath = imagePath; @@ -23,7 +26,7 @@ class CharacterSpriteScreen extends UISubstateWindow { } public override function create() { - winTitle = 'Edit character sprite'; + winTitle = translate("win-title"); winWidth = 360; winHeight = 183; function addLabelOn(ui:UISprite, text:String):UIText { @@ -36,13 +39,13 @@ class CharacterSpriteScreen extends UISubstateWindow { 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$", + addLabelOn(imageExplorer, "").applyMarkup( + translate('charImageFile'), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); ogImageSaveData = imageExplorer.getSaveData(); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, TU.translate("editor.saveClose"), 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); @@ -53,7 +56,7 @@ class CharacterSpriteScreen extends UISubstateWindow { saveButton.y -= saveButton.bHeight; saveButton.selectable = false; - closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Cancel", function() { + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.cancel"), function() { close(); }, 125); closeButton.color = 0xFFFF0000; diff --git a/source/funkin/editors/charter/ChartCreationScreen.hx b/source/funkin/editors/charter/ChartCreationScreen.hx index a93a7b36..c212481e 100644 --- a/source/funkin/editors/charter/ChartCreationScreen.hx +++ b/source/funkin/editors/charter/ChartCreationScreen.hx @@ -26,7 +26,7 @@ class ChartCreationScreen extends UISubstateWindow { } public override function create() { - winTitle = "Creating New Chart"; + winTitle = TU.translate("chartCreation.title"); winWidth = 652; winHeight = 600; @@ -43,11 +43,11 @@ class ChartCreationScreen extends UISubstateWindow { if (charFileList.length == 0) charFileList = Character.getList(false); var chartTitle:UIText; - add(chartTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Chart Info", 28)); + add(chartTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("chartCreation.info"), 28)); difficultyNameTextBox = new UITextBox(chartTitle.x, chartTitle.y + chartTitle.height + 36, "difficulty", 200); add(difficultyNameTextBox); - addLabelOn(difficultyNameTextBox, "Difficulty Name"); + addLabelOn(difficultyNameTextBox, TU.translate("chartCreation.diffName")); var stageFileList = Stage.getList(true); if (stageFileList.length == 0) stageFileList = Stage.getList(false); @@ -55,7 +55,7 @@ class ChartCreationScreen extends UISubstateWindow { stageTextBox = new UIAutoCompleteTextBox(difficultyNameTextBox.x + 200 + 26, difficultyNameTextBox.y, "stage", 180); stageTextBox.suggestItems = stageFileList; add(stageTextBox); - addLabelOn(stageTextBox, "Stage"); + addLabelOn(stageTextBox, TU.translate("chartCreation.stage")); scrollSpeedTextBox = new UINumericStepper(stageTextBox.x + 180 + 26, difficultyNameTextBox.y, 1.0, 0.1, 2, null, null, 90); scrollSpeedTextBox.onChange = function (text:String) { @@ -65,7 +65,7 @@ class ChartCreationScreen extends UISubstateWindow { button.scrollSpeedStepper.value = scrollSpeedTextBox.value; } add(scrollSpeedTextBox); - addLabelOn(scrollSpeedTextBox, "Scroll Speed"); + addLabelOn(scrollSpeedTextBox, TU.translate("chartCreation.scrollSpeed")); 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'); @@ -113,20 +113,21 @@ class ChartCreationScreen extends UISubstateWindow { }, strumLineList)); add(strumLineList); - addLabelOn(strumLineList, "Strumlines").applyMarkup( - "Strumlines $* Atleast 1 Strumline Required$", + addLabelOn(strumLineList, "").applyMarkup( + TU.translate("chartCreation.strumLineList"), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); - strumLineList.dragCallback = (object:StrumLineButton, oldIndex:Int, newIndex:Int) -> {object.idText.text = 'Strumline - #${newIndex}';}; + var strumlineFormat = TU.getRaw("chartCreation.strumLine.format"); + strumLineList.dragCallback = (object:StrumLineButton, oldIndex:Int, newIndex:Int) -> {object.idText.text = strumlineFormat.format([newIndex]);}; scrollSpeedTextBox.onChange(scrollSpeedTextBox.label.text); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { createChart(); close(); }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() { + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); add(closeButton); @@ -211,12 +212,12 @@ class StrumLineButton extends UIButton { charactersList.cameraSpacing = 0; charactersList.addButton.callback = function() - charactersList.add(new CompactCharacterButton("New Char", subState.charFileList, charactersList)); + charactersList.add(new CompactCharacterButton(TU.translate("charterStrumLine.newChar"), subState.charFileList, charactersList)); for (character in strumLine.characters) charactersList.add(new CompactCharacterButton(character, subState.charFileList, charactersList)); members.push(charactersList); - idText = addLabelOn(charactersList, 'Strumline - #$id'); + idText = addLabelOn(charactersList, TU.translate("chartCreation.strumLine.format", [id])); cameraClipShader = new CustomShader("engine/cameraClip"); cameraClipShader.hset("clipRect", [0, 0, 100, 100]); @@ -224,17 +225,17 @@ class StrumLineButton extends UIButton { typeDropdown = new UIDropDown(charactersList.x + charactersList.bWidth + 16, 8+26, 200, 32, ["OPPONENT", "PLAYER", "ADDITIONAL"], strumLine.type); members.push(typeDropdown); - addLabelOn(typeDropdown, "Type"); + addLabelOn(typeDropdown, TU.translate("charterStrumLine.type")); var stagePositionI = strumLine.position == null ? strumLine.type : ["DAD", "BOYFRIEND", "GIRLFRIEND"].indexOf(strumLine.position.toUpperCase()); stagePositionDropdown = new UIDropDown(typeDropdown.x + 200 - 32 + 26, typeDropdown.y, 200, 32, ["DAD", "BOYFRIEND", "GIRLFRIEND"], stagePositionI); members.push(stagePositionDropdown); - addLabelOn(stagePositionDropdown, "Stage Position"); + 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); members.push(hudScaleStepper); - addLabelOn(hudScaleStepper, "Scale"); + addLabelOn(hudScaleStepper, TU.translate("charterStrumLine.scale")); var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos; @@ -244,22 +245,22 @@ class StrumLineButton extends UIButton { hudXStepper = new UINumericStepper(hudScaleStepper.x + 80 - 32 + 26, hudScaleStepper.y, startingPos.x, 0.01, 2, 0, 2, 84); members.push(hudXStepper); - addLabelOn(hudXStepper, "Hud Position (X [Ratio 0-1],Y)"); + addLabelOn(hudXStepper, TU.translate("charterStrumLine.hudPos")); members.push(XYComma = new UIText(hudXStepper.x + 84 - 32 + 0, hudXStepper.y + 9, 0, ",", 22)); hudYStepper = new UINumericStepper(hudXStepper.x + 84 - 32 + 26, hudXStepper.y, startingPos.y, 0.001, 2, null, null, 84); members.push(hudYStepper); - visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, "Visible?", strumLine.visible == null ? true : strumLine.visible); + visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, TU.translate("charterStrumLine.visible"), strumLine.visible == null ? true : strumLine.visible); members.push(visibleCheckbox); scrollSpeedStepper = new UINumericStepper(typeDropdown.x, typeDropdown.y + 128, strumLine.scrollSpeed, 0.1, 2, 0, 10, 82); scrollSpeedStepper.selectable = strumLine.scrollSpeed != null; members.push(scrollSpeedStepper); - addLabelOn(scrollSpeedStepper, "Scroll Speed"); + addLabelOn(scrollSpeedStepper, TU.translate("charterStrumLine.scrollSpeed")); - usesChartScrollSpeed = new UICheckbox(scrollSpeedStepper.x + 104, typeDropdown.y + 135, "Uses charts scroll speed?", strumLine.scrollSpeed == null); + usesChartScrollSpeed = new UICheckbox(scrollSpeedStepper.x + 104, typeDropdown.y + 135, TU.translate("charterStrumLine.useChartScrollSpeed"), strumLine.scrollSpeed == null); usesChartScrollSpeed.onChecked = function(b) { if(b) { scrollSpeedStepper.value = subState.scrollSpeedTextBox.value; @@ -318,7 +319,8 @@ class CompactCharacterButton extends UIButton { 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); @@ -330,11 +332,10 @@ 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), null, function () { diff --git a/source/funkin/editors/charter/ChartDataScreen.hx b/source/funkin/editors/charter/ChartDataScreen.hx index ca395764..0cb996b7 100644 --- a/source/funkin/editors/charter/ChartDataScreen.hx +++ b/source/funkin/editors/charter/ChartDataScreen.hx @@ -29,7 +29,7 @@ class ChartDataScreen extends UISubstateWindow { Charter.instance.vocals.pause(); for (strumLine in Charter.instance.strumLines.members) strumLine.vocals.pause(); - winTitle = 'Chart Properties'; + winTitle = TU.translate("chartDataScreen.title"); winWidth = 420; winHeight = 230; // guys look, the funny numbers! super.create(); @@ -38,7 +38,7 @@ class ChartDataScreen 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, "Edit Chart Data", 28)); + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("chartDataScreen.editChartData"), 28)); var stageFileList = Stage.getList(true); if (stageFileList.length == 0) stageFileList = Stage.getList(false); @@ -46,19 +46,19 @@ class ChartDataScreen extends UISubstateWindow { stageTextBox = new UIAutoCompleteTextBox(title.x, title.y + title.height + 38, PlayState.SONG.stage, 200); stageTextBox.suggestItems = stageFileList; add(stageTextBox); - addLabelOn(stageTextBox, "Stage"); + addLabelOn(stageTextBox, TU.translate("chartDataScreen.stage")); scrollSpeedStepper = new UINumericStepper(stageTextBox.x + 200 + 26, stageTextBox.y, data.scrollSpeed, 0.1, 2, 0, 10, 82); add(scrollSpeedStepper); - addLabelOn(scrollSpeedStepper, "Scroll Speed"); + addLabelOn(scrollSpeedStepper, TU.translate("chartDataScreen.scrollSpeed")); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveInfo(); close(); }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); add(closeButton); diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 7c111dbe..84d1dd57 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -11,6 +11,7 @@ import funkin.backend.chart.ChartData; import funkin.backend.system.Conductor; import funkin.backend.system.framerate.Framerate; 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; @@ -22,6 +23,7 @@ import haxe.Json; #if sys import sys.FileSystem; #end +import flixel.util.FlxColor; class Charter extends UIState { public static var __song:String; @@ -32,6 +34,9 @@ class Charter extends UIState { private function get_chart() return PlayState.SONG; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("charter." + id, args); + public static var instance(get, never):Charter; private static inline function get_instance() @@ -44,6 +49,7 @@ 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; @@ -102,7 +108,11 @@ class Charter extends UIState { public static var clipboard:Array<CharterCopyboardObject> = []; public static var waveformHandler:CharterWaveformHandler; - //public var textLayer:FlxLayer; + private var SONGPOSINFO_STEP = TU.getRaw("songPosInfo.step"); + private var SONGPOSINFO_BEAT = TU.getRaw("songPosInfo.beat"); + private var SONGPOSINFO_MEASURE = TU.getRaw("songPosInfo.measure"); + private var SONGPOSINFO_BPM = TU.getRaw("songPosInfo.bpm"); + private var SONGPOSINFO_TIMESIGNATURE = TU.getRaw("songPosInfo.timeSignature"); public function new(song:String, diff:String, reload:Bool = true) { super(); @@ -116,154 +126,149 @@ class Charter extends UIState { public override function create() { super.create(); - WindowUtils.suffix = " (Chart Editor)"; + WindowUtils.suffix = " (" + TU.translate("editor.chart.name") + ")"; SaveWarning.selectionClass = CharterSelection; SaveWarning.saveFunc = () -> saveEverything(); topMenu = [ { - label: "File", + label: translate("topBar.file"), childs: [ /*{ - label: "New" + label: translate("file.new") }, null,*/ { - label: "Save", + label: translate("file.save"), keybind: [CONTROL, S], onSelect: _file_save_all, }, - null, { - label: "Save chart", + label: translate("file.saveAs"), keybind: [CONTROL, SHIFT, S], - onSelect: _file_save, - }, - { - label: "Save chart as...", onSelect: _file_saveas, }, null, { - label: "Save global events", + label: translate("file.saveGlobalEvents"), keybind: [CONTROL, B, S], onSelect: _file_events_save, }, { - label: "Save global events as...", + label: translate("file.saveGlobalEventsAs"), onSelect: _file_events_saveas, }, { - label: "Save chart without local events", + label: translate("file.saveNoLocalEvents"), keybind: [CONTROL, ALT, S], onSelect: _file_save_no_events, }, { - label: "Save chart without local events as...", + label: translate("file.saveNoLocalEventsAs"), onSelect: _file_saveas_no_events, }, null, { - label: "Save meta", + label: translate("file.saveMeta"), keybind: [CONTROL, M, S], onSelect: _file_meta_save, }, { - label: "Save meta as...", + label: translate("file.saveMetaAs"), onSelect: _file_meta_saveas, }, null, { - label: "Export for FNF Legacy as...", + label: translate("file.exportFnfLegacy"), onSelect: _file_saveas_fnflegacy, }, { - label: "Export for Psych Engine as...", + label: translate("file.exportPsych"), onSelect: _file_saveas_psych, }, null, { - label: "Exit", + label: translate("file.exit"), onSelect: _file_exit } ] }, { - label: "Edit", + label: translate("topBar.edit"), childs: [ { - label: "Undo", + label: translate("edit.undo"), keybind: [CONTROL, Z], onSelect: _edit_undo }, { - label: "Redo", + label: translate("edit.redo"), keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]], onSelect: _edit_redo }, null, { - label: "Copy", + label: translate("edit.copy"), keybind: [CONTROL, C], - onSelect: _edit_copy + onSelect: cast _edit_copy }, { - label: "Paste", + label: translate("edit.paste"), keybind: [CONTROL, V], onSelect: _edit_paste }, null, { - label: "Cut", + label: translate("edit.cut"), keybind: [CONTROL, X], onSelect: _edit_cut }, { - label: "Delete", + label: translate("edit.delete"), keybind: [DELETE], onSelect: _edit_delete } ] }, { - label: "Chart", + label: translate("topBar.chart"), childs: [ { - label: "Playtest", + label: translate("chart.playtest"), keybind: [ENTER], onSelect: _chart_playtest }, { - label: "Playtest here", + label: translate("chart.playtestHere"), keybind: [SHIFT, ENTER], onSelect: _chart_playtest_here }, null, { - label: "Playtest as opponent", + label: translate("chart.playtestOpponent"), keybind: [CONTROL, ENTER], onSelect: _chart_playtest_opponent }, { - label: "Playtest as opponent here", + label: translate("chart.playtestOpponentHere"), keybind: [CONTROL, SHIFT, ENTER], onSelect: _chart_playtest_opponent_here }, null, { - label: 'Enable scripts during playtesting', + label: translate("chart.enableScripts"), onSelect: _chart_enablescripts, icon: Options.charterEnablePlaytestScripts ? 1 : 0 }, null, { - label: "Edit chart data", + label: translate("chart.editChartData"), color: 0xFF959829, icon: 4, onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, onSelect: chart_edit_data }, { - label: "Edit metadata information", + label: translate("chart.editMetadata"), color: 0xFF959829, icon: 4, onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, onSelect: chart_edit_metadata @@ -271,160 +276,140 @@ class Charter extends UIState { ] }, { - label: "View", + label: translate("topBar.view"), childs: [ { - label: "Zoom in", + label: translate("view.zoomIn"), keybind: [CONTROL, NUMPADPLUS], onSelect: _view_zoomin }, { - label: "Zoom out", + label: translate("view.zoomOut"), keybind: [CONTROL, NUMPADMINUS], onSelect: _view_zoomout }, { - label: "Reset zoom", + label: translate("view.resetZoom"), keybind: [CONTROL, NUMPADZERO], onSelect: _view_zoomreset }, null, { - label: 'Show Sections Separator', + label: translate("view.showSectionsSeparator"), onSelect: _view_showeventSecSeparator, icon: Options.charterShowSections ? 1 : 0 }, { - label: 'Show Beats Separator', + label: translate("view.showBeatsSeparator"), onSelect: _view_showeventBeatSeparator, icon: Options.charterShowBeats ? 1 : 0 }, null, { - label: 'Rainbow Waveforms', + label: translate("view.rainbowWaveforms"), onSelect: _view_switchWaveformRainbow, icon: Options.charterRainbowWaveforms ? 1 : 0 }, { - label: 'Low Detail Waveforms', + label: translate("view.lowDetailWaveforms"), onSelect: _view_switchWaveformDetail, icon: Options.charterLowDetailWaveforms ? 1 : 0 }, null, { - label: "Scroll left", + label: translate("view.scrollLeft"), keybind: [SHIFT, LEFT], onSelect: _view_scrollleft }, { - label: "Scroll right", + label: translate("view.scrollRight"), keybind: [SHIFT, RIGHT], onSelect: _view_scrollright }, { - label: "Reset scroll", + label: translate("view.scrollReset"), keybind: [SHIFT, DOWN], onSelect: _view_scrollreset } ] }, { - label: "Song", - childs: [ - { - label: "Go back to the start", - keybind: [HOME], - onSelect: _song_start - }, - { - label: "Go to the end", - keybind: [END], - onSelect: _song_end - }, - null, - { - label: "Mute instrumental", - onSelect: _song_muteinst - }, - { - label: "Mute voices", - onSelect: _song_mutevoices - } - ] + label: translate("topBar.song"), + childs: buildSongUI() }, { - label: "Note >", + label: translate("topBar.note") + " >", childs: buildNoteTypesUI() }, { - label: "Snap >", + label: translate("topBar.snap") + " >", childs: buildSnapsUI() }, { - label: "Playback >", + label: translate("topBar.playback") + " >", childs: [ { - label: "Play/Pause", + label: translate("playback.play"), keybind: [SPACE], onSelect: _playback_play }, null, { - label: "↑ Speed 25%", + label: translate("playback.speedRaise", ["25"]), keybind: [PERIOD], onSelect: _playback_speed_raise }, { - label: "Reset Speed", + label: translate("playback.speedReset"), onSelect: _playback_speed_reset }, { - label: "↓ Speed 25%", + label: translate("playback.speedLower", ["25"]), keybind: [COMMA], onSelect: _playback_speed_lower }, null, { - label: "Go back a section", + label: translate("playback.sectionBack"), keybind: [A], onSelect: _playback_back }, { - label: "Go forward a section", + label: translate("playback.sectionForward"), keybind: [D], onSelect: _playback_forward }, { - label: "Go to start of section", + label: translate("playback.sectionStart"), keybind: [SHIFT, S], onSelect: _playback_section_start }, null, { - label: "Go back a step", + label: translate("playback.backStep"), keybind: [W], onSelect: _playback_back_step }, { - label: "Go forward a step", + label: translate("playback.forwardStep"), keybind: [S], onSelect: _playback_forward_step }, null, { - label: "Metronome", + label: translate("playback.metronome"), onSelect: _playback_metronome, icon: Options.charterMetronomeEnabled ? 1 : 0 }, /*{ - label: "Visual metronome" + label: translate("playback.visualMetronome") },*/ ] } ]; - hitsound = FlxG.sound.load(Paths.sound('editors/charter/hitsound')); - metronome = FlxG.sound.load(Paths.sound('editors/charter/metronome')); + hitsound = FlxG.sound.load(Paths.sound(Flags.DEFAULT_CHARTER_HITSOUND_SOUND)); + metronome = FlxG.sound.load(Paths.sound(Flags.DEFAULT_CHARTER_METRONOME_SOUND)); charterCamera = FlxG.camera; uiCamera = new FlxCamera(); @@ -443,7 +428,7 @@ class Charter extends UIState { gridBackdrops = new CharterBackdropGroup(strumLines); gridBackdrops.notesGroup = this.notesGroup; - leftEventRowText = new UIText(0, -40, 0, "Local Events\n(Only This Difficulty)", 12); + leftEventRowText = new UIText(0, -40, 0, translate("info.localEvents"), 12); leftEventRowText.alignment = "center"; leftEventRowText.alpha = 0.75; leftEventsBackdrop = new EventBackdrop(false); @@ -453,7 +438,7 @@ class Charter extends UIState { leftEventsGroup.eventsBackdrop = leftEventsBackdrop; leftEventsGroup.eventsRowText = leftEventRowText; - rightEventRowText = new UIText(0, -40, 0, "Global Events\n(All Difficulties)", 12); + rightEventRowText = new UIText(0, -40, 0, translate("info.globalEvents"), 12); rightEventRowText.alignment = "center"; rightEventRowText.alpha = 0.75; rightEventsBackdrop = new EventBackdrop(true); @@ -481,7 +466,7 @@ class Charter extends UIState { topMenuSpr = new UITopMenu(topMenu); topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; - noteTypeText = new UIText(0, 0, 0, "(0) Default Note"); + noteTypeText = new UIText(0, 0, 0, "(0) " + translate("noteTypes.default")); noteTypeText.cameras = [uiCamera]; scrollBar = new UIScrollBar(FlxG.width - 20, topMenuSpr.bHeight, 1000, 0, 100); @@ -519,13 +504,14 @@ class Charter extends UIState { autoSaveNotif = new CharterAutoSaveUI(20, strumlineInfoBG.y + strumlineInfoBG.height + 20); uiGroup.add(autoSaveNotif); - strumlineAddButton = new CharterStrumlineButton("editors/new", "Create New"); + strumlineAddButton = new CharterStrumlineButton("editors/new", translate("createNew")); strumlineAddButton.onClick = createStrumWithUI; strumlineAddButton.animationOnClick = false; strumlineAddButton.textColorLerp = 0.5; - strumlineLockButton = new CharterStrumlineButton("editors/charter/lock-strumline", "Lock/Unlock"); + strumlineLockButton = new CharterStrumlineButton("editors/charter/lock-strumline", translate("lock-unlock")); strumlineLockButton.onClick = function () { + FlxG.sound.play(Paths.sound(!strumLines.draggable ? Flags.DEFAULT_CHARTER_STRUMUNLOCK_SOUND : Flags.DEFAULT_CHARTER_STRUMLOCK_SOUND)); if (strumLines != null) { strumLines.draggable = !strumLines.draggable; strumlineLockButton.textTweenColor.color = strumLines.draggable ? 0xFF5C95CA : 0xFFE16565; @@ -667,6 +653,7 @@ class Charter extends UIState { } buildNoteTypesUI(); + updateBookmarks(); refreshBPMSensitive(); // Just for now until i add event stacking -lunar @@ -685,25 +672,22 @@ class Charter extends UIState { gridBackdrops.bottomLimitY = __endStep * 40; leftEventsBackdrop.bottomSeparator.y = rightEventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2; - updateWaveforms(); - } + CharterGridSeperatorBase.lastConductorSprY = Math.NEGATIVE_INFINITY; - inline function isSoundLoaded(sound:FlxSound) { - @:privateAccess - return sound != null && sound._sound != null && sound._sound.length > 0; + 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 @@ -795,10 +779,8 @@ class Charter extends UIState { if (autoSaveTimer < Options.charterAutoSaveWarningTime && !autoSaveNotif.cancelled && !autoSaveNotif.showedAnimation) { if (Options.charterAutoSavesSeparateFolder) __autoSaveLocation = __diff.toLowerCase() + DateTools.format(Date.now(), "%m-%d_%H-%M"); - autoSaveNotif.startAutoSave(autoSaveTimer, - !Options.charterAutoSavesSeparateFolder ? 'Saved chart at ${__diff.toLowerCase()}.json!' : - 'Saved chart at $__autoSaveLocation.json!' - ); + var filename = !Options.charterAutoSavesSeparateFolder ? '${__diff.toLowerCase()}.json' : '${__autoSaveLocation}.json'; + autoSaveNotif.startAutoSave(autoSaveTimer, translate("popup.savedChartAt", [filename])); } if (autoSaveTimer <= 0) { autoSaveTimer = Options.charterAutoSaveTime; @@ -924,7 +906,7 @@ class Charter extends UIState { s.cursor = CLICK; } } - + checkSelectionForBPMUpdates(); if (!(verticalChange == 0 && horizontalChange == 0)) { notesGroup.sortNotes(); @@ -969,6 +951,7 @@ class Charter extends UIState { notesGroup.add(note); selection = [note]; undos.addToUndo(CCreateSelection([note])); + FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_NOTEPLACE_SOUND)); } isSelecting = false; } @@ -1033,7 +1016,9 @@ class Charter extends UIState { }); if (FlxG.mouse.justReleasedRight) { - if (deletedNotes.length > 0) undos.addToUndo(CDeleteSelection(deletedNotes.copy())); + if (deletedNotes.length > 0) { + undos.addToUndo(CDeleteSelection(deletedNotes.copy())); + } else if (noteDeleteAnims.garbageIcon.alpha <= .5) { var mousePos = FlxG.mouse.getScreenPosition(uiCamera); closeCurrentContextMenu(); @@ -1095,6 +1080,7 @@ class Charter extends UIState { if (selected == null) return selected; if (selected is CharterNote) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND)); var note:CharterNote = cast selected; note.strumLineID = strumLines.members.indexOf(note.strumLine); note.strumLine = null; // For static undos :D @@ -1128,11 +1114,7 @@ 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(CCreateSelection(selection)); @@ -1156,11 +1138,7 @@ 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)); @@ -1205,6 +1183,8 @@ class Charter extends UIState { createSelection(toBeCreated, false); } + updateBookmarks(); + if (addToUndo) undos.addToUndo(CCreateStrumLine(strumLineID, strL)); } @@ -1228,6 +1208,8 @@ class Charter extends UIState { strumLines.refreshStrumlineIDs(); strumLines.snapStrums(); + updateBookmarks(); + if (addToUndo) { var newStrL = Reflect.copy(strL); newStrL.notes = undoNotes; @@ -1299,6 +1281,19 @@ class Charter extends UIState { 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 { @@ -1338,7 +1333,7 @@ class Charter extends UIState { noteTypeText.x = noteTopButton.x + noteTopButton.bWidth + 6; noteTypeText.y = Std.int((noteTopButton.bHeight - noteTypeText.height) / 2); } - noteTypeText.text = '($noteType) ${noteTypes[noteType-1] == null ? "Default Note" : noteTypes[noteType-1]}'; + noteTypeText.text = '($noteType) ' + (noteTypes[noteType-1] == null ? translate("noteTypes.default") : noteTypes[noteType-1]); super.update(elapsed); @@ -1380,12 +1375,16 @@ class Charter extends UIState { for (strumLine in strumLines.members) strumLine.vocals.pause(); } - songPosInfo.text = '${CoolUtil.timeToStr(Conductor.songPosition)} / ${CoolUtil.timeToStr(songLength)}' - + '\nStep: ${curStep}' - + '\nBeat: ${curBeat}' - + '\nMeasure: ${curMeasure}' - + '\nBPM: ${Conductor.bpm}' - + '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}'; + 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([(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))) updateDisplaySprites(); @@ -1423,15 +1422,18 @@ 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) { + if ((width < FlxG.initialWidth || height < FlxG.initialHeight) && !Options.bypassEditorsResize) { width = FlxG.initialWidth; height = FlxG.initialHeight; } @@ -1443,6 +1445,7 @@ class Charter extends UIState { playBackSlider.x = width - 160 - 26 - 20; updateDisplaySprites(); + updateBookmarks(); charterBG.screenCenter(); } @@ -1471,6 +1474,7 @@ class Charter extends UIState { public static function saveChart(shouldBuild:Bool = true, withEvents:Bool = true) { #if sys + FlxG.sound.play(Paths.sound('editors/save')); saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', !withEvents, shouldBuild); if (undos != null) undos.save(); #else @@ -1487,6 +1491,7 @@ class Charter extends UIState { public static function saveEvents(shouldBuild:Bool = true) { #if sys + FlxG.sound.play(Paths.sound('editors/save')); if (shouldBuild && instance != null) instance.buildChart(); var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events}; @@ -1509,6 +1514,7 @@ class Charter extends UIState { public static function saveMeta(shouldBuild:Bool = true) { #if sys + FlxG.sound.play(Paths.sound('editors/save')); if (shouldBuild && instance != null) instance.buildChart(); CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json', @@ -1538,6 +1544,7 @@ class Charter extends UIState { } public static function saveAs(data:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String, ?options:SaveSubstate.SaveSubstateData, ?saveOptions:Map<String, Bool>, shouldBuild:Bool = true) { + FlxG.sound.play(Paths.sound('editors/save')); if (shouldBuild && instance != null) instance.buildChart(); var cur = FlxG.state; while(true) { @@ -1558,7 +1565,7 @@ class Charter extends UIState { #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_all(_) saveEverything(); @@ -1573,7 +1580,8 @@ class Charter extends UIState { function _file_saveas_fnflegacy(_) saveLegacyChartAs(); function _file_saveas_psych(_) savePsychChartAs(); - function _edit_copy(_) { + function _edit_copy(_, playSFX=true) { + if (playSFX) FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_COPY_SOUND)); if(selection.length == 0) return; var minStep:Float = selection[0].step; @@ -1592,6 +1600,7 @@ class Charter extends UIState { ]; } function _edit_paste(_) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_PASTE_SOUND)); if (clipboard.length <= 0) return; var minStep = curStep; @@ -1611,19 +1620,23 @@ class Charter extends UIState { } } selection = sObjects; - _edit_copy(_); // to fix stupid bugs + _edit_copy(_, false); // to fix stupid bugs + + checkSelectionForBPMUpdates(); undos.addToUndo(CCreateSelection(sObjects.copy())); } function _edit_cut(_) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CUT_SOUND)); if (selection == null || selection.length == 0) return; - _edit_copy(_); + _edit_copy(_, false); deleteSelection(selection, false); } function _edit_delete(_) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND)); if (selection == null || selection.length == 0) return; selection.loop((n:CharterNote) -> { noteDeleteAnims.deleteNotes.push({note: n, time: noteDeleteAnims.deleteTime}); @@ -1632,6 +1645,7 @@ class Charter extends UIState { } function _undo(undo:CharterChange) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND)); switch(undo) { case null: // do nothing case CDeleteStrumLine(strumLineID, strumLine): @@ -1655,6 +1669,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); @@ -1673,6 +1688,9 @@ class Charter extends UIState { 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): @@ -1688,6 +1706,7 @@ class Charter extends UIState { } function _redo(redo:CharterChange) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND)); switch(redo) { case null: // do nothing case CDeleteStrumLine(strumLineID, strumLine): @@ -1710,6 +1729,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); @@ -1728,6 +1748,9 @@ class Charter extends UIState { 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): @@ -1767,12 +1790,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()); } } } @@ -1821,6 +1842,172 @@ 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(translate("bookmarks.newBookmarkName"), 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: translate("song.goStart"), + keybind: [HOME], + onSelect: _song_start + }, + { + label: translate("song.goEnd"), + keybind: [END], + onSelect: _song_end + }, + null, + { + label: translate("bookmarks.addBookmarkHere"), + onSelect: _bookmarks_add + }, + { + label: translate("bookmarks.editBookmarkList"), + 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) + { + var goToBookmark = TU.getRaw("charter.bookmarks.goTo"); + for (b in bookmarks) + { + newChilds.push({ + label: goToBookmark.format([b.name]), + onSelect: function(_) { Conductor.songPosition = Conductor.getTimeForStep(b.time); } + }); + } + newChilds.push(null); + } + + + newChilds.push({ + label: translate("song.muteInst"), + onSelect: _song_muteinst + }); + + newChilds.push({ + label: translate("song.muteVoices"), + onSelect: _song_mutevoices + }); + + if (songTopButton != null) songTopButton.contextMenu = newChilds; + return newChilds; + } + function _view_zoomin(_) { zoom += 0.25; __camZoom = Math.pow(2, zoom); @@ -1835,11 +2022,9 @@ class Charter extends UIState { } function _view_showeventSecSeparator(t) { t.icon = (Options.charterShowSections = !Options.charterShowSections) ? 1 : 0; - leftEventsBackdrop.eventSecSeparator.visible = rightEventsBackdrop.eventSecSeparator.visible = gridBackdrops.sectionsVisible = Options.charterShowSections; } function _view_showeventBeatSeparator(t) { t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0; - leftEventsBackdrop.eventBeatSeparator.visible = rightEventsBackdrop.eventBeatSeparator.visible = gridBackdrops.beatsVisible = Options.charterShowBeats; } function _view_switchWaveformRainbow(t) { t.icon = (Options.charterRainbowWaveforms = !Options.charterRainbowWaveforms) ? 1 : 0; @@ -1866,32 +2051,34 @@ class Charter extends UIState { inline function _snap_decreasesnap(_) changequant(-1); inline function _snap_resetsnap(_) setquant(16); - inline function changequant(change:Int) {quant = quants[FlxMath.wrap(quants.indexOf(quant) + change, 0, quants.length-1)]; buildSnapsUI();}; - inline function setquant(newQuant:Int) {quant = newQuant; buildSnapsUI();} + inline function changequant(change:Int) {FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND)); quant = quants[FlxMath.wrap(quants.indexOf(quant) + change, 0, quants.length-1)]; buildSnapsUI();}; + inline function setquant(newQuant:Int) {FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND)); quant = newQuant; buildSnapsUI();} function buildSnapsUI():Array<UIContextMenuOption> { var snapsTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[snapIndex]; var newChilds:Array<UIContextMenuOption> = [ { - label: "↑ Grid Snap", + label: translate("snap.increaseSnap"), keybind: [X], onSelect: _snap_increasesnap }, { - label: "Reset Grid Snap", + label: translate("snap.resetSnap"), onSelect: _snap_resetsnap }, { - label: "↓ Grid Snap", + label: translate("snap.decreaseSnap"), keybind: [Z], onSelect: _snap_decreasesnap }, null ]; + var snapStr = TU.getRaw("charter.snap.snap"); + for (_quant in quants) newChilds.push({ - label: '${_quant}x Grid Snap', + label: snapStr.format([_quant]), onSelect: (_) -> {setquant(_quant); buildSnapsUI();}, icon: _quant == quant ? 1 : 0 }); @@ -1900,11 +2087,15 @@ class Charter extends UIState { return newChilds; } - inline function _note_addsustain(t) + inline function _note_addsustain(t) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SUSTAINADD_SOUND)); changeNoteSustain(1); + } - inline function _note_subtractsustain(t) + inline function _note_subtractsustain(t) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SUSTAINDELETE_SOUND)); changeNoteSustain(-1); + } function _note_selectall(_) { selection = cast notesGroup.members.copy(); @@ -1959,29 +2150,29 @@ class Charter extends UIState { var noteTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[noteIndex]; var newChilds:Array<UIContextMenuOption> = [ { - label: "Add sustain length", + label: translate("note.addSustainLength"), keybind: [E], onSelect: _note_addsustain }, { - label: "Subtract sustain length", + label: translate("note.subtractSustainLength"), keybind: [Q], onSelect: _note_subtractsustain }, null, { - label: "Select all", + label: translate("note.selectAll"), keybind: [CONTROL, A], onSelect: _note_selectall }, { - label: "Select measure", + label: translate("note.selectMeasure"), keybind: [CONTROL, SHIFT, A], onSelect: _note_selectmeasure }, null, { - label: "(0) Default Note", + label: "(0) " + translate("noteTypes.default"), keybind: [ZERO], onSelect: (_) -> {changeNoteType(0);}, icon: this.noteType == 0 ? 1 : 0 @@ -1992,6 +2183,7 @@ class Charter extends UIState { for (i=>type in noteTypes) { var realNoteID:Int = i+1; // Default Note not stored var newChild:UIContextMenuOption = { + // TODO: make this translatable? label: '(${realNoteID}) ${type}', onSelect: (_) -> {changeNoteType(realNoteID);}, icon: this.noteType == realNoteID ? 1 : 0 @@ -2000,7 +2192,7 @@ class Charter extends UIState { newChilds.push(newChild); } newChilds.push({ - label: "Edit Note Types List", + label: translate("note.editNoteTypesList"), color: 0xFF959829, icon: 4, onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;}, onSelect: editNoteTypesList @@ -2056,14 +2248,40 @@ class Charter extends UIState { } 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; @@ -2145,7 +2363,7 @@ class Charter extends UIState { } @:noCompletion public function __clearStatics() { - selection = null; undos = null; clipboard = null; playtestInfo = null; + selection = null; undos = null; clipboard = null; playtestInfo = null; waveformHandler.destroy(); Charter.waveformHandler = null; autoSaveTimer = 0; } @@ -2193,6 +2411,7 @@ enum CharterChange { 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>); diff --git a/source/funkin/editors/charter/CharterAutoSaveUI.hx b/source/funkin/editors/charter/CharterAutoSaveUI.hx index 9ed9d692..901cba9a 100644 --- a/source/funkin/editors/charter/CharterAutoSaveUI.hx +++ b/source/funkin/editors/charter/CharterAutoSaveUI.hx @@ -26,7 +26,7 @@ class CharterAutoSaveUI extends UISliceSprite { icon.animation.play("icon"); icon.animation.curAnim.curFrame = 0; members.push(icon); - members.push(autosavingText = new UIText(x+12+10+4, y+8, 0, "Autosaving in 10 seconds...", 12)); + members.push(autosavingText = new UIText(x+12+10+4, y+8, 0, TU.translate("editor.autosavingIn", ["?", "..."]), 12)); progressBarBack = new FlxSprite(x + 10, y + bHeight - 20).makeGraphic(Std.int(bWidth-20), 10, 0x00000000, true); progressBarBack.drawRoundRect(0, 0, progressBarBack.width, progressBarBack.height, 4, 6, 0xFF727272, null, {smoothing: false}); @@ -45,7 +45,8 @@ class CharterAutoSaveUI extends UISliceSprite { icon.animation.curAnim.curFrame = 2; (new FlxTimer()).start(1, (_) -> {disappearAnimation(true);}); - autosavingText.text += " (Canceled)!"; cancelButton.visible = false; + autosavingText.text += " " + TU.translate("editor.autosavingCanceledSuffix"); + cancelButton.visible = false; for (member in [this, autosavingText]) member.color = 0xFFE67F7F; for (member in [progressBar, progressBarBack]) member.color = 0xFFE67F7F; }, 24, 14); @@ -68,8 +69,10 @@ class CharterAutoSaveUI extends UISliceSprite { __tween = FlxTween.num(0, 1, time, null, (v:Float) -> { if ((progress = v) < .95) { - autosavingText.text = 'Autosaving in ${Math.min(Math.round(time), 1+Math.floor(Math.abs(time-(progress*time))))} seconds'; - autosavingText.text += [for (i in 0...(Math.floor((progress*time*3)%4))) "."].join(""); + autosavingText.text = TU.translate("editor.autosavingIn", [ + Math.min(Math.round(time), 1+Math.floor(Math.abs(time-(progress*time)))), + ".".repeat(Math.floor((progress*time*3)%4)) + ]); } }); __timer = new FlxTimer(); @@ -79,6 +82,8 @@ class CharterAutoSaveUI extends UISliceSprite { icon.animation.curAnim.curFrame = 1; for (member in [this, progressBar, progressBarBack, autosavingText]) member.color = 0xFFA3EC95; + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_AUTOSAVE_SOUND)); + (new FlxTimer()).start(1, (_) -> {disappearAnimation();}); }); } @@ -105,11 +110,11 @@ 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() { - autosavingText.text = "Autosaving in seconds..."; progress = 0; icon.animation.curAnim.curFrame = 0; + progress = 0; icon.animation.curAnim.curFrame = 0; for (member in [this, autosavingText, progressBar, progressBarBack]) member.color = 0xFFFFFFFF; x = -(320); alpha=0; FlxTween.cancelTweensOf(this); cancelled = false; cancelButton.visible = true; diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index 165b4d6a..a4e35ce6 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -43,8 +43,6 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> { 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; @@ -80,8 +78,6 @@ 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; @@ -174,8 +170,7 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> { public var waveformSprite:FlxSprite; public var conductorFollowerSpr:FlxSprite; - public var beatSeparator:FlxBackdrop; - public var sectionSeparator:FlxBackdrop; + public var beatSeparator:CharterGridSeperator; public var notesGroup:NotesDrawGroup = new NotesDrawGroup(); public var strumLine:CharterStrumline; @@ -197,23 +192,14 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> { 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); @@ -263,7 +249,7 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> { 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; @@ -274,9 +260,6 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> { if (__lastKeyCount != keyCount) gridShader.hset("segments", keyCount); __lastKeyCount = keyCount; - sectionSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1; - beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; - topLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom)); topLimit.updateHitbox(); topLimit.y = -topLimit.height; @@ -284,7 +267,7 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> { bottomLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom)); bottomLimit.updateHitbox(); - for (spr in [conductorFollowerSpr, sectionSeparator, beatSeparator, topSeparator, bottomSeparator]) { + for (spr in [conductorFollowerSpr, beatSeparator, topSeparator, bottomSeparator]) { spr.scale.x = keyCount * 40; spr.updateHitbox(); } @@ -310,9 +293,161 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> { } } +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; + } + + 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); + } + } + } + } + + 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; @@ -325,25 +460,11 @@ class EventBackdrop extends FlxBackdrop { 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); @@ -364,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 = this.global ? x : ((x+width) - 20); - if (eventSecSeparator.visible) eventSecSeparator.draw(); - eventBeatSeparator.cameras = cameras; - eventBeatSeparator.x = this.global ? x : ((x+width) - 10); - if (eventBeatSeparator.visible) eventBeatSeparator.draw(); + eventBeatSeparator.xPos = global ? x : x+width; + eventBeatSeparator.draw(); - topSeparator.x = this.global ? 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 = this.global ? 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..c847b60f --- /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 = TU.translate("charter.bookmarks.createBookmarkTitle"); + + 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, TU.translate("charter.bookmarks.newBookmarkName"), 276); + textBox.antialiasing = true; + add(textBox); + addLabelOn(textBox, TU.translate("charter.bookmarks.createBookmarkName")); + + colorPicker = new UICompactColorwheel(textBox.x, textBox.y + textBox.bHeight + 34, flixel.util.FlxColor.RED); + add(colorPicker); + addLabelOn(colorPicker, TU.translate("charter.bookmarks.createBookmarkColor")); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { + callback(true, textBox.label.text, colorPicker.curColor, bookmarkStep); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.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..e471fe0c --- /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 = TU.translate("charter.bookmarks.editBookmarkListTitle"); + winWidth = 380; + winHeight = 390; + + super.create(); + + var title:UIText; + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("charter.bookmarks.editBookmarksTitle"), 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, TU.translate("editor.saveClose"), function() { + saveList(); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.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/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 54dffb6a..05b2a7e9 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,9 +1,14 @@ 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; @@ -58,8 +63,8 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { selectedColorTransform(colorTransform); useColorTransform = true; - for (sprite in icons) { - @:privateAccess sprite.colorTransform.__identity(); + for (sprite in icons) @:privateAccess { + sprite.colorTransform.__identity(); selectedColorTransform(sprite.colorTransform); } @@ -76,25 +81,84 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { 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; @@ -106,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() { @@ -154,19 +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); + } + + 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 09f96de5..dca9d308 100644 --- a/source/funkin/editors/charter/CharterEventAdd.hx +++ b/source/funkin/editors/charter/CharterEventAdd.hx @@ -15,7 +15,7 @@ class CharterEventAdd extends UISliceSprite { this.global = flipX = global; - sideText = new UIText(0, -40, 0, global ? "Global Event" : "Local Event", 12); + sideText = new UIText(0, -40, 0, TU.translate("charter.eventType-" + (global ? "global" : "local")), 12); sideText.alignment = "center"; sideText.alpha = 0.75; text = new UIText(0, 0, 0, ""); @@ -29,15 +29,27 @@ class CharterEventAdd extends UISliceSprite { if (FlxG.mouse.justReleased && FlxG.state.subState == null) { if (curCharterEvent != null) Charter.instance.openSubState(new CharterEventScreenNew(curCharterEvent)); - else - Charter.instance.openSubState(new CharterEventScreen(step, global)); + else { + CharterEventGroup.stopThisFuckingShitDudeIstg = true; + var chartEvent = new CharterEvent(step, [], global); + chartEvent.global = global; + + Charter.instance.createSelection([chartEvent]); + Charter.instance.openSubState(new CharterEventScreenNew(chartEvent)); + sprAlpha = 0; + alpha = sprAlpha * 0.75; + text.alpha = sprAlpha; + + sideText.alpha = sprAlpha; + } } } public override function update(elapsed:Float) { super.update(elapsed); - text.follow(this, global ? bWidth - text.width - (text.text == "Add event" ? 15 : 20) : 20, (bHeight - text.height) / 2); + if (FlxG.state.subState != null) return; + text.follow(this, global ? bWidth - text.width - (text.text == TU.translate("charter.addEvent") ? 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; @@ -46,18 +58,20 @@ class CharterEventAdd extends UISliceSprite { } public function updatePos(step:Float) { + if (FlxG.state.subState != null) return; curCharterEvent = null; this.step = step; this.y = (step * 40) - (bHeight / 2); - text.text = "Add event"; + text.text = TU.translate("charter.addEvent"); 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) { + if (FlxG.state.subState != null) return; curCharterEvent = event; this.y = event.y; - text.text = "Edit"; + text.text = TU.translate("charter.editEvent"); 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); } diff --git a/source/funkin/editors/charter/CharterEventGroup.hx b/source/funkin/editors/charter/CharterEventGroup.hx index d7be0864..41c9e09e 100644 --- a/source/funkin/editors/charter/CharterEventGroup.hx +++ b/source/funkin/editors/charter/CharterEventGroup.hx @@ -4,6 +4,7 @@ import flixel.util.FlxSort; import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; class CharterEventGroup extends FlxTypedGroup<CharterEvent> { + public static var stopThisFuckingShitDudeIstg = false; public var eventsBackdrop:EventBackdrop; public var eventsRowText:UIText; @@ -11,12 +12,12 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> { var __lastSort:Int = 0; public override function update(elapsed:Float) { - filterEvents(); + super.update(elapsed); + if (!CharterEventGroup.stopThisFuckingShitDudeIstg) filterEvents(); 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); } public override function remove(v:CharterEvent, force:Bool = true):CharterEvent { diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 07f5004e..a6cb28b8 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -38,7 +38,7 @@ class CharterEventScreen extends UISubstateWindow { var creatingEvent:Bool = chartEvent == null; if (creatingEvent) chartEvent = new CharterEvent(step, [], global); - winTitle = creatingEvent ? "Create Event Group" : "Edit Event Group"; + winTitle = TU.translate("charterEventScreen.title" + (creatingEvent ? "-creating" : "-editing")); winWidth = 960; super.create(); @@ -59,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); @@ -67,10 +67,12 @@ class CharterEventScreen extends UISubstateWindow { paramsPanel = new FlxGroup(); add(paramsPanel); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveCurTab(); chartEvent.refreshEventIcons(); + Charter.instance.updateBPMEvents(); + if (events.length <= 0 && !creatingEvent) Charter.instance.deleteSelection([chartEvent]); else if (events.length > 0) { @@ -78,12 +80,11 @@ class CharterEventScreen extends UISubstateWindow { 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.refreshEventIcons(); - Charter.instance.updateBPMEvents(); - Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); } } @@ -93,7 +94,7 @@ class CharterEventScreen extends UISubstateWindow { saveButton.x -= saveButton.bWidth; add(saveButton); - closeButton = new UIButton(saveButton.x - 10, saveButton.y, "Close", ()->close()); + closeButton = new UIButton(saveButton.x - 10, saveButton.y, TU.translate("editor.close"), ()->close()); closeButton.color = 0xFFFF0000; closeButton.x -= closeButton.bWidth; add(closeButton); @@ -120,12 +121,20 @@ class CharterEventScreen extends UISubstateWindow { if (id >= 0 && id < events.length) { curEvent = id; var curEvent = events[curEvent]; - eventName.text = curEvent.name; + + var visualName = curEvent.name; + var tuId = "charter.events." + TU.raw2Id(curEvent.name); + if(TU.exists(tuId)) + visualName = TU.translate(tuId); + + eventName.text = visualName; // add new elements var y:Float = eventName.y + eventName.height + 10; for(k=>param in EventsData.getEventParams(curEvent.name)) { function addLabel() { - var label:UIText = new UIText(eventName.x, y, 0, param.name); + var visualName = param.name; + // TODO: add translations + var label:UIText = new UIText(eventName.x, y, 0, visualName); y += label.height + 4; paramsPanel.add(label); }; @@ -153,7 +162,11 @@ class CharterEventScreen extends UISubstateWindow { numericStepper; case TStrumLine: addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, [for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})'], cast value); + var strumlineFormat = TU.getRaw("charterEventScreen.strumLine.format"); + var dropdown = new UIDropDown(eventName.x, y, 320, 32, [ + for(k=>s in cast(FlxG.state, Charter).strumLines.members) + strumlineFormat.format([k+1, s.strumLine.characters[0]]) + ], cast value); paramsPanel.add(dropdown); paramsFields.push(dropdown); dropdown; case TColorWheel: @@ -194,7 +207,7 @@ class CharterEventScreen extends UISubstateWindow { y += cast(lastAdded, FlxSprite).height + 6; } } else { - eventName.text = "No event"; + eventName.text = TU.translate("charterEventScreen.noEvent"); // Maybe should be No Events? curEvent = -1; } } @@ -202,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) { @@ -227,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; } } diff --git a/source/funkin/editors/charter/CharterEventScreenNew.hx b/source/funkin/editors/charter/CharterEventScreenNew.hx index 06ee004c..6f1fb7fd 100644 --- a/source/funkin/editors/charter/CharterEventScreenNew.hx +++ b/source/funkin/editors/charter/CharterEventScreenNew.hx @@ -88,7 +88,7 @@ class CharterEventScreenNew extends MusicBeatSubstate { }); 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)); @@ -284,19 +284,19 @@ class CharterEventScreenNew extends MusicBeatSubstate { } public function quit() { + CharterEventGroup.stopThisFuckingShitDudeIstg = false; saveCurTab(); - chartEvent.refreshEventIcons(); 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]; - chartEvent.refreshEventIcons(); - Charter.instance.updateBPMEvents(); Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); } + chartEvent.refreshEventIcons(); + Charter.instance.updateBPMEvents(); close(); } diff --git a/source/funkin/editors/charter/CharterEventTypeSelection.hx b/source/funkin/editors/charter/CharterEventTypeSelection.hx index d83022cc..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,13 +15,14 @@ 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() { - winTitle = "Choose an event type..."; + winTitle = TU.translate("charterEventTypeSelection.title"); super.create(); var w:Int = winWidth - 20; @@ -32,8 +35,21 @@ class CharterEventTypeSelection extends UISubstateWindow { 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 button = new UIButton(0, (32 * k), eventName, function() { + var visualName = eventName; + var tuId = "charter.events." + TU.raw2Id(eventName); + if(TU.exists(tuId)) + visualName = TU.translate(tuId); + var button = new UIButton(0, (32 * k), visualName, function() { close(); callback(eventName); }, w); @@ -52,11 +68,16 @@ 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)); - add(new UIButton(10, windowSpr.bHeight-42, "Cancel", function() { + add(new UIButton(10, windowSpr.bHeight-42, TU.translate("editor.cancel"), function() { close(); }, w)); @@ -79,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; diff --git a/source/funkin/editors/charter/CharterMetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx index 6ba0fc62..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; @@ -30,8 +31,11 @@ class CharterMetaDataScreen extends UISubstateWindow { this.metadata = metadata; } + inline function translate(id:String):String + return TU.translate("charterMetaDataScreen." + id); + public override function create() { - winTitle = "Edit Metadata"; + winTitle = translate("title"); winWidth = 1056; winHeight = 520; @@ -45,54 +49,54 @@ class CharterMetaDataScreen 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, "Song Data", 28)); + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("songData"), 28)); songNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.name); add(songNameTextBox); - addLabelOn(songNameTextBox, "Song Name"); + addLabelOn(songNameTextBox, translate("songName")); bpmStepper = new UINumericStepper(songNameTextBox.x + 320 + 26, songNameTextBox.y, metadata.bpm, 1, 2, 1, null, 90); add(bpmStepper); - addLabelOn(bpmStepper, "BPM"); + addLabelOn(bpmStepper, translate("bpm")); beatsPerMeasureStepper = new UINumericStepper(bpmStepper.x + 60 + 26, bpmStepper.y, metadata.beatsPerMeasure, 1, 0, 1, null, 54); add(beatsPerMeasureStepper); - addLabelOn(beatsPerMeasureStepper, "Time Signature"); + addLabelOn(beatsPerMeasureStepper, translate("timeSignature")); 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, "Menus Data (Freeplay/Story)", 28)); + add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, translate("menusData"), 28)); displayNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.displayName); add(displayNameTextBox); - addLabelOn(displayNameTextBox, "Display Name"); + addLabelOn(displayNameTextBox, translate("displayName")); iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, metadata.icon, 150); iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);} add(iconTextBox); - addLabelOn(iconTextBox, "Icon"); + addLabelOn(iconTextBox, translate("icon")); updateIcon(metadata.icon); - opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, "Opponent Mode", metadata.opponentModeAllowed); + opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, translate("opponentMode"), metadata.opponentModeAllowed); add(opponentModeCheckbox); - addLabelOn(opponentModeCheckbox, "Modes Allowed"); + addLabelOn(opponentModeCheckbox, translate("modesAllowed")); - coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", metadata.coopAllowed); + 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, "Color"); + addLabelOn(colorWheel, translate("color")); difficultiesTextBox = new UITextBox(opponentModeCheckbox.x, opponentModeCheckbox.y + 6 + 32 + 26, metadata.difficulties.join(", ")); add(difficultiesTextBox); - addLabelOn(difficultiesTextBox, "Difficulties"); + 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() { @@ -101,19 +105,19 @@ class CharterMetaDataScreen extends UISubstateWindow { for (val in Reflect.fields(metadata.customValues)) customPropertiesButtonList.add(new PropertyButton(val, Reflect.field(metadata.customValues, val), customPropertiesButtonList)); add(customPropertiesButtonList); - addLabelOn(customPropertiesButtonList, "Custom Values (Advanced)"); + addLabelOn(customPropertiesButtonList, translate("customValues")); for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox]) {checkbox.y += 6; checkbox.x += 4;} - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, TU.translate("editor.saveClose"), function() { saveMeta(); close(); }, 125); saveButton.x -= saveButton.bWidth; saveButton.y -= saveButton.bHeight; - closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); closeButton.color = 0xFFFF0000; @@ -124,22 +128,14 @@ 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() { @@ -154,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/CharterNoteTypesList.hx b/source/funkin/editors/charter/CharterNoteTypesList.hx index 7056d1b8..d9a2b66f 100644 --- a/source/funkin/editors/charter/CharterNoteTypesList.hx +++ b/source/funkin/editors/charter/CharterNoteTypesList.hx @@ -15,32 +15,33 @@ class CharterNoteTypesList extends UISubstateWindow { Charter.instance.vocals.pause(); for (strumLine in Charter.instance.strumLines.members) strumLine.vocals.pause(); - winTitle = 'Note Types List Editor'; + winTitle = TU.translate("charterNoteTypesList.title"); winWidth = 380; winHeight = 390; super.create(); var title:UIText; - add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Note Types", 28)); + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("charterNoteTypesList.editNoteTypes"), 28)); 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, null, FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0); - noteTypesList.addButton.callback = () -> noteTypesList.add(new NoteTypeButton(0, 0, 'Note Type ${noteTypesList.buttons.members.length}', noteTypesList.buttons.members.length, noteTypesFileList, noteTypesList)); + 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 noteTypesList.add(new NoteTypeButton(0, 0, noteType, i, noteTypesFileList, noteTypesList)); add(noteTypesList); noteTypesList.frames = Paths.getFrames('editors/ui/inputbox'); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveList(); close(); }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); add(closeButton); diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx index e16b0a9c..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; @@ -26,26 +27,32 @@ class CharterSelection extends EditorTreeMenu { freeplayList = FreeplaySonglist.get(false); var list:Array<OptionType> = [ - for(s in freeplayList.songs) new EditorIconOption(s.name, "Press ACCEPT to choose a difficulty to edit.", s.icon, function() { + for(s in freeplayList.songs) new EditorIconOption(s.name, TU.translate("charterSelection.acceptSong"), s.icon, function() { curSong = s; var list:Array<OptionType> = [ for(d in s.difficulties) if (d != "") - new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() { + new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() { FlxG.switchState(new Charter(s.name, d)); }) ]; - list.push(new NewOption("New Difficulty", "Press ACCEPT to create a new difficulty.", function() { + #if sys + var newDiff = TU.translate("charterSelection.newDifficulty"); + list.push(new NewOption(newDiff, newDiff, function() { FlxG.state.openSubState(new ChartCreationScreen(saveChart)); })); - optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list)); - }, s.parsedColor.getDefault(0xFFFFFFFF)) + #end + optionsTree.add(new OptionsScreen(s.name, TU.translate("charterSelection.selectDifficulty"), list)); + }, s.color.getDefault(0xFFFFFFFF)) ]; - list.insert(0, new NewOption("New Song", "Press ACCEPT to create a new song.", function() { + #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("Chart Editor", "Select a song to modify the charts from.", list); + main = new OptionsScreen(TU.translate("editor.chart.name"), TU.translate("charterSelection.desc"), list); DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]); } @@ -88,12 +95,13 @@ 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) { - openSubState(new UIWarningSubstate("Creating Song: Error!", "The song you are trying to create already exists, if you would like to override it delete the song first!", [ - {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [ + {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}}, ])); return; } @@ -109,24 +117,31 @@ 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 - var option = new EditorIconOption(creation.meta.name, "Press ACCEPT to choose a difficulty to edit.", creation.meta.icon, function() { + 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> = [ for(d in creation.meta.difficulties) - if (d != "") new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() { + if (d != "") new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() { FlxG.switchState(new Charter(creation.meta.name, d)); }) ]; - list.push(new NewOption("New Difficulty", "Press ACCEPT to create a new difficulty.", function() { + var newDiff = TU.translate("charterSelection.newDifficulty"); + list.push(new NewOption(newDiff, newDiff, function() { FlxG.state.openSubState(new ChartCreationScreen(saveChart)); })); - optionsTree.insert(1, new OptionsScreen(creation.meta.name, "Select a difficulty to continue.", list)); - }, creation.meta.parsedColor.getDefault(0xFFFFFFFF)); + optionsTree.insert(1, new OptionsScreen(creation.meta.name, TU.translate("charterSelection.selectDifficulty"), list)); + }, creation.meta.color.getDefault(0xFFFFFFFF)); // Add to List freeplayList.songs.insert(0, creation.meta); @@ -137,8 +152,8 @@ class CharterSelection extends EditorTreeMenu { var difficultyAlreadyExists:Bool = curSong.difficulties.contains(name); if (difficultyAlreadyExists) { - openSubState(new UIWarningSubstate("Creating Chart: Error!", "The chart you are trying to create already exists, if you would like to override it delete the chart first!", [ - {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [ + {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}}, ])); return; } @@ -150,8 +165,9 @@ class CharterSelection extends EditorTreeMenu { CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, Flags.JSON_PRETTY_PRINT)); // Add to List + // duplicated code, todo: fix this curSong.difficulties.push(name); - var option = new TextOption(name, "Press ACCEPT to edit the chart for the selected difficulty", function() { + var option = new TextOption(name, TU.translate("charterSelection.acceptDifficulty"), function() { FlxG.switchState(new Charter(curSong.name, name)); }); optionsTree.members[optionsTree.members.length-1].insert(optionsTree.members[optionsTree.members.length-1].length-1, option); @@ -160,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/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index 22caa289..8d0a0b21 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -88,8 +88,7 @@ class CharterStrumline extends UISprite { 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; } @@ -136,8 +135,16 @@ class CharterStrumline extends UISprite { healthIcons.add(healthIcon); } - vocals = null; - vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound(); + var asset = strumLine.vocalsSuffix.length > 0 ? Assets.getSound(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : null; + + if (vocals == null) FlxG.sound.list.add(vocals = new FlxSound()); + if (asset != null) { + vocals.reset(); + vocals.loadEmbedded(asset); + } + else { + vocals.destroy(); + } vocals.group = FlxG.sound.defaultMusicGroup; } } @@ -145,7 +152,8 @@ class CharterStrumline extends UISprite { class CharterStrumlineOptions extends UITopMenuButton { var strLine:CharterStrumline; public function new(parent:CharterStrumline) { - super(0, 95, null, "Options ↓", []); + // TODO: better id for this + super(0, 95, null, TU.translate("charter.strumLine.button-name"), []); strLine = parent; } @@ -159,14 +167,14 @@ class CharterStrumlineOptions extends UITopMenuButton { public override function openContextMenu() { contextMenu = [ { - label: "Hitsounds", + label: TU.translate("charter.strumLine.hitsounds"), onSelect: function(_) { strLine.hitsounds = !strLine.hitsounds; }, icon: strLine.hitsounds ? 1 : 0 }, { - label: "Mute Vocals", + label: TU.translate("charter.strumLine.muteVocals"), onSelect: function(_) { strLine.vocals.volume = strLine.vocals.volume > 0 ? 0 : 1; }, @@ -174,7 +182,7 @@ class CharterStrumlineOptions extends UITopMenuButton { }, null, { - label: "Edit", + label: TU.translate("charter.strumLine.edit"), onSelect: function (_) { Charter.instance.editStrumline(strLine.strumLine); }, @@ -182,7 +190,7 @@ class CharterStrumlineOptions extends UITopMenuButton { icon: 4 }, { - label: "Delete", + label: TU.translate("charter.strumLine.delete"), onSelect: function (_) { Charter.instance.deleteStrumlineFromData(strLine.strumLine); }, @@ -192,7 +200,7 @@ class CharterStrumlineOptions extends UITopMenuButton { ]; contextMenu.insert(0, { - label: "No Waveform", + label: TU.translate("charter.strumLine.noWaveform"), onSelect: function(_) {strLine.selectedWaveform = -1;}, icon: strLine.selectedWaveform == -1 ? 1 : 0 }); diff --git a/source/funkin/editors/charter/CharterStrumlineButton.hx b/source/funkin/editors/charter/CharterStrumlineButton.hx index 39207e5f..a1955248 100644 --- a/source/funkin/editors/charter/CharterStrumlineButton.hx +++ b/source/funkin/editors/charter/CharterStrumlineButton.hx @@ -59,7 +59,7 @@ class CharterStrumlineButton extends UISprite { super.update(elapsed); button.updateSpriteRect(); - if(UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect)) { + 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 8d4517d9..e4b1b318 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -56,7 +56,7 @@ class CharterStrumlineScreen extends UISubstateWindow { keyCount: 4 }; - winTitle = creatingStrumLine ? 'Creating strumline #$strumLineID' : 'Editing strumline #$strumLineID properties'; + winTitle = TU.translate("charterStrumlineScreen.win-title" + (creatingStrumLine ? "-creating" : "-editing"), ["#" + strumLineID]); winWidth = 720; winHeight = 400; FlxG.sound.music.pause(); @@ -69,26 +69,26 @@ class CharterStrumlineScreen 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, creatingStrumLine ? "Create New Strumline" : "Edit Strumline Properties", 28)); + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("charterStrumlineScreen.title" + (creatingStrumLine ? "-creating" : "-editing")), 28)); 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, null, FlxPoint.get(250, 54), null, 0); - charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, "New Char", charFileList, charactersList)); + charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, TU.translate("charterStrumLine.newChar"), charFileList, charactersList)); charactersList.cameraSpacing = 0; for (i in strumLine.characters) charactersList.add(new CharacterButton(0, 0, i, charFileList, charactersList)); add(charactersList); - addLabelOn(charactersList, "Characters"); + addLabelOn(charactersList, TU.translate("charterStrumLine.characters")); charactersList.frames = Paths.getFrames('editors/ui/inputbox'); typeDropdown = new UIDropDown(charactersList.x + charactersList.bWidth + 16, charactersList.y, 200, 32, ["OPPONENT", "PLAYER", "ADDITIONAL"], strumLine.type); add(typeDropdown); - addLabelOn(typeDropdown, "Type"); + addLabelOn(typeDropdown, TU.translate("charterStrumLine.type")); - usesChartScrollSpeed = new UICheckbox(typeDropdown.x + 104, typeDropdown.y + 135, "Uses charts scroll speed?", strumLine.scrollSpeed == null); + usesChartScrollSpeed = new UICheckbox(typeDropdown.x + 104, typeDropdown.y + 135, TU.translate("charterStrumLine.useChartScrollSpeed"), strumLine.scrollSpeed == null); usesChartScrollSpeed.onChecked = function(b) { if(b) { @@ -108,21 +108,21 @@ class CharterStrumlineScreen extends UISubstateWindow { scrollSpeedStepper.selectable = true; } add(scrollSpeedStepper); - addLabelOn(scrollSpeedStepper, "Scroll Speed"); + addLabelOn(scrollSpeedStepper, TU.translate("charterStrumLine.scrollSpeed")); var stagePositionI = strumLine.position == null ? strumLine.type : ["DAD", "BOYFRIEND", "GIRLFRIEND"].indexOf(strumLine.position.toUpperCase()); stagePositionDropdown = new UIDropDown(typeDropdown.x + 200 - 32 + 26, typeDropdown.y, 200, 32, ["DAD", "BOYFRIEND", "GIRLFRIEND"], stagePositionI); add(stagePositionDropdown); - addLabelOn(stagePositionDropdown, "Stage Position"); + 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, 84); add(hudScaleStepper); - addLabelOn(hudScaleStepper, "Scale"); + 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"); + addLabelOn(hudSpacingStepper, TU.translate("charterStrumLine.spacing")); var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos; @@ -132,23 +132,23 @@ class CharterStrumlineScreen extends UISubstateWindow { hudXStepper = new UINumericStepper(hudSpacingStepper.x + 80, hudScaleStepper.y, startingPos.x, 0.01, 2, 0, 2, 84); add(hudXStepper); - addLabelOn(hudXStepper, "Hud Position (X [Ratio 0-1],Y)"); + addLabelOn(hudXStepper, TU.translate("charterStrumLine.hudPos")); add(new UIText(hudXStepper.x + 84 - 32 + 0, hudXStepper.y + 9, 0, ",", 22)); hudYStepper = new UINumericStepper(hudXStepper.x + 84 - 32 + 26, hudXStepper.y, startingPos.y, 0.001, 2, null, null, 84); add(hudYStepper); - visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, "Visible?", strumLine.visible); + visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, TU.translate("charterStrumLine.visible"), strumLine.visible); add(visibleCheckbox); - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveStrumline(); close(); }, 125); add(saveButton); - closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, creatingStrumLine ? "Cancel" : "Close", function() { + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor." + (creatingStrumLine ? "cancel" : "close")), function() { if (creatingStrumLine && onSave != null) onSave(null); close(); }, 125); @@ -170,12 +170,12 @@ 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, "Vocal Suffix"); + 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"); + addLabelOn(keyCountStepper, TU.translate("charterStrumLine.keyCount")); // } strumLineCam = new HudCamera(); @@ -242,7 +242,8 @@ class CharacterButton extends UIButton { 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); @@ -254,11 +255,10 @@ 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), null, function () { diff --git a/source/funkin/editors/charter/CharterWaveformHandler.hx b/source/funkin/editors/charter/CharterWaveformHandler.hx index 05cced6f..2ba9756e 100644 --- a/source/funkin/editors/charter/CharterWaveformHandler.hx +++ b/source/funkin/editors/charter/CharterWaveformHandler.hx @@ -29,7 +29,7 @@ class CharterWaveformHandler extends FlxBasic { public function generateData(name:String, sound:FlxSound):BitmapData { if (!sounds.exists(name)) sounds.set(name, sound); - if (!analyzers.exists(name)) + if (!analyzers.exists(name)) analyzers.set(name, new AudioAnalyzer(sound)); var analyzer:AudioAnalyzer = analyzers.get(name); diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index 3548171d..eefdc61a 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,17 +57,33 @@ 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; } + inline function translate(id:String):String + return TU.translate("songCreationScreen." + id); + + inline function translateMeta(id:String):String + return TU.translate("charterMetaDataScreen." + id); + public override function create() { - winTitle = "Creating New Song"; + winTitle = translate("win-title"); winWidth = 748 - 32 + 40; winHeight = 520; @@ -55,32 +91,30 @@ 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; - songDataGroup.add(songTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Song Info", 28)); + songDataGroup.add(songTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28)); - songNameTextBox = new UITextBox(songTitle.x, songTitle.y + songTitle.height + 36, "Song Name"); + songNameTextBox = new UITextBox(songTitle.x, songTitle.y + songTitle.height + 36, translateMeta("songName")); songDataGroup.add(songNameTextBox); - addLabelOn(songNameTextBox, "Song Name"); + addLabelOn(songNameTextBox, translateMeta("songName")); bpmStepper = new UINumericStepper(songNameTextBox.x + 320 + 26, songNameTextBox.y, 100, 1, 2, 1, null, 90); songDataGroup.add(bpmStepper); - addLabelOn(bpmStepper, "BPM"); + addLabelOn(bpmStepper, translateMeta("bpm")); beatsPerMeasureStepper = new UINumericStepper(bpmStepper.x + 60 + 26, bpmStepper.y, 4, 1, 0, 1, null, 54); songDataGroup.add(beatsPerMeasureStepper); - addLabelOn(beatsPerMeasureStepper, "Time Signature"); + addLabelOn(beatsPerMeasureStepper, translateMeta("timeSignature")); 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); - - var voicesUIText:UIText = null; + denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54); + songDataGroup.add(denominatorStepper); instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (path, res) { if (path == null || res == null) return; @@ -89,8 +123,8 @@ class SongCreationScreen extends UISubstateWindow { instExplorer.uiElement = audioPlayer; }); songDataGroup.add(instExplorer); - addLabelOn(instExplorer, "Inst Audio File").applyMarkup( - "Inst Audio File $* Required$", + addLabelOn(instExplorer, "").applyMarkup( + translate("instAudio"), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (path, res) { @@ -100,41 +134,114 @@ class SongCreationScreen extends UISubstateWindow { voicesExplorer.uiElement = audioPlayer; }); songDataGroup.add(voicesExplorer); - addLabelOn(voicesExplorer, "Vocal Audio File"); + + /*voicesUIText = addLabelOn(voicesExplorer, ""); + voicesUIText.applyMarkup( + translate("voicesAudio"), + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);*/ + + addLabelOn(voicesExplorer, translate("voicesAudio")); + + importFrom = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, translate("importFrom"), function() { + winTitle = translate("win-title-importing"); + isImporting = true; + updatePagesTexts(); + refreshPages(); + }, 150); + songDataGroup.add(importFrom); var menuTitle:UIText; - menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Menus Data (Freeplay/Story)", 28)); + menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translateMeta("menusData"), 28)); - displayNameTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36, "Display Name"); + displayNameTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36, translateMeta("displayName")); menuDataGroup.add(displayNameTextBox); - addLabelOn(displayNameTextBox, "Display Name"); + addLabelOn(displayNameTextBox, translateMeta("displayName")); - iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, "Icon", 150); + iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, "face", 150); iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);} menuDataGroup.add(iconTextBox); - addLabelOn(iconTextBox, "Icon"); + addLabelOn(iconTextBox, translateMeta("icon")); - updateIcon("Icon"); + updateIcon("face"); - opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, "Opponent Mode", true); + opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, translateMeta("opponentMode"), true); menuDataGroup.add(opponentModeCheckbox); - addLabelOn(opponentModeCheckbox, "Modes Allowed"); + addLabelOn(opponentModeCheckbox, translateMeta("modesAllowed")); - coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", true); + coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, translateMeta("coopAllowed"), true); menuDataGroup.add(coopAllowedCheckbox); colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, 0xFFFFFF); menuDataGroup.add(colorWheel); - addLabelOn(colorWheel, "Color"); + addLabelOn(colorWheel, translateMeta("color")); difficultiesTextBox = new UITextBox(opponentModeCheckbox.x, opponentModeCheckbox.y + 6 + 32 + 26, ""); menuDataGroup.add(difficultiesTextBox); - addLabelOn(difficultiesTextBox, "Difficulties"); + addLabelOn(difficultiesTextBox, translateMeta("difficulties")); for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox]) {checkbox.y += 6; checkbox.x += 4;} - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + var menuTitle:UIText; + selectFormatGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("importSource"), 28)); + + engineDropdown = new UIDropDown(menuTitle.x, menuTitle.y + menuTitle.height + 36, 480, 32, [translate("legacyOrPsych"), translate("vslice"), translate("vsliceProject")]); + selectFormatGroup.add(engineDropdown); + addLabelOn(engineDropdown, translate("importChartFormat")); + + createSong = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "< " + translate("back"), function() { + winTitle = translate("win-title"); + isImporting = false; + updatePagesTexts(); + refreshPages(); + }, 120); + selectFormatGroup.add(createSong); + + var menuTitle:UIText; + importAudioGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("importAudios"), 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, "").applyMarkup( + translate("instAudio"), + [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, translate("voicesAudio")); + + 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, "").applyMarkup( + translate("songFileName"), + [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, "").applyMarkup( + translate("songDataFile"), + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + importMetaFile = new UIFileExplorer(importChartFile.x + 320 + 26, importChartFile.y, null, null, "json"); + importDataGroup.add(importMetaFile); + addLabelOn(importMetaFile, "").applyMarkup( + translate("songMetaFile"), + [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(); @@ -147,7 +254,7 @@ class SongCreationScreen extends UISubstateWindow { }, 125); add(saveButton); - backButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "< Back", function() { + backButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "< " + translate("back"), function() { curPage--; refreshPages(); @@ -155,7 +262,7 @@ class SongCreationScreen extends UISubstateWindow { }, 125); add(backButton); - closeButton = new UIButton(backButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() { + closeButton = new UIButton(backButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() { close(); }, 125); add(closeButton); @@ -167,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 == translate("vsliceProject"); + + 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 == translate("vslice"); + 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); @@ -185,70 +316,145 @@ 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 ? "Save & Close" : 'Next >'; - titleSpr.text = 'Creating New Song (${curPage+1}/${pages.length})'; + saveButton.field.text = curPage == pages.length-1 ? TU.translate("editor.saveClose") : translate("next") + ' >'; + titleSpr.text = '$winTitle (${curPage+1}/${pages.length})'; - backButton.field.text = '< Back'; + backButton.field.text = '< ' + translate("back"); backButton.visible = backButton.exists = curPage > 0; 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() { - UIUtil.confirmUISelections(this); + if (isImporting) + { + try switch(engineDropdown.index) + { + case 0 /*"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 1 /*"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/stage/StageCreationScreen.hx b/source/funkin/editors/stage/StageCreationScreen.hx index 957ff896..5802f023 100644 --- a/source/funkin/editors/stage/StageCreationScreen.hx +++ b/source/funkin/editors/stage/StageCreationScreen.hx @@ -20,8 +20,11 @@ class StageCreationScreen extends UISubstateWindow { if (onSave != null) this.onSave = onSave; } + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageCreationScreen." + id, args); + public override function create() { - winTitle = "Creating New Stage"; + winTitle = translate("win-title"); winWidth = 748 - 32 + 40; winHeight = 220; @@ -35,20 +38,20 @@ class StageCreationScreen extends UISubstateWindow { } var stageInfo:UIText; - add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Stage Info", 28)); + add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28)); - add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, "Stage Name")); - addLabelOn(stageNameTextBox, "Stage Name"); + add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, translate("stage-name"))); + addLabelOn(stageNameTextBox, translate("stage-name")); - add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, "Stage Path")); - addLabelOn(stagePathTextBox, "Stage Path"); + add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, translate("stage-path"))); + addLabelOn(stagePathTextBox, translate("stage-path")); - add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() { saveStageInfo(); close(); }, 125)); - add(closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", close, 125)); + add(closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.cancel"), close, 125)); closeButton.color = 0xFFFF0000; } diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx index 717f7cb4..fb63c10a 100644 --- a/source/funkin/editors/stage/StageEditor.hx +++ b/source/funkin/editors/stage/StageEditor.hx @@ -71,6 +71,9 @@ class StageEditor extends UIState { return "stageEditor." + id; } + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageEditor." + id, args); + public function new(stage:String) { super(); if (stage != null) __stage = stage; @@ -79,47 +82,47 @@ class StageEditor extends UIState { public override function create() { super.create(); - WindowUtils.suffix = " (Stage Editor)"; + WindowUtils.suffix = " (" + TU.translate("editor.stage.name") + ")"; SaveWarning.selectionClass = StageSelection; SaveWarning.saveFunc = () -> {_file_save(null);}; topMenu = [ { - label: "File", + label: translate("topBar.file"), childs: [ { - label: "Save", + label: translate("file.save"), keybind: [CONTROL, S], onSelect: _file_save, }, { - label: "Save As...", + label: translate("file.saveAs"), keybind: [CONTROL, SHIFT, S], onSelect: _file_saveas, }, null, { - label: "Exit", + label: translate("file.exit"), onSelect: _file_exit } ] }, { - label: "Edit", + label: translate("topBar.edit"), childs: [ { - label: "Undo", + label: translate("edit.undo"), keybind: [CONTROL, Z], onSelect: _edit_undo, }, { - label: "Redo", + label: translate("edit.redo"), keybind: [CONTROL, SHIFT, Z], onSelect: _edit_redo, }, null, { - label: "Edit Stage Info", + label: translate("edit.editStageInfo"), onSelect: (_) -> { openSubState(new UISoftcodedWindow( "layouts/stage/stageInfoScreen", @@ -130,7 +133,10 @@ class StageEditor extends UIState { "stage" => stage, "Stage" => Stage, "exID" => exID, - "CEditInfo" => StageChange.CEditInfo + "CEditInfo" => StageChange.CEditInfo, + "translate" => function (id:String, ?args:Array<Dynamic>) { + return TU.translate("stageInfoScreen." + id, args); + } ] )); }, @@ -138,56 +144,56 @@ class StageEditor extends UIState { ] }, { - label: "Select", + label: translate("topBar.select"), childs: [ { - label: "All", + label: translate("select.all"), keybind: [CONTROL, A], onSelect: (_) -> _select_all(_), }, { - label: "Deselect", + label: translate("select.deselect"), keybind: [CONTROL, D], onSelect: (_) -> _select_deselect(_), }, { - label: "Inverse", + label: translate("select.inverse"), keybind: [CONTROL, SHIFT, I], onSelect: (_) -> _select_inverse(_), }, ] }, { - label: "View", + label: translate("topBar.view"), childs: [ { - label: "Zoom in", + label: translate("view.zoomIn"), keybind: [CONTROL, NUMPADPLUS], onSelect: _view_zoomin }, { - label: "Zoom out", + label: translate("view.zoomOut"), keybind: [CONTROL, NUMPADMINUS], onSelect: _view_zoomout }, { - label: "Reset zoom", + label: translate("view.zoomReset"), keybind: [CONTROL, NUMPADZERO], onSelect: _view_zoomreset }, null, { - label: "Focus Dad", + label: translate("view.focusDad"), //keybind: [CONTROL, NUMPADZERO], onSelect: _view_focusdad }, { - label: "Focus Gf", + label: translate("view.focusGF"), //keybind: [CONTROL, NUMPADZERO], onSelect: _view_focusgf }, { - label: "Focus BF", + label: translate("view.focusBF"), //keybind: [CONTROL, NUMPADZERO], onSelect: _view_focusbf }, @@ -195,10 +201,10 @@ class StageEditor extends UIState { ] }, { - label: "Editor", + label: translate("topBar.editor"), childs: [ { - label: "Show Characters", + label: translate("editor.showCharacters"), //keybind: [CONTROL, NUMPADMINUS], onSelect: _editor_showCharacters, icon: showCharacters ? 1 : 0 @@ -502,6 +508,7 @@ class StageEditor extends UIState { function _file_save(_) { #if sys + FlxG.sound.play(Paths.sound('editors/save')); CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/data/stages/${__stage}.xml', buildStage() @@ -513,6 +520,7 @@ class StageEditor extends UIState { } function _file_saveas(_) { + FlxG.sound.play(Paths.sound('editors/save')); openSubState(new SaveSubstate(buildStage(), { defaultSaveFile: '${__stage}.xml' })); @@ -719,12 +727,12 @@ class StageEditor extends UIState { } } - Logs.trace(Printer.print(xml, true)); - - return "<!DOCTYPE codename-engine-stage>\n" + Printer.print(xml, true); + var xmlThingYea:String = "<!DOCTYPE codename-engine-stage>\n" + Printer.print(xml, Options.editorPrettyPrint); + return Options.editorPrettyPrint ? xmlThingYea : xmlThingYea.replace("\n", ""); } function _edit_undo(_) { + FlxG.sound.play(Flags.DEFAULT_EDITOR_UNDO_SOUND); var undo = undos.undo(); switch(undo) { case null: @@ -750,6 +758,7 @@ class StageEditor extends UIState { } function _edit_redo(_) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND)); var redo = undos.redo(); switch(redo) { case null: @@ -810,7 +819,7 @@ class StageEditor extends UIState { var sprite:FunkinSprite = cast sprite; sprite.extra.set(exID("selected"), true); sprite.extra.get(exID("button")).selected = true; - Logs.trace("Selected " + sprite.name); + //Logs.trace("Selected " + sprite.name); } } } @@ -1352,6 +1361,9 @@ class StageXMLEditScreen extends UISoftcodedWindow { public var xml:Access; public var saveCallback:Void->Void; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageXMLEditScreen." + id, args); + public function new(xml:Access, saveCallback:Void->Void, type:String = "Unknown") { this.xml = xml; this.saveCallback = saveCallback; @@ -1359,7 +1371,8 @@ class StageXMLEditScreen extends UISoftcodedWindow { "stage" => StageEditor.instance.stage, "xml" => xml, "exID" => StageEditor.exID, - "type" => type + "type" => type, + "translate" => translate ]); } diff --git a/source/funkin/editors/stage/StageSelection.hx b/source/funkin/editors/stage/StageSelection.hx index 942f5c30..b78aebc3 100644 --- a/source/funkin/editors/stage/StageSelection.hx +++ b/source/funkin/editors/stage/StageSelection.hx @@ -21,20 +21,20 @@ class StageSelection extends EditorTreeMenu 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.", + new TextOption(stage, TU.translate("stageSelection.acceptStage"), function() { FlxG.switchState(new StageEditor(stage)); }); } ]; - list.insert(0, new NewOption("New Stage", "Press ACCEPT to create a new stage.", function() { + list.insert(0, new NewOption(TU.translate("stageSelection.newStage"), TU.translate("stageSelection.acceptNewStage"), function() { FlxG.state.openSubState(new StageCreationScreen(saveStage)); })); - main = new OptionsScreen("Stage Editor", "Select a stage to edit. HSCRIPT only stages are not supported.", list); + main = new OptionsScreen(TU.translate("editor.stage.name"), TU.translate("stageSelection.desc"), list); - //DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]); + DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]); } public function saveStage(creation:StageCreationData) { @@ -51,7 +51,7 @@ class StageSelection extends EditorTreeMenu #end // Add to List - var option = new TextOption(creation.name, "Press ACCEPT to edit this stage.", function() { + var option = new TextOption(creation.name, TU.translate("stageSelection.acceptStage"), function() { FlxG.switchState(new StageEditor(creation.name)); }); optionsTree.members[optionsTree.members.length-1].insert(1, option); diff --git a/source/funkin/editors/stage/StageSpritesWindow.hx b/source/funkin/editors/stage/StageSpritesWindow.hx index 78988e1e..25a930ea 100644 --- a/source/funkin/editors/stage/StageSpritesWindow.hx +++ b/source/funkin/editors/stage/StageSpritesWindow.hx @@ -4,8 +4,12 @@ import funkin.editors.stage.elements.StageElementButton; import funkin.editors.stage.elements.*; class StageSpritesWindow extends UIButtonList<StageElementButton> { + + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageSprites." + id, args); + public function new(x:Float, y:Float) { - super(x, y, 400, FlxG.height-25, "Stage Sprites", FlxPoint.get(400, 64)); + super(x, y, 400, FlxG.height-25, translate("win-title"), FlxPoint.get(400, 64)); collapsable = true; topAlpha = 0.9; @@ -23,22 +27,22 @@ class StageSpritesWindow extends UIButtonList<StageElementButton> { var screenPos = addButton.getScreenPosition(null, lastDrawCam == null ? FlxG.camera : lastDrawCam); StageEditor.instance.openContextMenu([ { - label: "Sprite", + label: translate("sprite"), onSelect: StageEditor.instance._sprite_new, color: 0xFF00FF00, icon: 2 }, { - label: "Box", + label: translate("box"), onSelect: function(_) { - UIState.state.displayNotification(new UIBaseNotification("Creating a box isnt implemented yet!", 2, BOTTOM_LEFT)); + UIState.state.displayNotification(new UIBaseNotification(translate("warnings.not-implemented"), 2, BOTTOM_LEFT)); CoolUtil.playMenuSFX(WARNING, 0.45); }, color: 0xFF00FF00, icon: 2 }, { - label: "Character", + label: translate("character"), onSelect: StageEditor.instance._character_new, color: 0xFF00FF00, icon: 2 diff --git a/source/funkin/editors/stage/elements/StageCharacterButton.hx b/source/funkin/editors/stage/elements/StageCharacterButton.hx index 68156dcc..afd13840 100644 --- a/source/funkin/editors/stage/elements/StageCharacterButton.hx +++ b/source/funkin/editors/stage/elements/StageCharacterButton.hx @@ -12,6 +12,9 @@ class StageCharacterButton extends StageElementButton { public var char:Character; public var charScale(get, null):Float; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageCharacters." + id, args); + public function new(x:Float,y:Float, char:Character, xml:Access) { this.char = char; super(x,y, xml); @@ -61,9 +64,9 @@ class StageCharacterButton extends StageElementButton { 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"; + var text = translate("requiredPosition"); + if (char.name == "NO_DELETE_girlfriend" && FlxG.random.bool(1)) + text = translate("noDeleteGirlfriend"); UIState.state.displayNotification(new UIBaseNotification(text, 2, BOTTOM_LEFT)); return; } @@ -100,6 +103,9 @@ class StageCharacterEditScreen extends UISoftcodedWindow { public var button:StageCharacterButton; public var char:Character; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageElementEditScreen." + id, args); + public function new(button:StageCharacterButton) { this.button = button; this.char = button.char; @@ -117,6 +123,7 @@ class StageCharacterEditScreen extends UISoftcodedWindow { "setEx" => function(name:String, value:Dynamic) { char.extra.set(StageEditor.exID(name), value); }, + "translate" => translate, ]); } diff --git a/source/funkin/editors/stage/elements/StageElementButton.hx b/source/funkin/editors/stage/elements/StageElementButton.hx index 937893c5..4ba56ec8 100644 --- a/source/funkin/editors/stage/elements/StageElementButton.hx +++ b/source/funkin/editors/stage/elements/StageElementButton.hx @@ -209,8 +209,10 @@ class StageElementButton extends UIButton { 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)})'; + var scaleText = TU.getRaw("stageElement.scale"); + var scrollText = TU.getRaw("stageElement.scroll"); + text += '\n${scaleText.format([CoolUtil.quantize(sprite.scale.x, 100), CoolUtil.quantize(sprite.scale.y, 100)])}'; + text += '\n${scrollText.format([CoolUtil.quantize(sprite.scrollFactor.x, 100), CoolUtil.quantize(sprite.scrollFactor.y, 100)])}'; } pos.put(); return text; diff --git a/source/funkin/editors/stage/elements/StageSpriteButton.hx b/source/funkin/editors/stage/elements/StageSpriteButton.hx index 485eca9f..81b609c2 100644 --- a/source/funkin/editors/stage/elements/StageSpriteButton.hx +++ b/source/funkin/editors/stage/elements/StageSpriteButton.hx @@ -76,6 +76,9 @@ class StageSpriteEditScreen extends UISoftcodedWindow { public var sprite:FunkinSprite; var isSaving:Bool = false; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageElementEditScreen." + id, args); + public function new(button:StageSpriteButton) { this.button = button; this.sprite = button.getSprite(); @@ -91,6 +94,7 @@ class StageSpriteEditScreen extends UISoftcodedWindow { "setEx" => function(name:String, value:Dynamic) { sprite.extra.set(StageEditor.exID(name), value); }, + "translate" => translate, ]); } diff --git a/source/funkin/editors/stage/elements/StageUnknownButton.hx b/source/funkin/editors/stage/elements/StageUnknownButton.hx index a1beb0bc..a3fb0949 100644 --- a/source/funkin/editors/stage/elements/StageUnknownButton.hx +++ b/source/funkin/editors/stage/elements/StageUnknownButton.hx @@ -75,7 +75,7 @@ class StageUnknownButton extends StageElementButton { } public override function getInfoText():String { - return 'Unknown\n<${getName()} />'; + return TU.translate("stageElement.unknown", [getName()]); } public override function updatePos() { @@ -86,13 +86,17 @@ class StageUnknownButton extends StageElementButton { class StageUnknownEditScreen extends UISoftcodedWindow { public var button:StageUnknownButton; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("stageElementEditScreen." + id, args); + public function new(button:StageUnknownButton) { this.button = button; super("layouts/stage/unknownEditScreen", [ "stage" => StageEditor.instance.stage, "button" => button, "xml" => button.xml, - "exID" => StageEditor.exID + "exID" => StageEditor.exID, + "translate" => translate, ]); } diff --git a/source/funkin/editors/ui/UIAudioPlayer.hx b/source/funkin/editors/ui/UIAudioPlayer.hx index ed567b59..da1707b7 100644 --- a/source/funkin/editors/ui/UIAudioPlayer.hx +++ b/source/funkin/editors/ui/UIAudioPlayer.hx @@ -54,6 +54,7 @@ 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); @@ -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); @@ -106,20 +107,17 @@ class UIAudioPlayer extends UIButton { var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); - if (((mousePos.x > (spritePos.x)) && (mousePos.x < (spritePos.x) + timeBar.barWidth)) - && ((mousePos.y > (spritePos.y)) && (mousePos.y < (spritePos.y) + timeBar.barHeight))) { - if (FlxG.mouse.justPressed) { - draggingObj = sprite; - wasPlaying = draggingObj == timeBar ? sound.playing : false; - } + if (((mousePos.x > spritePos.x) && (mousePos.x < spritePos.x + sprite.barWidth)) + && ((mousePos.y > spritePos.y) && (mousePos.y < spritePos.y + sprite.barHeight))) { + if (FlxG.mouse.justPressed) { + if ((draggingObj = sprite) == timeBar && (wasPlaying = sound.playing)) sound.pause(); + } if (FlxG.mouse.pressed) { - if (draggingObj == timeBar) { - if (sound.playing) sound.pause(); + if (draggingObj == timeBar) sound.time = FlxMath.remapToRange(mousePos.x - spritePos.x, 0, timeBar.barWidth, 0, sound.length); - } else if (draggingObj == volumeBar) { + else if (draggingObj == volumeBar) sound.volume = FlxMath.remapToRange(mousePos.x - spritePos.x, 0, volumeBar.barWidth, 0, 1); - } } } spritePos.put(); @@ -127,8 +125,10 @@ class UIAudioPlayer extends UIButton { mousePos.put(); if (FlxG.mouse.released) { - if (draggingObj == timeBar && wasPlaying) - sound.play(wasPlaying = false, sound.time); + if (draggingObj == timeBar && wasPlaying) { + sound.resume(); + wasPlaying = false; + } draggingObj = null; } } diff --git a/source/funkin/editors/ui/UIButton.hx b/source/funkin/editors/ui/UIButton.hx index 8aa75936..2064e3d7 100644 --- a/source/funkin/editors/ui/UIButton.hx +++ b/source/funkin/editors/ui/UIButton.hx @@ -28,7 +28,10 @@ class UIButton extends UISliceSprite { public override function onHovered() { super.onHovered(); - if (FlxG.mouse.justPressed) hasBeenPressed = true; + if (FlxG.mouse.justPressed) { + hasBeenPressed = true; + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_BUTTONCLICK_SOUND)); + } if (FlxG.mouse.justReleased && callback != null && shouldPress && hasBeenPressed) { callback(); hasBeenPressed = false; 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 bcbf6dbe..dcd8d0af 100644 --- a/source/funkin/editors/ui/UIContextMenu.hx +++ b/source/funkin/editors/ui/UIContextMenu.hx @@ -88,6 +88,14 @@ class UIContextMenu extends MusicBeatSubstate { for(o in separators) o.y -= bg.bHeight; } + + if (bg.x + bg.bWidth > FlxG.width && bg.x > FlxG.width*0.5) { + bg.x -= bg.bWidth; + for(o in contextMenuOptions) + o.x -= bg.bWidth; + for(o in separators) + o.x -= bg.bWidth; + } } public function select(option:UIContextMenuOption) { diff --git a/source/funkin/editors/ui/UIDropDown.hx b/source/funkin/editors/ui/UIDropDown.hx index 0dbd1545..d0cfb323 100644 --- a/source/funkin/editors/ui/UIDropDown.hx +++ b/source/funkin/editors/ui/UIDropDown.hx @@ -87,6 +87,7 @@ class UIDropDown extends UISliceSprite { } public function openContextMenu() { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND)); var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]); curMenu = UIState.state.openContextMenu([ for(k=>o in items) { diff --git a/source/funkin/editors/ui/UIFileExplorer.hx b/source/funkin/editors/ui/UIFileExplorer.hx index 6e7c8f81..4f75fd6e 100644 --- a/source/funkin/editors/ui/UIFileExplorer.hx +++ b/source/funkin/editors/ui/UIFileExplorer.hx @@ -15,16 +15,19 @@ class UIFileExplorer extends UISliceSprite { public var onFile:(String, Bytes)->Void; public var uiElement:FlxSprite; + + public var fileType:String = "txt"; 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, null, function () { var fileDialog = new FileDialog(); fileDialog.onSelect.add(loadFile); - fileDialog.browse(OPEN, fileType); + fileDialog.browse(OPEN, this.fileType); }, bWidth - 16, bHeight - 16); members.push(uploadButton); @@ -66,6 +69,7 @@ class UIFileExplorer extends UISliceSprite { } public function removeFile() { + if (!selectable) return; if (uiElement != null) { members.remove(uiElement); uiElement.destroy(); diff --git a/source/funkin/editors/ui/UIImageExplorer.hx b/source/funkin/editors/ui/UIImageExplorer.hx index ecf98e19..12268e4d 100644 --- a/source/funkin/editors/ui/UIImageExplorer.hx +++ b/source/funkin/editors/ui/UIImageExplorer.hx @@ -35,6 +35,9 @@ class UIImageExplorer extends UIFileExplorer { public var directoryTextBoxLabel:UIText; public var directoryTextBox:UITextBox; + inline function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("uiImageExplorer." + id, args); + 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); @@ -51,7 +54,7 @@ class UIImageExplorer extends UIFileExplorer { 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)); + directoryBG.alpha = 0.9; directoryBG.members.push(directoryTextBoxLabel = new UIText(8, 6, 0, TU.getRaw('uiImageExplorer.directory').format([directory]), 12)); directoryTextBox = new UITextBox(8, 8+12+4, "", 210, 22, false, true); directoryBG.members.push(directoryTextBox); @@ -245,17 +248,18 @@ class UIImageExplorer extends UIFileExplorer { message.add('${imagePath.file}.${imagePath.ext}'); message.add(' (${CoolUtil.getSizeString(size)}'); + var typeOfFrame = isAtlas ? translate("symbol") : translate("animation"); + var shouldUsePlural = animationList.length != 1; 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#'); + message.add(', ${TU.getRaw("uiImageExplorer." + (shouldUsePlural ? "foundplural" : "found")).format([animationList.length, typeOfFrame])}'); + } else message.add(', ${TU.getRaw("uiImageExplorer.notFound").format([typeOfFrame])}'); if (isAtlas) { - if(spritemaps.length == 1) message.add(', ${spritemaps.length} Spritemap Found'); - else message.add(', ${spritemaps.length} Spritemaps Found'); + shouldUsePlural = spritemaps.length != 1; + message.add(', ${TU.getRaw("uiImageExplorer.foundAtlas" + (shouldUsePlural ? "plural" : "")).format([spritemaps.length])}'); } message.add(')'); - message.add(isAtlas ? " - Atlas" : " - Spritemap"); + message.add(isAtlas ? ' - ${translate("atlas")}' : ' - ${translate("spritemap")}'); fileText = new UIText(x+20, y+16, bWidth-20-deleteButton.bWidth-16, ""); fileText.applyMarkup(message.toString(), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "#")]); @@ -337,10 +341,9 @@ class UIImageExplorer extends UIFileExplorer { } 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", + var buttons = [ + { + label: TU.translate("uiImageExplorer.override"), color: 0xFFFF0000, onClick: (_) -> { deleteExistingFiles(); @@ -348,10 +351,12 @@ class UIImageExplorer extends UIFileExplorer { } }, { - label: "Use Existing", + label: TU.translate("uiImageExplorer.useExisting"), onClick: (_) -> {if (onFinishSaving != null) onFinishSaving();} } - ], false)); + ]; + (FlxG.state.subState != null && !FlxG.state._requestSubStateReset ? FlxG.state.subState : FlxG.state).openSubState(new UIWarningSubstate("Alreadly Existing Files!!!", + TU.getRaw("uiImageExplorer.fileAlreadyExists").format([alreadlyExistingFiles.join('\n')]), buttons, false)); } else acuttalySaveFiles(); } diff --git a/source/funkin/editors/ui/UIScaleMode.hx b/source/funkin/editors/ui/UIScaleMode.hx index 5d67bc7c..0cb44a19 100644 --- a/source/funkin/editors/ui/UIScaleMode.hx +++ b/source/funkin/editors/ui/UIScaleMode.hx @@ -4,7 +4,7 @@ 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) { + if ((Width < FlxG.initialWidth || Height < FlxG.initialHeight) && !Options.bypassEditorsResize) { @:privateAccess { FlxG.width = FlxG.initialWidth; FlxG.height = FlxG.initialHeight; diff --git a/source/funkin/editors/ui/UISoftcodedWindow.hx b/source/funkin/editors/ui/UISoftcodedWindow.hx index e791f883..4b4f9e65 100644 --- a/source/funkin/editors/ui/UISoftcodedWindow.hx +++ b/source/funkin/editors/ui/UISoftcodedWindow.hx @@ -283,7 +283,7 @@ class UISoftcodedWindow extends UISubstateWindow { addLayout(getArr(layout.elements)); if(get("hasSaveButton")) { - saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, TU.translate("editor.saveClose"), function() { saveData(); close(); }, 125); @@ -295,7 +295,7 @@ class UISoftcodedWindow extends UISubstateWindow { 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() { + closeButton = new UIButton(x, y, TU.translate("editor.close"), function() { close(); }, 125); if(saveButton != null) diff --git a/source/funkin/editors/ui/UIState.hx b/source/funkin/editors/ui/UIState.hx index fb578b61..97db4580 100644 --- a/source/funkin/editors/ui/UIState.hx +++ b/source/funkin/editors/ui/UIState.hx @@ -105,6 +105,10 @@ class UIState extends MusicBeatState { buttonHandler = null; } + if (FlxG.mouse.justPressed) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CLICK_SOUND)); + } + if (FlxG.mouse.justReleased) currentFocus = (hoveredSprite is IUIFocusable) ? (cast hoveredSprite) : null; @@ -143,6 +147,7 @@ class UIState extends MusicBeatState { } public function closeCurrentContextMenu() { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND)); if(curContextMenu != null) { curContextMenu.close(); curContextMenu = null; @@ -150,6 +155,7 @@ class UIState extends MusicBeatState { } public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float, ?w:Int) { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND)); var state = FlxG.state; while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null)) state = state.subState; diff --git a/source/funkin/editors/ui/UISubstateWindow.hx b/source/funkin/editors/ui/UISubstateWindow.hx index be975868..61c9f5ca 100644 --- a/source/funkin/editors/ui/UISubstateWindow.hx +++ b/source/funkin/editors/ui/UISubstateWindow.hx @@ -86,6 +86,7 @@ class UISubstateWindow extends MusicBeatSubstate { } public override function destroy() { + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND)); super.destroy(); for(e in camShaders) e.removeShader(blurShader); diff --git a/source/funkin/editors/ui/UITextBox.hx b/source/funkin/editors/ui/UITextBox.hx index f2939ac9..371297f2 100644 --- a/source/funkin/editors/ui/UITextBox.hx +++ b/source/funkin/editors/ui/UITextBox.hx @@ -110,6 +110,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { case RIGHT: changeSelection(1); case BACKSPACE: + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND)); if (position > 0) { label.text = label.text.substr(0, position-1) + label.text.substr(position); changeSelection(-1); @@ -119,6 +120,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { case END: position = label.text.length; case V: + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND)); // Hey lj here, fixed copying because before we checked if the modifier was left or right ctrl // but somehow it gave a int outside of the KeyModifier's range :sob: // apparently there is a boolean that just checks for you. yw :D @@ -129,13 +131,14 @@ class UITextBox extends UISliceSprite implements IUIFocusable { var data:String = Clipboard.generalClipboard.getData(TEXT_FORMAT); if (data != null) onTextInput(data); case C: + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND)); // if we are not holding ctrl, ignore if (!modifier.ctrlKey) return; // copying Clipboard.generalClipboard.setData(TEXT_FORMAT, label.text); default: - // nothing + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND)); } } diff --git a/source/funkin/editors/ui/UIWarningSubstate.hx b/source/funkin/editors/ui/UIWarningSubstate.hx index 952290fb..9fdcdb91 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>; @@ -70,14 +72,14 @@ class UIWarningSubstate extends MusicBeatSubstate { 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(232 + Math.abs(Math.min(sprIcon.height-messageSpr.height, 0))); + 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; @@ -96,7 +98,7 @@ class UIWarningSubstate extends MusicBeatSubstate { add(sprIcon); messageSpr.x = sprIcon.x + 70 + 16 + 20; - messageSpr.y = sprIcon.y + 16; + messageSpr.y = sprIcon.y; add(messageSpr); var xPos = (FlxG.width - (30 + (170 * buttons.length))) / 2; diff --git a/source/funkin/editors/ui/UIWindow.hx b/source/funkin/editors/ui/UIWindow.hx index e6ac6388..e7b994e9 100644 --- a/source/funkin/editors/ui/UIWindow.hx +++ b/source/funkin/editors/ui/UIWindow.hx @@ -15,6 +15,7 @@ class UIWindow extends UISliceSprite { content = new FlxTypedGroup<FlxBasic>(); members.push(content); + FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND)); } public override function update(elapsed:Float) { diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index 84f7fe9d..15a9729f 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -172,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(); } @@ -359,12 +360,12 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (xml.x.exists("gameOverChar")) gameOverCharacter = xml.x.get("gameOverChar"); if (xml.x.exists("camx")) cameraOffset.x = Std.parseFloat(xml.x.get("camx")); if (xml.x.exists("camy")) cameraOffset.y = Std.parseFloat(xml.x.get("camy")); - if (xml.x.exists("holdTime")) holdTime = CoolUtil.getDefault(Std.parseFloat(xml.x.get("holdTime")), 4); + if (xml.x.exists("holdTime")) holdTime = Std.parseFloat(xml.x.get("holdTime")).getDefaultFloat(4); if (xml.x.exists("flipX")) flipX = (xml.x.get("flipX") == "true"); if (xml.x.exists("icon")) icon = xml.x.get("icon"); if (xml.x.exists("color")) iconColor = FlxColor.fromString(xml.x.get("color")); if (xml.x.exists("scale")) { - var scale:Float = Std.parseFloat(xml.x.get("scale")).getDefault(1); + var scale:Float = Std.parseFloat(xml.x.get("scale")).getDefaultFloat(1); this.scale.set(scale, scale); updateHitbox(); } 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 19618bb7..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 = CoolUtil.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/PlayState.hx b/source/funkin/game/PlayState.hx index f4e6f04c..98697536 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"); @@ -701,7 +731,7 @@ 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); } @@ -782,16 +812,16 @@ 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); } - scoreTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), "Score:0", 16); - missesTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), "Misses:0", 16); - accuracyTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), "Accuracy:-% (N/A)", 16); + scoreTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), TEXT_GAME_SCORE.format([songScore]), 16); + missesTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), TEXT_GAME_MISSES.format([misses]), 16); + accuracyTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), TEXT_GAME_ACCURACY.format(["-%", "(N/A)"]), 16); accuracyTxt.addFormat(accFormat, 0, 1); for(text in [scoreTxt, missesTxt, accuracyTxt]) { @@ -801,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]; @@ -821,7 +852,7 @@ class PlayState extends MusicBeatState if (chartingMode) { WindowUtils.prefix = Charter.undos.unsaved ? Flags.UNDO_PREFIX : ""; - WindowUtils.suffix = " (Chart Playtesting)"; + WindowUtils.suffix = TU.translate("playtesting.chartPlaytesting"); SaveWarning.showWarning = Charter.undos.unsaved; SaveWarning.selectionClass = CharterSelection; @@ -838,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); @@ -847,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() @@ -867,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'); @@ -980,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(); @@ -1009,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(); @@ -1027,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; } @@ -1168,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"); } @@ -1216,19 +1235,19 @@ class PlayState extends MusicBeatState state.openSubState(new PlaytestingWarningSubstate(closingWindow, [ { - label: closingWindow ? "Exit Game" : "Exit To Menu", + label: closingWindow ? TU.translate("playtesting.exitGame") : TU.translate("playtesting.exitToMenu"), color: 0xFF0000, onClick: function(_) { if (!closingWindow) { if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, [])); } else { WindowUtils.preventClosing = false; WindowUtils.resetClosing(); - Sys.exit(0); + openfl.system.System.exit(0); } } }, { - label: closingWindow ? "Save & Exit Game" : "Save & Exit To Menu", + label: closingWindow ? TU.translate("playtesting.saveAndExitGame") : TU.translate("playtesting.saveAndExitToMenu"), color: 0xFFFF00, onClick: function(_) { if (SaveWarning.saveFunc != null) SaveWarning.saveFunc(); @@ -1236,12 +1255,12 @@ 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); } } }, { - label: "Cancel", + label: TU.translate("playtesting.cancel"), color: 0xFFFFFF, onClick: function (_) { if (closingWindow) WindowUtils.resetClosing(); @@ -1250,30 +1269,35 @@ 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 = CoolUtil.bound(health, 0, maxHealth); - - iconP1.health = healthBar.percent / 100; - iconP2.health = 1 - (healthBar.percent / 100); + iconP1.health = healthBarPercent / 100; + iconP2.health = 1 - (healthBarPercent / 100); } - function updateRatingStuff() { - scoreTxt.text = 'Score:$songScore'; - missesTxt.text = '${comboBreaks ? "Combo Breaks" : "Misses"}:$misses'; + // bypass the caching in FormatUtil by doing it manually so its faster + private var TEXT_GAME_SCORE = TU.getRaw("game.score"); + private var TEXT_GAME_MISSES = TU.getRaw("game.misses"); + private var TEXT_GAME_COMBOBREAKS = TU.getRaw("game.comboBreaks"); + private var TEXT_GAME_ACCURACY = TU.getRaw("game.accuracy"); + + dynamic function updateRatingStuff() { + scoreTxt.text = TEXT_GAME_SCORE.format([songScore]); + missesTxt.text = (comboBreaks ? TEXT_GAME_COMBOBREAKS : TEXT_GAME_MISSES).format([misses]); if (curRating == null) curRating = new ComboRating(0, "[N/A]", 0xFF888888); @:privateAccess { accFormat.format.color = curRating.color; - accuracyTxt.text = 'Accuracy:${accuracy < 0 ? "-%" : '${CoolUtil.quantize(accuracy * 100, 100)}%'} - ${curRating.rating}'; + accuracyTxt.text = TEXT_GAME_ACCURACY.format([accuracy < 0 ? "-%" : '${CoolUtil.quantize(accuracy * 100, 100)}%', curRating.rating]); for (i => frmtRange in accuracyTxt._formatRanges) if (frmtRange.format == accFormat) { accuracyTxt._formatRanges[i].range.start = accuracyTxt.text.length - curRating.rating.length; @@ -1294,80 +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 EXPERMENTAL_SCRIPT_RELOADING - if (FlxG.keys.justPressed.F5) { - Logs.warn('Reloading scripts...', "PlayState"); - scripts.reload(); - Logs.warn('Song scripts successfully reloaded.', GREEN, "PlayState"); - } - #end } - 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); } @@ -1401,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; @@ -1413,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; @@ -1431,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) { @@ -1451,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 } } @@ -1506,6 +1645,7 @@ class PlayState extends MusicBeatState */ public function endSong():Void { + endingSong = true; gameAndCharsCall("onSongEnd"); canPause = false; inst.volume = 0; @@ -1546,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; @@ -1581,7 +1719,7 @@ class PlayState extends MusicBeatState FlxG.sound.music.stop(); - __loadSong(storyPlaylist[0].toLowerCase(), difficulty); + __loadSong(storyPlaylist[0], difficulty); FlxG.switchState(new PlayState()); } @@ -1711,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); @@ -1868,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]); } @@ -1974,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; @@ -2008,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; @@ -2018,10 +2146,12 @@ 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 = rating; + this.rating = TranslationUtil.get('game.rating.$rating', rating); this.color = color; } } @@ -2034,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 121951bc..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. */ @@ -40,7 +40,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { @:privateAccess splash.drawComplex(FlxG.camera); } catch(e:Dynamic) { - Logs.trace('Couldn\'t parse splash data for "${path}": ${e.toString()}', ERROR); + Logs.error('Couldn\'t parse splash data for "${path}": ${e.toString()}'); valid = false; } maxSize = Flags.MAX_SPLASHES; @@ -51,20 +51,16 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { 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).getDefault(1)); - if (xml.has.alpha) splash.alpha = Std.parseFloat(xml.att.alpha).getDefault(1); - if (xml.has.antialiasing) splash.antialiasing = xml.att.antialiasing == "true"; - _scale = splash.scale.x; - _alpha = splash.alpha; - _antialiasing = splash.antialiasing; + _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) { @@ -90,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); } @@ -112,18 +112,21 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> { 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 ab653653..4b5cc8c8 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -130,18 +130,19 @@ class Stage extends FlxBasic implements IBeatReceiver { case "box" | "solid": if (!node.has.name || !node.has.width || !node.has.height) continue; - var spr = new FunkinSprite( - (node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0, - (node.has.y) ? Std.parseFloat(node.att.y).getDefault(0) : 0 - ); - spr.name = node.getAtt("name"); + 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 ); + 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": @@ -156,8 +157,8 @@ class Stage extends FlxBasic implements IBeatReceiver { case "ratings" | "combo": if (PlayState.instance != state) continue; PlayState.instance.comboGroup.setPosition( - Std.parseFloat(node.getAtt("x")).getDefault(PlayState.instance.comboGroup.x), - Std.parseFloat(node.getAtt("y")).getDefault(PlayState.instance.comboGroup.y) + Std.parseFloat(node.getAtt("x")).getDefaultFloat(PlayState.instance.comboGroup.x), + Std.parseFloat(node.getAtt("y")).getDefaultFloat(PlayState.instance.comboGroup.y) ); PlayState.instance.add(PlayState.instance.comboGroup); PlayState.instance.comboGroup; @@ -353,6 +354,11 @@ 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 @@ -469,4 +475,4 @@ typedef OldCharInfo = { var alpha:Float; var zoomFactor:Float; var angle:Float; -} \ No newline at end of file +} diff --git a/source/funkin/game/Strum.hx b/source/funkin/game/Strum.hx index 2ecccf53..d5031945 100644 --- a/source/funkin/game/Strum.hx +++ b/source/funkin/game/Strum.hx @@ -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. **/ @@ -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 * (height / Note.swagWidth); - } 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 * (height / Note.swagWidth); - - 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 484a6075..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,8 +350,10 @@ 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 * (data.strumSpacing != null ? data.strumSpacing : 1) * i), startingPos.y + (Note.swagWidth*0.5) - (Note.swagWidth * strumScale * 0.5)); @@ -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) { 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 22c27da3..f3c3ee13 100644 --- a/source/funkin/game/cutscenes/DialogueCutscene.hx +++ b/source/funkin/game/cutscenes/DialogueCutscene.hx @@ -68,8 +68,10 @@ class DialogueCutscene extends ScriptedCutscene { // Add characters for (char in dialogueData.nodes.char) { if (!char.has.name) continue; - if (charMap.exists(char.att.name)) - Logs.trace('2 dialogue characters share the same name (${char.att.name}, ${char.att.name}). The old character has been replaced.'); + if (charMap.exists(char.att.name)) { + Logs.warn('2 dialogue characters share the same name (${char.att.name}, ${char.att.name}). The old character has been replaced.'); + remove(charMap[char.att.name], true); + } var leChar:DialogueCharacter = new DialogueCharacter(char.att.name, char.getAtt('position').getDefault('default')); if (char.has.defaultAnim) leChar.defaultAnim = char.att.defaultAnim; @@ -92,7 +94,7 @@ class DialogueCutscene extends ScriptedCutscene { callback: node.getAtt('callback'), changeDefAnim: node.getAtt('changeDefAnim'), speed: Std.parseFloat(node.getAtt("speed")).getDefault(0.05), - musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.speed).getDefault(0.8)) : null, + 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, @@ -200,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/game/cutscenes/dialogue/DialogueBox.hx b/source/funkin/game/cutscenes/dialogue/DialogueBox.hx index a82beda5..d1d30c79 100644 --- a/source/funkin/game/cutscenes/dialogue/DialogueBox.hx +++ b/source/funkin/game/cutscenes/dialogue/DialogueBox.hx @@ -54,8 +54,8 @@ class DialogueBox extends FunkinSprite { for(pos in dialogueBoxData.nodes.charpos) { if (!pos.has.name) continue; positions[pos.att.name] = { - x: pos.has.x ? Std.parseFloat(pos.att.x).getDefault(0) : 0, - y: pos.has.y ? Std.parseFloat(pos.att.y).getDefault(0) : 0, + x: pos.has.x ? Std.parseFloat(pos.att.x).getDefaultFloat(0) : 0, + y: pos.has.y ? Std.parseFloat(pos.att.y).getDefaultFloat(0) : 0, flipBubble: pos.getAtt('flipBubble') == "true" }; } @@ -64,8 +64,8 @@ class DialogueBox extends FunkinSprite { if (textNode == null) throw "The dialog box XML requires one text element."; text = new FlxTypeText( - textNode.has.x ? Std.parseFloat(textNode.att.x).getDefault(0) : 0, - FlxG.height - (textNode.has.y ? Std.parseFloat(textNode.att.y).getDefault(0) : 0), + textNode.has.x ? Std.parseFloat(textNode.att.x).getDefaultFloat(0) : 0, + FlxG.height - (textNode.has.y ? Std.parseFloat(textNode.att.y).getDefaultFloat(0) : 0), textNode.has.width ? Std.parseInt(textNode.att.width).getDefault(FlxG.width) : FlxG.width, ""); text.color = textNode.getAtt("color").getColorFromDynamic().getDefault(0xFF000000); text.size = Std.parseInt(textNode.att.size).getDefault(20); @@ -78,10 +78,10 @@ class DialogueBox extends FunkinSprite { case "outline_fast": OUTLINE_FAST; default: OUTLINE; } - text.borderQuality = Std.parseFloat(textNode.getAtt("borderQuality")).getDefault(1); - text.shadowOffset.x = Std.parseFloat(textNode.getAtt("shadowOffsetX")).getDefault(1); - text.shadowOffset.y = Std.parseFloat(textNode.getAtt("shadowOffsetY")).getDefault(1); - text.borderSize = Std.parseFloat(textNode.getAtt("borderSize")).getDefault(1); + text.borderQuality = Std.parseFloat(textNode.getAtt("borderQuality")).getDefaultFloat(1); + text.shadowOffset.x = Std.parseFloat(textNode.getAtt("shadowOffsetX")).getDefaultFloat(1); + text.shadowOffset.y = Std.parseFloat(textNode.getAtt("shadowOffsetY")).getDefaultFloat(1); + text.borderSize = Std.parseFloat(textNode.getAtt("borderSize")).getDefaultFloat(1); text.borderColor = textNode.getAtt("borderColor").getColorFromDynamic().getDefault(0xFFFFFFFF); } } catch(e) { diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 88a2efdb..92619a7a 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -6,6 +6,7 @@ import funkin.backend.MusicBeatSubstate; import funkin.backend.MusicBeatGroup; import funkin.backend.FunkinSprite; import funkin.backend.utils.*; +import funkin.backend.utils.TranslationUtil as TU; import funkin.backend.system.Logs; import funkin.options.Options; import funkin.game.PlayState; @@ -23,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/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index 9bb7c9a9..736f1d6b 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -50,7 +50,7 @@ class FreeplayState extends MusicBeatState /** * Currently lerped score. Is updated to go towards `intendedScore`. */ - public var lerpScore:Int = 0; + public var lerpScore:Float = 0; /** * Destination for the currently lerped score. */ @@ -132,13 +132,15 @@ class FreeplayState extends MusicBeatState for (i in 0...songs.length) { - var songText:Alphabet = new Alphabet(0, (70 * i) + 30, songs[i].displayName, true, false); + var songText:Alphabet = new Alphabet(0, (70 * i) + 30, songs[i].displayName, "bold"); songText.isMenuItem = true; songText.targetY = i; grpSongs.add(songText); 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); @@ -160,7 +162,7 @@ class FreeplayState extends MusicBeatState diffText.font = scoreText.font; add(diffText); - coopText = new FlxText(diffText.x, diffText.y + diffText.height + 2, 0, "[TAB] Solo", 24); + coopText = new FlxText(diffText.x, diffText.y + diffText.height + 2, 0, "", 24); coopText.font = scoreText.font; add(coopText); @@ -204,6 +206,8 @@ class FreeplayState extends MusicBeatState public var autoplayShouldPlay:Bool = true; #end + private var TEXT_FREEPLAY_SCORE = TU.getRaw("freeplay.score"); + override function update(elapsed:Float) { super.update(elapsed); @@ -213,7 +217,7 @@ class FreeplayState extends MusicBeatState FlxG.sound.music.volume += 0.5 * elapsed; } - lerpScore = Math.floor(lerp(lerpScore, intendedScore, 0.4)); + lerpScore = lerp(lerpScore, intendedScore, 0.4); if (Math.abs(lerpScore - intendedScore) <= 10) lerpScore = intendedScore; @@ -221,12 +225,12 @@ class FreeplayState extends MusicBeatState if (canSelect) { changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel); changeDiff((controls.LEFT_P ? -1 : 0) + (controls.RIGHT_P ? 1 : 0)); - changeCoopMode((FlxG.keys.justPressed.TAB ? 1 : 0)); + changeCoopMode((FlxG.keys.justPressed.TAB ? 1 : 0)); // TODO: make this configurable // putting it before so that its actually smooth updateOptionsAlpha(); } - scoreText.text = "PERSONAL BEST:" + lerpScore; + scoreText.text = TEXT_FREEPLAY_SCORE.format([Math.round(lerpScore)]); 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; @@ -234,7 +238,7 @@ class FreeplayState extends MusicBeatState 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 @@ -320,10 +324,9 @@ class FreeplayState extends MusicBeatState } public function convertChart() { - var curSong = songs[curSelected]; - trace('Converting ${curSong.name} (${curSong.difficulties[curDifficulty]}) to Codename format...'); - var chart = Chart.parse(curSong.name, curSong.difficulties[curDifficulty]); - Chart.save('${Main.pathBack}assets/songs/${curSong.name}', chart, curSong.difficulties[curDifficulty].toLowerCase()); + 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]); } /** @@ -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() { @@ -368,10 +371,10 @@ class FreeplayState extends MusicBeatState * Array containing all labels for Co-Op / Opponent modes. */ public var coopLabels:Array<String> = [ - "[TAB] Solo", - "[TAB] Opponent Mode", - "[TAB] Co-Op Mode", - "[TAB] Co-Op Mode (Switched)" + TU.translate("freeplay.solo"), + TU.translate("freeplay.opponentMode"), + TU.translate("freeplay.coopMode"), + TU.translate("freeplay.coopModeSwitched") ]; /** @@ -392,10 +395,12 @@ class FreeplayState extends MusicBeatState updateScore(); + var key = "[TAB] "; // TODO: make this configurable + if (bothEnabled) { - coopText.text = coopLabels[curCoopMode]; + coopText.text = key + coopLabels[curCoopMode]; } else { - coopText.text = coopLabels[curCoopMode * (songs[curSelected].coopAllowed ? 2 : 1)]; + coopText.text = key + coopLabels[curCoopMode * (songs[curSelected].coopAllowed ? 2 : 1)]; } } @@ -429,19 +434,22 @@ class FreeplayState extends MusicBeatState var event = event("onUpdateOptionsAlpha", EventManager.get(FreeplayAlphaUpdateEvent).recycle(0.6, 0.45, 1, 1, 0.25)); if (event.cancelled) return; - for (i in 0...iconArray.length) - iconArray[i].alpha = lerp(iconArray[i].alpha, #if PRELOAD_ALL songInstPlaying ? event.idlePlayingAlpha : #end event.idleAlpha, event.lerp); + final idleAlpha = #if PRELOAD_ALL songInstPlaying ? event.idlePlayingAlpha : #end event.idleAlpha; + final selectedAlpha = #if PRELOAD_ALL songInstPlaying ? event.selectedPlayingAlpha : #end event.selectedAlpha; - iconArray[curSelected].alpha = #if PRELOAD_ALL songInstPlaying ? event.selectedPlayingAlpha : #end event.selectedAlpha; + for (i in 0...iconArray.length) + iconArray[i].alpha = lerp(iconArray[i].alpha, idleAlpha, event.lerp); + + iconArray[curSelected].alpha = selectedAlpha; for (i=>item in grpSongs.members) { item.targetY = i - curSelected; - item.alpha = lerp(item.alpha, #if PRELOAD_ALL songInstPlaying ? event.idlePlayingAlpha : #end event.idleAlpha, event.lerp); + item.alpha = lerp(item.alpha, idleAlpha, event.lerp); if (item.targetY == 0) - item.alpha = #if PRELOAD_ALL songInstPlaying ? event.selectedPlayingAlpha : #end event.selectedAlpha; + item.alpha = selectedAlpha; } } } @@ -451,18 +459,12 @@ class FreeplaySonglist { public function new() {} - public function getSongsFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource, useTxt:Bool = true) { + 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; @@ -485,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 06f1cbe1..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"]); @@ -73,7 +74,12 @@ class MainMenuState extends MusicBeatState FlxG.camera.follow(camFollow, null, 0.06); - versionText = new FunkinText(5, FlxG.height - 2, 0, '${Flags.VERSION_MESSAGE}\n${Flags.COMMIT_MESSAGE}\n[${controls.getKeyName(SWITCHMOD)}] Open Mods menu\n'); + versionText = new FunkinText(5, FlxG.height - 2, 0, [ + Flags.VERSION_MESSAGE, + TU.translate("mainMenu.commit", [Flags.COMMIT_NUMBER, Flags.COMMIT_HASH]), + TU.translate("mainMenu.openMods", [controls.getKeyName(SWITCHMOD)]), + '' + ].join('\n')); versionText.y -= versionText.height; versionText.scrollFactor.set(); add(versionText); @@ -92,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 38175085..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(); @@ -28,7 +34,7 @@ class ModSwitchMenu extends MusicBeatSubstate { alphabets = new FlxTypedGroup<Alphabet>(); for(mod in mods) { - var a = new Alphabet(0, 0, mod == null ? "DISABLE MODS" : mod, true); + var a = new Alphabet(0, 0, mod == null ? TU.translate("mods.disableMods") : mod, "bold"); if(mod == ModsFolder.currentModFolder) a.color = FlxColor.LIME; a.isMenuItem = true; @@ -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 8badd60a..f652b36c 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -1,6 +1,7 @@ package funkin.menus; import flixel.sound.FlxSound; +import funkin.backend.FunkinText; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import funkin.backend.FunkinText; @@ -65,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))); @@ -79,12 +80,19 @@ class PauseSubState extends MusicBeatSubstate bg.scrollFactor.set(); add(bg); + var multiplayerInfo:String = PlayState.opponentMode ? 'pause.coopMode' : + PlayState.coopMode ? 'pause.opponentMode' : + null; + levelInfo = new FunkinText(20, 15, 0, PlayState.SONG.meta.displayName, 32, false); - levelDifficulty = new FunkinText(20, 15, 0, PlayState.difficulty.toUpperCase(), 32, false); - deathCounter = new FunkinText(20, 15, 0, "Blue balled: " + PlayState.deathCounter, 32, false); - multiplayerText = new FunkinText(20, 15, 0, PlayState.opponentMode ? 'OPPONENT MODE' : (PlayState.coopMode ? 'CO-OP MODE' : ''), 32, false); + levelDifficulty = new FunkinText(20, 15, 0, TU.translateDiff(PlayState.difficulty).toUpperCase(), 32, false); + deathCounter = new FunkinText(20, 15, 0, TU.translate("pause.deathCounter", [PlayState.deathCounter]), 32, false); + multiplayerText = null; + if(multiplayerInfo != null) + multiplayerText = new FunkinText(20, 15, 0, TU.translate(multiplayerInfo), 32, false); for(k=>label in [levelInfo, levelDifficulty, deathCounter, multiplayerText]) { + if(label == null) continue; label.scrollFactor.set(); label.alpha = 0; label.x = FlxG.width - (label.width + 20); @@ -100,7 +108,8 @@ class PauseSubState extends MusicBeatSubstate for (i in 0...menuItems.length) { - var songText:Alphabet = new Alphabet(0, (70 * i) + 30, menuItems[i], true, false); + var pauseId = "pause." + TU.raw2Id(menuItems[i]); + var songText:Alphabet = new Alphabet(0, (70 * i) + 30, TU.translate(pauseId), "bold"); songText.isMenuItem = true; songText.targetY = i; grpMenuShit.add(songText); @@ -165,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 @@ -205,10 +213,7 @@ class PauseSubState extends MusicBeatSubstate { item.targetY = i - curSelected; - if (item.targetY == 0) - item.alpha = 1; - else - item.alpha = 0.6; + item.alpha = (item.targetY == 0) ? 1 : 0.6; } } } diff --git a/source/funkin/menus/PlaytestingWarningSubstate.hx b/source/funkin/menus/PlaytestingWarningSubstate.hx index 32859705..de455d83 100644 --- a/source/funkin/menus/PlaytestingWarningSubstate.hx +++ b/source/funkin/menus/PlaytestingWarningSubstate.hx @@ -40,13 +40,13 @@ class PlaytestingWarningSubstate extends MusicBeatSubstate bg.scrollFactor.set(); add(bg); - titleAlphabet = new Alphabet(0, 140, "UNSAVED CHANGES!", true); + titleAlphabet = new Alphabet(0, 140, TU.translate("playtesting.warningTitle"), "bold"); titleAlphabet.screenCenter(X); add(titleAlphabet); disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 70, FlxG.width - 32, "", 32); disclaimer.alignment = CENTER; - disclaimer.applyMarkup("Your changes will be *lost* if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?", + disclaimer.applyMarkup(TU.translate("playtesting.warningDesc"), [ new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*") ] diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx index 79ef23de..1a4c9de3 100644 --- a/source/funkin/menus/StoryMenuState.hx +++ b/source/funkin/menus/StoryMenuState.hx @@ -5,19 +5,23 @@ 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 public var scoreMessage:String = "WEEK SCORE:{0}"; public var scoreText:FlxText; @@ -33,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; @@ -56,7 +59,7 @@ class StoryMenuState extends MusicBeatState { blackBar.color = 0xFF000000; blackBar.updateHitbox(); - scoreText = new FunkinText(10, 10, 0, "SCORE: -", 36); + scoreText = new FunkinText(10, 10, 0, TU.translate("story.score", ["-"]), 36); scoreText.setFormat(Paths.font("vcr.ttf"), 32); weekTitle = new FlxText(10, 10, FlxG.width - 20, "", 32); @@ -64,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>(); @@ -87,24 +90,23 @@ class StoryMenuState extends MusicBeatState { } rightArrow.x -= rightArrow.width; - tracklist = new FunkinText(16, weekBG.y + weekBG.height + 44, Std.int(((FlxG.width - 400) / 2) - 80), "TRACKS", 32); + tracklist = new FunkinText(16, weekBG.y + weekBG.height + 44, Std.int(((FlxG.width - 400) / 2) - 80), TU.translate("story.tracks"), 32); tracklist.alignment = CENTER; 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); @@ -138,7 +140,7 @@ class StoryMenuState extends MusicBeatState { super.update(elapsed); lerpScore = lerp(lerpScore, intendedScore, 0.5); - scoreText.text = scoreMessage.replace("{0}", Std.string(Math.round(lerpScore))); + scoreText.text = TU.translate("story.score", [Math.round(lerpScore)]); if (canSelect) { if (leftArrow != null && leftArrow.exists) leftArrow.animation.play(controls.LEFT ? 'press' : 'idle'); @@ -163,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) @@ -181,13 +189,16 @@ class StoryMenuState extends MusicBeatState { e.targetY = k - curWeek; e.alpha = k == curWeek ? 1.0 : 0.6; } - tracklist.text = 'TRACKS\n\n${[for(e in weeks[curWeek].songs) if (!e.hide) e.name].join('\n')}'; + 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); @@ -195,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; @@ -244,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.trace('Cannot parse week "$weekName.xml": ${Std.string(e)}`', ERROR); - } - - if (week == null) continue; - - if (!week.has.name) { - Logs.trace('Story Menu: Week at index ${k} has no name. Skipping...', WARNING); - 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.trace('Story Menu: Song at index ${k2} in week ${weekObj.name} has no name. Skipping...', WARNING); - continue; - } - weekObj.songs.push({ - name: name, - hide: song.getAtt('hide').getDefault('false') == "true" - }); - } catch(e) { - Logs.trace('Story Menu: Song at index ${k2} in week ${weekObj.name} cannot contain any other XML nodes in its name.', WARNING); - continue; - } - } - if (weekObj.songs.length <= 0) { - Logs.trace('Story Menu: Week ${weekObj.name} has no songs. Skipping...', WARNING); - 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.trace('Story Menu: Cannot parse character "$charName.xml": ${Std.string(e)}`', ERROR); - } + 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.AssetsLibraryList.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() { @@ -369,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); @@ -382,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; @@ -438,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/TitleState.hx b/source/funkin/menus/TitleState.hx index a4a42385..ab190b62 100644 --- a/source/funkin/menus/TitleState.hx +++ b/source/funkin/menus/TitleState.hx @@ -175,7 +175,7 @@ class TitleState extends MusicBeatState for (i=>text in textArray) { if (text == "" || text == null) continue; - var money:Alphabet = new Alphabet(0, (i * 60) + 200, text, true, false); + var money:Alphabet = new Alphabet(0, (i * 60) + 200, text, "bold"); money.screenCenter(X); textGroup.add(money); } @@ -183,7 +183,7 @@ class TitleState extends MusicBeatState public function addMoreText(text:String) { - var coolText:Alphabet = new Alphabet(0, (textGroup.length * 60) + 200, text, true, false); + var coolText:Alphabet = new Alphabet(0, (textGroup.length * 60) + 200, text, "bold"); coolText.screenCenter(X); textGroup.add(coolText); } @@ -284,7 +284,7 @@ class TitleState extends MusicBeatState } } } catch(e) { - Logs.trace('Failed to load titlescreen XML: $e', ERROR); + Logs.error('Failed to load titlescreen XML: $e'); } } diff --git a/source/funkin/menus/BetaWarningState.hx b/source/funkin/menus/WarningState.hx similarity index 73% rename from source/funkin/menus/BetaWarningState.hx rename to source/funkin/menus/WarningState.hx index 30e3c4fd..d51d6667 100644 --- a/source/funkin/menus/BetaWarningState.hx +++ b/source/funkin/menus/WarningState.hx @@ -1,17 +1,16 @@ package funkin.menus; -import flixel.text.FlxText; import flixel.util.FlxColor; +import flixel.text.FlxText; import funkin.backend.FunkinText; -class BetaWarningState extends MusicBeatState { +class WarningState extends MusicBeatState { var titleAlphabet:Alphabet; var disclaimer:FunkinText; var transitioning:Bool = false; public override function create() { - super.create(); titleAlphabet = new Alphabet(0, 0, "WARNING", true); titleAlphabet.screenCenter(X); @@ -19,9 +18,14 @@ class BetaWarningState extends MusicBeatState { disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 10, FlxG.width - 32, "", 32); disclaimer.alignment = CENTER; - disclaimer.applyMarkup('This engine is still in a *${Flags.RELEASE_CYCLE}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue', + disclaimer.text = "This is a warning state! Make a script #./data/states/WarningState.hx# to edit this text! \n\n Note: you can use *asterisks* or #hashtags# to highlight your text in *red* or #yellow#, or you can just make your own markup using postCreate (you have to change the text in create() to apply the default markup)"; + + super.create(); + + disclaimer.applyMarkup(disclaimer.text, [ - new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*") + new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*"), + new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFFFF44), "#") ] ); add(disclaimer); diff --git a/source/funkin/menus/credits/CreditsCodename.hx b/source/funkin/menus/credits/CreditsCodename.hx index dcc93614..70e172dc 100644 --- a/source/funkin/menus/credits/CreditsCodename.hx +++ b/source/funkin/menus/credits/CreditsCodename.hx @@ -16,7 +16,7 @@ class CreditsCodename extends funkin.options.OptionsScreen { public override function new() { - super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes)."); + super("Codename Engine", TU.translate("credits.allContributors")); tryUpdating(true); } @@ -39,7 +39,7 @@ class CreditsCodename extends funkin.options.OptionsScreen { } public function tryUpdating(forceDisplaying:Bool = false) { - updateMenuDesc("Downloading List..."); + updateMenuDesc(TU.translate("credits.downloadingList")); _canReset = false; Main.execAsync(function() { if(checkUpdate() || forceDisplaying) _downloadingSteps = 2; @@ -77,7 +77,7 @@ class CreditsCodename extends funkin.options.OptionsScreen { var idk = GitHub.getContributors(Flags.REPO_OWNER, Flags.REPO_NAME, function(e) { error = true; var errMsg:String = 'Error while trying to download contributors list:\n${CoolUtil.removeIP(e.message)}'; - Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR); + Logs.error(errMsg.replace('\n', ' ')); funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING); }); if(error) return false; @@ -98,7 +98,7 @@ class CreditsCodename extends funkin.options.OptionsScreen { var idk2 = GitHub.getOrganizationMembers(Flags.REPO_OWNER, function(e) { errorOnMain = true; var errMsg:String = 'Error while trying to download ${Flags.REPO_OWNER} members list:\n${CoolUtil.removeIP(e.message)}'; - Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR); + Logs.error(errMsg.replace('\n', ' ')); funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING); }); if(!errorOnMain) { @@ -122,9 +122,10 @@ class CreditsCodename extends funkin.options.OptionsScreen { totalContributions = 0; for(c in Options.contributors) totalContributions += c.contributions; for(c in Options.contributors) { - var opt:GithubIconOption = new GithubIconOption(c, 'Total Contributions: ~${c.contributions}~ / *${totalContributions}* (~${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%~) - Select to open GitHub account'); + var text = TU.translate("credits.totalContributions", [c.contributions, totalContributions, FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)]); + var opt:GithubIconOption = new GithubIconOption(c, text); if(Options.mainDevs.contains(c.id)) { - opt.desc += " *- Public member of the main Devs!*"; + opt.desc += TU.translate("credits.mainDev"); @:privateAccess opt.__text.color = Flags.MAIN_DEVS_COLOR; } add(opt); diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx index e447ef01..76485cbf 100644 --- a/source/funkin/menus/credits/CreditsMain.hx +++ b/source/funkin/menus/credits/CreditsMain.hx @@ -1,11 +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; @@ -21,29 +22,25 @@ class CreditsMain extends TreeMenu { bg.antialiasing = true; add(bg); - var xmlPath = Paths.xml('config/credits'); - for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.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.trace('[CreditsMain] Error while parsing credits.xml: ${Std.string(e)}', ERROR); - } + 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); } } - items.push(new TextOption("Codename Engine >", "Select this to see all the contributors of the engine!", function() { + + items.push(new TextOption("Codename Engine >", TU.translate("credits.selectCodename"), function() { optionsTree.add(Type.createInstance(CreditsCodename, [])); })); - items.push(new TextOption("Friday Night Funkin'", "Select this to open the itch.io page of the original game to donate!", function() { + items.push(new TextOption("Friday Night Funkin'", TU.translate("credits.selectBase"), function() { CoolUtil.openURL(Flags.URL_FNF_ITCH); })); - main = new OptionsScreen('Credits', 'The people who made this possible!', items); + main = new OptionsScreen(TU.translate("credits.name"), TU.translate("credits.madePossible"), items); super.create(); DiscordUtil.call("onMenuLoaded", ["Credits Menu"]); @@ -52,7 +49,7 @@ class CreditsMain extends TreeMenu { /** * XML STUFF */ - public function parseCreditsFromXML(xml:Access, source:funkin.backend.assets.AssetsLibraryList.AssetSource):Array<OptionType> { + public function parseCreditsFromXML(xml:Access, source:AssetSource = BOTH):Array<OptionType> { var credsMenus:Array<OptionType> = []; for(node in xml.elements) { @@ -60,7 +57,7 @@ class CreditsMain extends TreeMenu { if (node.name == "github") { if (!node.has.user) { - Logs.trace("A github node requires a user attribute.", WARNING); + Logs.warn("A github node requires a user attribute.", "CreditsMain"); continue; } @@ -80,7 +77,7 @@ class CreditsMain extends TreeMenu { credsMenus.push(opt); } else { if (!node.has.name) { - Logs.trace("A credit node requires a name attribute.", WARNING); + Logs.warn("A credit node requires a name attribute.", "CreditsMain"); continue; } var name = node.getAtt("name"); diff --git a/source/funkin/menus/ui/Alphabet.hx b/source/funkin/menus/ui/Alphabet.hx index 3ae5a127..c3508c31 100644 --- a/source/funkin/menus/ui/Alphabet.hx +++ b/source/funkin/menus/ui/Alphabet.hx @@ -1,284 +1,861 @@ package funkin.menus.ui; -import flixel.group.FlxSpriteGroup; -import flixel.util.FlxColor; -import flixel.util.FlxTimer; -import funkin.backend.assets.IModsAssetLibrary; -import funkin.backend.assets.LimeLibrarySymbol; +import flixel.util.typeLimit.OneOfTwo; +import haxe.ds.Vector; +import funkin.menus.ui.effects.RegionEffect; + +import flixel.animation.FlxAnimation; +import flixel.util.FlxDirectionFlags; +import flixel.math.FlxPoint; +import flixel.math.FlxAngle; +import flixel.FlxTypes; + import haxe.xml.Access; -import openfl.utils.AssetLibrary; using StringTools; +using flixel.util.FlxColorTransformUtil; -/** - * Loosely based on FlxTypeText lolol - */ -class Alphabet extends FlxSpriteGroup -{ - public var delay:Float = 0.05; - public var paused:Bool = false; +@:structInit +final class AlphabetOutline { + public var anim:String; + public var x:Float; + public var y:Float; +} +@:structInit +final class AlphabetComponent { + @:optional public var refIndex:Null<Int>; + @:optional public var outIndex:Null<Int>; + public var anim:String; + public var x:Float; + public var y:Float; + + // Precalculated values for angle + public var shouldRotate:Bool; + public var angle:Float; + public var sin:Float; + public var cos:Float; + public var scaleX:Float; + public var scaleY:Float; + + public var hasColorMode:Bool; + public var colorMode:ColorMode; +} + +@:structInit +final class AlphabetLetterData { + @:optional public var isDefault:Bool = false; + public var advance:Float; + public var advanceEmpty:Bool; + public var components:Array<AlphabetComponent>; + public var startIndex:Int = 0; +} + +enum abstract AlphabetAlignment(ByteUInt) from ByteUInt to ByteUInt { + var LEFT; + var CENTER; + var RIGHT; + + public function getMultiplier():Float { + return switch(this) { + case CENTER: 0.5; + case RIGHT: 1.0; + default: 0.0; + } + } + + @:from + public static function fromString(value:String):AlphabetAlignment + return switch(value) { + case "left": LEFT; + case "center": CENTER; + case "right": RIGHT; + default: LEFT; + } +} + +enum abstract CaseMode(ByteUInt) from ByteUInt to ByteUInt { + var NONE; + var UPPER; + var LOWER; + + @:from + public static function fromString(value:String):CaseMode + return switch(value) { + case "none": NONE; + case "upper": UPPER; + case "lower": LOWER; + default: NONE; + } +} + +enum abstract ColorMode(ByteInt) from ByteInt to ByteInt { + var TINT = 0; + var OFFSET = 1; + var NONE = 2; + + public function setColorTransform(color:openfl.geom.ColorTransform, red:Float, green:Float, blue:Float, alpha:Float) { + var moduloWorkaround:Int = this; + var tintMult = Math.max(-moduloWorkaround + 1, 0); + var tintOffset = Math.min(moduloWorkaround, 1); + var offsetMult = (moduloWorkaround % 2) * 255; + + color.redMultiplier = red * tintMult + tintOffset; + color.greenMultiplier = green * tintMult + tintOffset; + color.blueMultiplier = blue * tintMult + tintOffset; + color.redOffset = red * offsetMult; + color.greenOffset = green * offsetMult; + color.blueOffset = blue * offsetMult; + color.alphaMultiplier = alpha; + } +} + +enum abstract AlphabetRenderMode(ByteUInt) from ByteUInt to ByteUInt { + var DEFAULT = 0; + var MONOSPACE = 1; +} + +@:allow(funkin.editors.alphabet.AlphabetEditor) +@:allow(funkin.editors.alphabet.AlphabetMainDataScreen) +class Alphabet extends FlxSprite { + public var effects:Array<RegionEffect> = []; + var __renderData:AlphabetRenderData; + var __component:AlphabetComponent; + var __useDrawScale:Bool = false; + var __drawScale:FlxPoint = FlxPoint.get(1, 1); + + public var text(default, set):String = ""; + @:isVar public var textWidth(get, set):Float; + public var textHeight(get, null):Float; + + public var alignment:AlphabetAlignment = LEFT; + public var forceCase:CaseMode = NONE; + + var __laneWidths:Array<Float> = []; + var __forceWidth:Float = 0.0; // TODO: implement functionality for this. + var __queueResize:Bool = false; + var __animTime:Float = 0.0; + var __ogForceScreen:Bool = false; + + public var originOffset:FlxPoint = FlxPoint.get(); + + public var font(default, set):String; + var colorMode:ColorMode = TINT; + var defaultAdvance:Float = 40.0; + var lineGap:Float = 75.0; + var fps:Float = 24.0; + var letterData:Map<String, AlphabetLetterData> = []; + var defaults:Vector<AlphabetLetterData> = { + var v = new Vector(3); + v[0] = null; + v[1] = null; + v[2] = null; + v; + }; + var loaded:Vector<Array<String>> = { + var v = new Vector(3); + v[0] = []; + v[1] = []; + v[2] = []; + v; + }; + var manualLetters:Array<String> = []; + var failedLetters:Array<String> = []; + var failedOutlines:Array<String> = []; + + public var renderMode:AlphabetRenderMode = DEFAULT; // for menu shit public var targetY:Float = 0; public var isMenuItem:Bool = false; - public var text:String = ""; - - var _finalText:String = ""; - var _curText:String = ""; - - var yMulti:Float = 1; - - // custom shit - // amp, backslash, question mark, apostrophy, comma, angry faic, period - var lastSprite:AlphaCharacter; - var xPosResetted:Bool = false; - - var splitWords:Array<String> = []; - - var isBold:Bool = false; - - private override function set_color(c:Int):Int { - for(e in group.members) { - if (e is AlphaCharacter) { - var char:AlphaCharacter = cast e; - char.setColor(c, isBold); - } - } - return super.set_color(c); - } - - // TODO: fix this shit refreshing - public function refreshAlphabetXML(path:String) { - AlphaCharacter.__alphaPath = Paths.getAssetsRoot() + path; - try { - var xml = new Access(Xml.parse(Assets.getText(path)).firstElement()); - AlphaCharacter.boldAnims = []; - AlphaCharacter.letterAnims = []; - AlphaCharacter.boldAlphabetPath = AlphaCharacter.letterAlphabetPath = 'ui/alphabet'; - - for(e in xml.elements) { - var bold = e.name == "bold"; - var list = bold ? AlphaCharacter.boldAnims : AlphaCharacter.letterAnims; - if (e.has.spritesheet) { - if (bold) - AlphaCharacter.boldAlphabetPath = e.att.spritesheet; - else - AlphaCharacter.letterAlphabetPath = e.att.spritesheet; - } - for(e in e.nodes.letter) { - if (!e.has.char || !e.has.anim) continue; - var name = e.att.char; - var anim = e.att.anim; - list[name] = anim; - } - } - } catch(e) { - trace(e.details()); - } - } - public function new(x:Float, y:Float, text:String = "", ?bold:Bool = false, typed:Bool = false) - { + public function new(?x:Float, ?y:Float, ?text:String = "", ?font:OneOfTwo<String, Bool> = "normal") { super(x, y); + this.text = text; + if (font != "<SKIP>") + this.font = (font is String) ? font : ((font == true) ? "bold" : "normal"); // == true just in case + this.__renderData = new AlphabetRenderData(this); + } - _finalText = this.text = text; - isBold = bold; - - var alphabetPath = Paths.xml("alphabet"); - if (Paths.getAssetsRoot() + alphabetPath != AlphaCharacter.__alphaPath) { - refreshAlphabetXML(alphabetPath); - } - #if MOD_SUPPORT else { - var libThing = new LimeLibrarySymbol(alphabetPath); - if (libThing.library is AssetLibrary) { - 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 - if (!modLib.__isCacheValid(library.cachedBytes, libThing.symbolName)) { - refreshAlphabetXML(alphabetPath); - } - } - } - } + override function update(elapsed:Float):Void { + //super.update(elapsed); + // FLXOBJECT UPDATE + #if FLX_DEBUG + @:privateAccess FlxBasic.activeCount += 1; #end - if (text != "") - { - if (typed) - { - startTypedText(); - } - else - { - addText(); - } - } - } + last.set(x, y); - public function addText() - { - doSplitWords(); + if (path != null && path.active) + path.update(elapsed); - var xPos:Float = 0; - var curRow:Int = 0; - for (i=>character in splitWords) - { - if (lastSprite != null) - xPos = lastSprite.x + lastSprite.width - x; + if (moves) + updateMotion(elapsed); - if (xPosResetted && !(xPosResetted = false)) xPos = 0; + wasTouching = touching; + touching = FlxDirectionFlags.NONE; + // FLXOBJECT UPDATE END - var letter:AlphaCharacter = new AlphaCharacter(xPos, 0); - letter.row = curRow; - if (isBold) - letter.createBold(character); - else - letter.createLetter(character); + __animTime += elapsed; + for (effect in effects) + effect.effectTime += elapsed * effect.speed; - // anim not found - if (!letter.visible) - xPos += 40; + if (isMenuItem) { + var scaledY = targetY * 1.3; - letter.setColor(color, isBold); - add(letter); - - lastSprite = letter; - if (_finalText.fastCodeAt(i) == "\n".code) - { - xPosResetted = true; - curRow += 1; - } - } - } - - function doSplitWords():Void - { - splitWords = _finalText.split(""); - } - - //public var personTalking:String = Flags.DEFAULT_GIRLFRIEND; - - public function startTypedText():Void - { - _finalText = text; - doSplitWords(); - - // trace(arrayShit); - - var loopNum:Int = 0; - - var xPos:Float = 0; - var curRow:Int = 0; - - new FlxTimer().start(0.05, function(tmr:FlxTimer) - { - // trace(_finalText.fastCodeAt(loopNum) + " " + _finalText.charAt(loopNum)); - if (_finalText.fastCodeAt(loopNum) == "\n".code) - { - yMulti += 1; - xPosResetted = true; - xPos = 0; - curRow += 1; - } - loopNum += 1; - - tmr.time = FlxG.random.float(0.04, 0.09); - }, splitWords.length); - } - - override function update(elapsed:Float) - { - super.update(elapsed); - - if (isMenuItem) - { - var scaledY = FlxMath.remapToRange(targetY, 0, 1, 0, 1.3); - - y = CoolUtil.fpsLerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.16); + y = CoolUtil.fpsLerp(y, (scaledY * 120) + (FlxG.height - height) * 0.5, 0.16); x = CoolUtil.fpsLerp(x, (targetY * 20) + 90, 0.16); } + } - if (text != _finalText) { - _finalText = text; - for(e in members) - e.destroy(); - members.clear(); - lastSprite = null; - addText(); + override function draw():Void { + __ogForceScreen = forceIsOnScreen; + forceIsOnScreen = true; + super.draw(); + } + + override function isSimpleRender(?camera:FlxCamera):Bool { + return false; // maybe ill get simple render working another time??? not right now tho. + } + + override function drawComplex(camera:FlxCamera):Void { + forceIsOnScreen = __ogForceScreen; + + if (__queueResize) + recalcSizes(); + var curLine = 0; + var lastLine = 0; + var daText = switch (forceCase) { + case UPPER: text.toUpperCase(); + case LOWER: text.toLowerCase(); + case NONE: text; + } + + var alignmentMultiplier = alignment.getMultiplier(); + + var ogOffX = frameOffset.x; + var ogOffY = frameOffset.y; + frameOffset.x -= (textWidth - __laneWidths[0]) * alignmentMultiplier; + + var isGraphicsShader = shader != null && shader is flixel.graphics.tile.FlxGraphicsShader; + var ogRed:Float = colorTransform.redMultiplier; + var ogGreen:Float = colorTransform.greenMultiplier; + var ogBlue:Float = colorTransform.blueMultiplier; + var ogAlpha:Float = colorTransform.alphaMultiplier; + + var frameTime = Math.floor(__animTime * fps); + + var isMonospace = renderMode == MONOSPACE; + + for (i in 0...daText.length) { + var cantrace = i == daText.length - 1; + __renderData.reset(this, ogRed, ogGreen, ogBlue, ogAlpha, daText.charAt(i)); + for (effect in effects) { + if (effect.willModify(i, i - lastLine, __renderData)) + effect.modify(i, i - lastLine, __renderData); + } + + var letter = __renderData.letter; + if (letter == "\n") { + curLine++; + lastLine = i + 1; + frameOffset.x = ogOffX - (textWidth - __laneWidths[curLine]) * alignmentMultiplier; + frameOffset.y -= lineGap; + continue; + } + + var data = getData(letter); + + if (data == null) { + frameOffset.x -= defaultAdvance; + continue; + } + + var advance:Float = Math.NaN; + + for (i in 0...data.components.length) { + __component = data.components[i]; + var anim = getLetterAnim(letter, data, __component, i); + //if (cantrace) + //trace(anim.name + " | " + __component.anim + " | " + frames.frames[anim.frames[0]]); + advance = (Math.isNaN(advance)) ? getAdvance(letter, anim, data) : advance; + + if (anim == null || __renderData.alpha <= 0.0) + continue; + + var frameToGet = frameTime % anim.numFrames; + frame = frames.frames[anim.frames[frameToGet]]; + + var offsetX = __component.x + __renderData.offsetX; + var offsetY = frame.sourceSize.y - lineGap + __component.y + __renderData.offsetY; + __useDrawScale = false; + if (__component.refIndex != null) { + var actualIdx = __component.refIndex + data.startIndex; + var refCompon = data.components[actualIdx]; + var refAnim = getLetterAnim(letter, data, refCompon, actualIdx); + var refFrameIdx = frameTime % refAnim.numFrames; + var refFrame = frames.frames[refAnim.frames[refFrameIdx]]; + + var diff = Math.min( + (frame.sourceSize.x - refFrame.sourceSize.x), + (frame.sourceSize.y - refFrame.sourceSize.y) + ); + var diffScale = (diff == (frame.sourceSize.x - refFrame.sourceSize.x)) ? __component.scaleX : __component.scaleY; + + __useDrawScale = true; + __drawScale.set( + (refFrame.sourceSize.x * __component.scaleX + diff * diffScale) / frame.sourceSize.x, + (refFrame.sourceSize.y * __component.scaleY + diff * diffScale) / frame.sourceSize.y + ); + offsetX = __component.x * __drawScale.x + __renderData.offsetX + diff * 0.5 + refCompon.x; + offsetY = refFrame.sourceSize.y - lineGap + __component.y * __drawScale.y + __renderData.offsetY + diff * 0.5 + refCompon.y; + } + + if (isMonospace) + offsetX -= (defaultAdvance - advance) * 0.5; + frameOffset.x += offsetX; + frameOffset.y += offsetY; + if (!isOnScreen(camera)) { + frameOffset.y -= offsetY; + frameOffset.x -= offsetX; + break; + } + + if (__component.hasColorMode) + __component.colorMode.setColorTransform(colorTransform, __renderData.red, __renderData.green, __renderData.blue, __renderData.alpha); + else + colorMode.setColorTransform(colorTransform, __renderData.red, __renderData.green, __renderData.blue, __renderData.alpha); + + if (isGraphicsShader) + shader.setCamSize(_frame.frame.x, _frame.frame.y, _frame.frame.width, _frame.frame.height); + drawLetter(camera); + + frameOffset.y -= offsetY; + frameOffset.x -= offsetX; + } + frameOffset.x -= isMonospace ? defaultAdvance : advance; + } + __component = null; + + setColorTransform(ogRed, ogGreen, ogBlue, ogAlpha, 0, 0, 0, 0); + frameOffset.set(ogOffX, ogOffY); + } + + function drawLetter(camera) { + _frame.prepareMatrix(_matrix, ANGLE_0, checkFlipX() != camera.flipX, checkFlipY() != camera.flipY); + + _matrix.translate(_frame.frame.width * -0.5, _frame.frame.height * -0.5); + if (__useDrawScale) + _matrix.scale(__drawScale.x, __drawScale.y); + else + _matrix.scale(__component.scaleX, __component.scaleY); + if(__component.shouldRotate) + _matrix.rotateWithTrig(__component.cos, __component.sin); + _matrix.translate(_frame.frame.width * 0.5, _frame.frame.height * 0.5); + + _matrix.translate(-origin.x, -origin.y); + + if (frameOffsetAngle != null && frameOffsetAngle != angle) + { + var angleOff = (frameOffsetAngle - angle) * FlxAngle.TO_RAD; + var cos = Math.cos(angleOff); + var sin = Math.sin(angleOff); + // cos doesnt need to be negated + _matrix.rotateWithTrig(cos, -sin); + _matrix.translate(-frameOffset.x, -frameOffset.y); + _matrix.rotateWithTrig(cos, sin); + } + else + _matrix.translate(-frameOffset.x, -frameOffset.y); + + _matrix.scale(scale.x, scale.y); + + if (bakedRotationAngle <= 0) + { + updateTrig(); + + if (angle != 0) + _matrix.rotateWithTrig(_cosAngle, _sinAngle); + } + + getScreenPosition(_point, camera).subtractPoint(offset); + _point.add(origin.x, origin.y); + _matrix.translate(_point.x, _point.y); + + if (isPixelPerfectRender(camera)) + { + _matrix.tx = Math.floor(_matrix.tx); + _matrix.ty = Math.floor(_matrix.ty); + } + + doAdditionalMatrixStuff(_matrix, camera); + + if (layer != null) + layer.drawPixels(this, camera, _frame, framePixels, _matrix, colorTransform, blend, antialiasing, shaderEnabled ? shader : null); + else + camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shaderEnabled ? shader : null); + } + + override function updateHitbox():Void { + offset.set(-0.5 * (width - textWidth), -0.5 * (height - textHeight)); + origin.set(textWidth * 0.5 + originOffset.x, textHeight * 0.5 + originOffset.y); + } + + function recalcSizes():Void { + __queueResize = false; + var curLine = 0; + var daText = switch (forceCase) { + case UPPER: text.toUpperCase(); + case LOWER: text.toLowerCase(); + case NONE: text; + } + @:bypassAccessor textWidth = 0; + textHeight = lineGap; + __laneWidths = [0]; + for (i in 0...daText.length) { + var letter = daText.charAt(i); + if (letter == "\n") { + textHeight += lineGap; + __laneWidths.push(0); + curLine++; + continue; + } + + var data = getData(letter); + __laneWidths[curLine] += (data != null && data.components.length > 0) ? getAdvance(letter, getLetterAnim(letter, data, data.components[data.startIndex], data.startIndex), data) : defaultAdvance; + @:bypassAccessor textWidth = Math.max(textWidth, __laneWidths[curLine]); + } + + origin.set(textWidth * 0.5 + originOffset.x, textHeight * 0.5 + originOffset.y); + } + + function getAdvance(letter:String, anim:FlxAnimation, data:AlphabetLetterData):Float { + if (anim == null) + return defaultAdvance; + + if (data.advanceEmpty && !data.isDefault) { + data.advanceEmpty = false; + data.advance = frames.frames[anim.frames[0]].sourceSize.x; + } + return (data.isDefault) ? frames.frames[anim.frames[0]].sourceSize.x : data.advance; + } + + private function fastGetData(char:String):AlphabetLetterData { + var data = letterData.get(char); + + if (data == null) { + var charCode:Int = char.charCodeAt(0); + if (charCode >= 'A'.code && charCode <= 'Z'.code && defaults[CaseMode.UPPER] != null) + data = defaults[CaseMode.UPPER]; + else if (charCode >= 'a'.code && charCode <= 'z'.code && defaults[CaseMode.LOWER] != null) + data = defaults[CaseMode.LOWER]; + else + data = defaults[CaseMode.NONE]; + } + + return data; + } + + function getData(char:String):AlphabetLetterData { + if (failedLetters.contains(char)) return null; + for (i in 0...3) { // this feels wrong + if (loaded[i].contains(char)) + return defaults[i]; + } + + var data:AlphabetLetterData = fastGetData(char); + + if (data == null) { + failedLetters.push(char); + Logs.error('Character $char: Data not found.'); + return null; + } else if (data.isDefault) + loaded[defaultsIndexOf(data)].push(char); + return data; + } + + final function defaultsIndexOf(data:AlphabetLetterData):Int { + //while(i < defaults.length) { + for(i in 0...3) { + if(defaults[i] == data) + return i; + } + return -1; + } + + function fastGetLetterAnim(char:String, data:AlphabetLetterData, component:AlphabetComponent, index:Int):FlxAnimation { + var name = char + Std.string(index); + + var anim = (data.isDefault) ? + component.anim.replace("LOWERLETTER", char.toLowerCase()).replace("UPPERLETTER", char.toUpperCase()).replace("LETTER", char) : + component.anim; + animation.addByPrefix(name, anim, fps); + return animation.exists(name) ? animation.getByName(name) : null; + } + + function getLetterAnim(char:String, data:AlphabetLetterData, component:AlphabetComponent, index:Int):FlxAnimation { + if (data == null) return null; + var name = char + Std.string(index); + if (animation.exists(name)) return animation.getByName(name); + + var anim = (data.isDefault) ? + component.anim.replace("LOWERLETTER", char.toLowerCase()).replace("UPPERLETTER", char.toUpperCase()).replace("LETTER", char) : + component.anim; + animation.addByPrefix(name, anim, fps); + if (!animation.exists(name)) { + failedLetters.push(char); + var traceIndex = (component.refIndex != null) ? 'Outline For Component Index ${component.refIndex}' : 'Component Index ${index - data.startIndex}'; + Logs.error('Character $char: $traceIndex: Animation "$anim" not found.'); + return null; + } + return animation.getByName(name); + } + + /*function getLetterOutline(char:String, component:AlphabetComponent, index:Int) { + var name = char + "Outline" + Std.string(index); + if (failedOutlines.contains(name)) return null; + if (animation.exists(name)) return animation.getByName(name); + + animation.addByPrefix(name, component.outline.anim, fps); + if (!animation.exists(name)) { + failedOutlines.push(name); + return null; + } + return animation.getByName(name); + }*/ + + function checkNode(node:Xml):Void { + switch (node.nodeName) { + case "spritesheet": + if (frames == null) + frames = Paths.getFrames(node.firstChild().nodeValue); + else { + for (frame in Paths.getFrames(node.firstChild().nodeValue).frames) + frames.pushFrame(frame); + } + case "defaultAnim": + var idx = ["UPPER", "LOWER"].indexOf(node.get("casing").toUpperCase()) + 1; + + var angle:Float = Std.parseFloat(node.get("angle")).getDefaultFloat(0.0); + var rad = angle * FlxAngle.TO_RAD; + var angleCos = Math.cos(rad); + var angleSin = Math.sin(rad); + + var res:AlphabetLetterData = { + isDefault: true, + advance: 0.0, + advanceEmpty: true, + components: [{ + anim: node.firstChild().nodeValue, + + x: 0.0, + y: 0.0, + scaleX: Std.parseFloat(node.get("scaleX")).getDefaultFloat(1.0), + scaleY: Std.parseFloat(node.get("scaleY")).getDefaultFloat(1.0), + + shouldRotate: angle != 0, + angle: angle, + cos: angleCos, + sin: angleSin, + + hasColorMode: false, + colorMode: 0 + }], + startIndex: 0 + } + + defaults[idx] = res; + case "composite": + if(!node.exists("char")) { + Logs.error("<composite> must have a char attribute", "Alphabet"); + return; + } + var startIndex:Int = 0; + var componentsPushed:Int = 0; + var char = node.get("char"); + var advance:Float = Std.parseFloat(node.get("advance")); + var components:Array<AlphabetComponent> = []; + for (component in node.elementsNamed("component")) { + if(!component.exists("anim")) { + Logs.error('Character $char: <component> must have a anim attribute', "Alphabet"); + return; + } + + var angle:Float = Std.parseFloat(component.get("angle")).getDefaultFloat(0.0); + var rad = angle * FlxAngle.TO_RAD; + var angleCos = Math.cos(rad); + var angleSin = Math.sin(rad); + + var xOff:Float = -Std.parseFloat(component.get("x")).getDefaultFloat(0.0); + var yOff:Float = Std.parseFloat(component.get("y")).getDefaultFloat(0.0); + var xScale:Float = Std.parseFloat(component.get("scaleX")).getDefaultFloat(1.0); + var yScale:Float = Std.parseFloat(component.get("scaleY")).getDefaultFloat(1.0); + + var colorMode = ["tint", "offsets", "none"].indexOf(component.get("colorMode")); + + if (component.get("hasOutline") == "true") { + components.insert(startIndex, { + refIndex: componentsPushed, + anim: component.get("outline"), + + x: Std.parseFloat(component.get("outlineX")).getDefaultFloat(0.0), + y: Std.parseFloat(component.get("outlineY")).getDefaultFloat(0.0), + scaleX: xScale, + scaleY: yScale, + + shouldRotate: angle != 0, + angle: angle, + cos: angleCos, + sin: angleSin, + + hasColorMode: colorMode >= 0, + colorMode: colorMode, + }); + ++startIndex; + } + + components.push({ + outIndex: (component.get("hasOutline") == "true") ? startIndex - 1 : null, + anim: component.get("anim"), + + x: xOff, + y: yOff, + scaleX: xScale, + scaleY: yScale, + + shouldRotate: angle != 0, + angle: angle, + cos: angleCos, + sin: angleSin, + + hasColorMode: colorMode >= 0, + colorMode: colorMode, + }); + ++componentsPushed; + } + letterData.set(char, { + isDefault: false, + advance: advance, + advanceEmpty: Math.isNaN(advance), + components: components, + startIndex: startIndex + }); + manualLetters.push(char); + case "anim": + if(!node.exists("char")) { + Logs.error("<anim> must have a char attribute", "Alphabet"); + return; + } + var char = node.get("char"); + + var angle:Float = Std.parseFloat(node.get("angle")).getDefaultFloat(0.0); + var rad = angle * FlxAngle.TO_RAD; + var angleCos = Math.cos(rad); + var angleSin = Math.sin(rad); + + var xOff:Float = -Std.parseFloat(node.get("x")).getDefaultFloat(0.0); + var yOff:Float = Std.parseFloat(node.get("y")).getDefaultFloat(0.0); + var xScale:Float = Std.parseFloat(node.get("scaleX")).getDefaultFloat(1.0); + var yScale:Float = Std.parseFloat(node.get("scaleY")).getDefaultFloat(1.0); + var advance:Float = Std.parseFloat(node.get("advance")); + + var colorMode = ["tint", "offsets", "none"].indexOf(node.get("colorMode")); + + var components:Array<AlphabetComponent> = []; + if (node.get("hasOutline") == "true") { + components.insert(0, { + refIndex: 0, + anim: node.get("outline"), + + x: Std.parseFloat(node.get("outlineX")).getDefaultFloat(0.0), + y: Std.parseFloat(node.get("outlineY")).getDefaultFloat(0.0), + scaleX: xScale, + scaleY: yScale, + + shouldRotate: angle != 0, + angle: angle, + cos: angleCos, + sin: angleSin, + + hasColorMode: colorMode >= 0, + colorMode: colorMode, + }); + } + + components.push({ + outIndex: (node.get("hasOutline") == "true") ? 0 : null, + anim: node.firstChild().nodeValue, + + x: xOff, + y: yOff, + scaleX: xScale, + scaleY: yScale, + + shouldRotate: angle != 0, + angle: angle, + cos: angleCos, + sin: angleSin, + + hasColorMode: colorMode >= 0, + colorMode: colorMode, + }); + letterData.set(char, { + isDefault: false, + advance: advance, + advanceEmpty: Math.isNaN(advance), + components: components, + startIndex: (node.get("hasOutline") == "true") ? 1 : 0 + }); + manualLetters.push(char); + case "languageSection": // used to only parse characters if a specific language is selected + if(!node.exists("langs")) { + Logs.error("<languageSection> must have a langs attribute", "Alphabet"); + return; + } + var langs = [for (lang in node.get("langs").split(",")) lang.trim()]; + // maybe add a way to toggle this off? like force it to add all + if (langs.contains(Options.language)) { + for (langNode in node.elements()) + checkNode(langNode); + } } } -} -class AlphaCharacter extends FlxSprite -{ - @:dox(hide) @:noCompletion public static var __alphaPath:String = null; + function loadFont(value:String):Void { + __queueResize = true; - public static var letterAlphabetPath:String; - public static var boldAlphabetPath:String; + var xml:Xml = Xml.parse(Assets.getText(Paths.xml("alphabet/" + value))).firstElement(); - public static var boldAnims:Map<String, String> = []; - public static var letterAnims:Map<String, String> = []; - - public var row:Int = 0; - - public function setColor(c:FlxColor, isBold:Bool) { - if (isBold) { - colorTransform.redMultiplier = c.redFloat; - colorTransform.greenMultiplier = c.greenFloat; - colorTransform.blueMultiplier = c.blueFloat; - } else { - colorTransform.redOffset = c.red; - colorTransform.greenOffset = c.green; - colorTransform.blueOffset = c.blue; + // reset old values + letterData = new Map(); + defaults = { + var v = new Vector(3); + v[0] = null; + v[1] = null; + v[2] = null; + v; } - } - public function new(x:Float, y:Float) - { - super(x, y); - - antialiasing = true; - } - - public function createBold(letter:String) - { - if(!boldAnims.exists(letter)) - letter = letter.toUpperCase(); - if (!boldAnims.exists(letter)) { - visible = false; - active = false; - scale.set(); - width = 40; - return; + loaded = { + var v = new Vector(3); + v[0] = []; + v[1] = []; + v[2] = []; + v; } - frames = Paths.getFrames(boldAlphabetPath); - animation.addByPrefix(letter, boldAnims[letter], 24); - animation.play(letter); - updateHitbox(); + failedLetters = [" "]; - y += row * height; + defaultAdvance = Std.parseFloat(xml.get("advance")).getDefaultFloat(40.0); + lineGap = Std.parseFloat(xml.get("lineGap")).getDefaultFloat(75.0); + fps = Std.parseFloat(xml.get("fps")).getDefaultFloat(24.0); + forceCase = xml.get("forceCasing").getDefault("none").toLowerCase(); + colorMode = (["offsets", "none"].indexOf(xml.get("colorMode")) + 1); + antialiasing = xml.get("antialiasing").getDefault("true") == "true"; + + frames = null; + + for (node in xml.elements()) + checkNode(node); } - public function createLetter(letter:String):Void - { - if (!letterAnims.exists(letter)) { - visible = false; - active = false; - scale.set(); - width = 40; - return; + private static var alphabetProperties:Array<String> = ["fps", "advance", "lineGap", "forceCasing", "useColorOffsets", "antialiasing"]; + private static var componentProperties:Array<String> = ["name", "anim", "x", "y", "scaleX", "scaleY", "angle", "offsetX", "offsetY"]; + + public function buildXML():Xml { + var xml = Xml.createElement("alphabet"); + xml.set("fps", Std.string(fps)); + xml.set("advance", Std.string(defaultAdvance)); + xml.set("lineGap", Std.string(lineGap)); + xml.set("forceCasing", ["none", "upper", "lower"][forceCase]); + xml.set("useColorOffsets", ["tint", "offsets", "none"][colorMode]); + xml.set("antialiasing", antialiasing ? "true" : "false"); + + xml.attributeOrder = alphabetProperties; + + /*for (component in components) { + var componentXml = Xml.createElement("component"); + componentXml.attributeOrder = componentProperties; + componentXml.set("name", component.name); + componentXml.set("anim", component.anim); + componentXml.set("x", Std.string(component.x)); + componentXml.set("y", Std.string(component.y)); + if(component.scaleX != 1) componentXml.set("scaleX", Std.string(component.scaleX)); + if(component.scaleY != 1) componentXml.set("scaleY", Std.string(component.scaleY)); + if(component.angle != 0) componentXml.set("angle", Std.string(component.angle)); + if(component.offsetX != 0) componentXml.set("offsetX", Std.string(component.offsetX)); + if(component.offsetY != 0) componentXml.set("offsetY", Std.string(component.offsetY)); + xml.addChild(componentXml); + }*/ + + return xml; + } + + public function copyData(from:Alphabet) { + if (from.font == this.font) return; + + __queueResize = true; + @:bypassAccessor @:bypassAccessor this.font = from.font; // double bypass in case haxe gets finicky + this.frames = from.frames; + this.antialiasing = from.antialiasing; + this.fps = from.fps; + this.defaultAdvance = from.defaultAdvance; + this.lineGap = from.lineGap; + this.forceCase = from.forceCase; + this.colorMode = from.colorMode; + this.letterData = from.letterData; + this.defaults = from.defaults; + this.loaded = from.loaded; + this.manualLetters = from.manualLetters; + this.failedLetters = from.failedLetters; + this.failedOutlines = from.failedOutlines; + } + + override function destroy():Void { + originOffset = FlxDestroyUtil.destroy(originOffset); + __drawScale = FlxDestroyUtil.put(__drawScale); + __renderData = null; + __laneWidths = null; + effects = null; + letterData = null; + defaults = null; + manualLetters = null; + failedLetters = null; + failedOutlines = null; + super.destroy(); + } + + function get_textWidth():Float { + if (__queueResize) + recalcSizes(); + return textWidth; + } + function set_textWidth(value:Float):Float { + if(!__queueResize) + __queueResize = __forceWidth != value; + return __forceWidth = value; + } + + override function get_width():Float { + return textWidth * scale.x; + } + override function get_height():Float { + return textHeight * scale.y; + } + + function get_textHeight():Float { + if (__queueResize) + recalcSizes(); + return textHeight; + } + + function set_text(value:String):String { + if(!__queueResize) + __queueResize = text != value; + return text = value; + } + + function set_font(value:String):String { + if (font != value) { + loadFont(font = value); } - frames = Paths.getFrames(letterAlphabetPath); - animation.addByPrefix(letter, letterAnims[letter], 24); - animation.play(letter); - updateHitbox(); - - y = (110 - height); - y += row * 60; + return value; } -} +} \ No newline at end of file diff --git a/source/funkin/menus/ui/ClassicAlphabet.hx b/source/funkin/menus/ui/ClassicAlphabet.hx new file mode 100644 index 00000000..f9a29ecf --- /dev/null +++ b/source/funkin/menus/ui/ClassicAlphabet.hx @@ -0,0 +1,286 @@ +package funkin.menus.ui; + +import flixel.group.FlxSpriteGroup; +import flixel.util.FlxColor; +import flixel.util.FlxTimer; +import funkin.backend.assets.IModsAssetLibrary; +import funkin.backend.assets.LimeLibrarySymbol; +import haxe.xml.Access; +import openfl.utils.AssetLibrary; + +using StringTools; + +/** + * Loosely based on FlxTypeText lolol + */ +class ClassicAlphabet extends FlxSpriteGroup +{ + public var delay:Float = 0.05; + public var paused:Bool = false; + + // for menu shit + public var targetY:Float = 0; + public var isMenuItem:Bool = false; + + public var text:String = ""; + + var _finalText:String = ""; + var _curText:String = ""; + + var yMulti:Float = 1; + + // custom shit + // amp, backslash, question mark, apostrophy, comma, angry faic, period + var lastSprite:AlphaCharacter; + var xPosResetted:Bool = false; + + var splitWords:Array<String> = []; + + var isBold:Bool = false; + + private override function set_color(c:Int):Int { + for(e in group.members) { + if (e is AlphaCharacter) { + var char:AlphaCharacter = cast e; + char.setColor(c, isBold); + } + } + return super.set_color(c); + } + + // TODO: fix this shit refreshing + public function refreshAlphabetXML(path:String) { + AlphaCharacter.__alphaPath = Paths.getAssetsRoot() + path; + try { + 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'; + + for(e in xml.elements) { + var bold = e.name == "bold"; + var list = bold ? AlphaCharacter.boldAnims : AlphaCharacter.letterAnims; + if (e.has.spritesheet) { + if (bold) + AlphaCharacter.boldAlphabetPath = e.att.spritesheet; + else + AlphaCharacter.letterAlphabetPath = e.att.spritesheet; + } + for(e in e.nodes.letter) { + if (!e.has.char || !e.has.anim) continue; + var name = e.att.char; + var anim = e.att.anim; + list[name] = anim; + } + } + } catch(e) { + trace(e.details()); + } + } + public function new(x:Float, y:Float, text:String = "", ?bold:Bool = false, typed:Bool = false) + { + super(x, y); + + _finalText = this.text = text; + isBold = bold; + + var alphabetPath = Paths.xml("alphabet"); + if (Paths.getAssetsRoot() + alphabetPath != AlphaCharacter.__alphaPath) { + refreshAlphabetXML(alphabetPath); + } + #if MOD_SUPPORT else { + var libThing = new LimeLibrarySymbol(alphabetPath); + if (libThing.library is AssetLibrary) { + 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 + if (!modLib.__isCacheValid(library.cachedBytes, libThing.symbolName)) { + refreshAlphabetXML(alphabetPath); + } + } + } + } + #end + + if (text != "") + { + if (typed) + { + startTypedText(); + } + else + { + addText(); + } + } + } + + public function addText() + { + doSplitWords(); + + var xPos:Float = 0; + var curRow:Int = 0; + for (i=>character in splitWords) + { + if (lastSprite != null) + xPos = lastSprite.x + lastSprite.width - x; + + if (xPosResetted && !(xPosResetted = false)) xPos = 0; + + var letter:AlphaCharacter = new AlphaCharacter(xPos, 0); + letter.row = curRow; + if (isBold) + letter.createBold(character); + else + letter.createLetter(character); + + // anim not found + if (!letter.visible) + xPos += 40; + + letter.setColor(color, isBold); + add(letter); + + lastSprite = letter; + if (_finalText.fastCodeAt(i) == "\n".code) + { + xPosResetted = true; + curRow += 1; + } + } + } + + function doSplitWords():Void + { + splitWords = _finalText.split(""); + } + + //public var personTalking:String = Flags.DEFAULT_GIRLFRIEND; + + public function startTypedText():Void + { + _finalText = text; + doSplitWords(); + + // trace(arrayShit); + + var loopNum:Int = 0; + + var xPos:Float = 0; + var curRow:Int = 0; + + new FlxTimer().start(0.05, function(tmr:FlxTimer) + { + // trace(_finalText.fastCodeAt(loopNum) + " " + _finalText.charAt(loopNum)); + if (_finalText.fastCodeAt(loopNum) == "\n".code) + { + yMulti += 1; + xPosResetted = true; + xPos = 0; + curRow += 1; + } + loopNum += 1; + + tmr.time = FlxG.random.float(0.04, 0.09); + }, splitWords.length); + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (isMenuItem) + { + var scaledY = FlxMath.remapToRange(targetY, 0, 1, 0, 1.3); + + y = CoolUtil.fpsLerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.16); + x = CoolUtil.fpsLerp(x, (targetY * 20) + 90, 0.16); + } + + if (text != _finalText) { + _finalText = text; + for(e in members) + e.destroy(); + members.clear(); + lastSprite = null; + addText(); + } + } +} + +class AlphaCharacter extends FlxSprite +{ + @:dox(hide) @:noCompletion public static var __alphaPath:String = null; + + public static var letterAlphabetPath:String; + public static var boldAlphabetPath:String; + + public static var boldAnims:Map<String, String> = []; + public static var letterAnims:Map<String, String> = []; + + public var row:Int = 0; + + public function setColor(c:FlxColor, isBold:Bool) { + if (isBold) { + colorTransform.redMultiplier = c.redFloat; + colorTransform.greenMultiplier = c.greenFloat; + colorTransform.blueMultiplier = c.blueFloat; + } else { + colorTransform.redOffset = c.red; + colorTransform.greenOffset = c.green; + colorTransform.blueOffset = c.blue; + } + } + public function new(x:Float, y:Float) + { + super(x, y); + + antialiasing = true; + } + + public function createBold(letter:String) + { + if(!boldAnims.exists(letter)) + letter = letter.toUpperCase(); + if (!boldAnims.exists(letter)) { + visible = false; + active = false; + scale.set(); + width = 40; + return; + } + frames = Paths.getFrames(boldAlphabetPath); + animation.addByPrefix(letter, boldAnims[letter], 24); + animation.play(letter); + updateHitbox(); + + y += row * height; + } + + public function createLetter(letter:String):Void + { + if (!letterAnims.exists(letter)) { + visible = false; + active = false; + scale.set(); + width = 40; + return; + } + frames = Paths.getFrames(letterAlphabetPath); + animation.addByPrefix(letter, letterAnims[letter], 24); + animation.play(letter); + updateHitbox(); + + y = (110 - height); + y += row * 60; + } +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/ColorWaveEffect.hx b/source/funkin/menus/ui/effects/ColorWaveEffect.hx new file mode 100644 index 00000000..f5fcce52 --- /dev/null +++ b/source/funkin/menus/ui/effects/ColorWaveEffect.hx @@ -0,0 +1,22 @@ +package funkin.menus.ui.effects; + +import flixel.util.FlxColor; +import funkin.menus.ui.effects.RegionEffect; + +class ColorWaveEffect extends RegionEffect { + public var period:Float = 30; + public var color1:FlxColor; + public var color2:FlxColor; + + public function new(color1:FlxColor, color2:FlxColor, ?period:Float = 30) { + super(); + speed = 5; + this.color1 = color1; + this.color2 = color2; + this.period = period; + } + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData) { + renderData.color = FlxColor.interpolate(color1, color2, (-effectTime + lineIndex).positiveModulo(period) / period); + } +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/RegionEffect.hx b/source/funkin/menus/ui/effects/RegionEffect.hx new file mode 100644 index 00000000..7f99b166 --- /dev/null +++ b/source/funkin/menus/ui/effects/RegionEffect.hx @@ -0,0 +1,77 @@ +package funkin.menus.ui.effects; + +import flixel.util.FlxColor; + +class AlphabetRenderData { + public var parent:Alphabet; + public var letter:String; + public var offsetX:Float = 0; + public var offsetY:Float = 0; + public var color(get, set):FlxColor; + public var red:Float = 0; + public var green:Float = 0; + public var blue:Float = 0; + public var alpha:Float = 1; + + public function new(parent:Alphabet) { + this.parent = parent; + } + + public function reset(parent:Alphabet, red:Float, green:Float, blue:Float, alpha:Float, letter:String) { + this.parent = parent; + this.letter = letter; + this.offsetX = 0; + this.offsetY = 0; + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + function get_color():FlxColor { + return FlxColor.fromRGBFloat(red, green, blue); + } + function set_color(value:FlxColor):FlxColor { + red = value.redFloat; + green = value.greenFloat; + blue = value.blueFloat; + return value; + } +} + +class RegionEffect { + public var effectTime:Float = 0; + public var speed:Float = 1; + public var enabled:Bool = true; + public var regionMin:Array<Int> = []; + public var regionMax:Array<Int> = []; + + public function new() {} + + public function resetRegions() { + regionMin.splice(0, regionMin.length); + regionMax.splice(0, regionMax.length); + } + + public function addRegion(min:Int, max:Int) { + regionMin.push(min); + regionMax.push(max); + } + + public function willModify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Bool { + if (!enabled) return false; + + if (regionMin.length == 0) regionMin.push(0); + if (regionMax.length == 0) regionMax.push(-1); + + for (i in 0...Std.int(Math.min(regionMin.length, regionMax.length))) { + var min = (regionMin[i] < 0) ? renderData.parent.text.length - regionMin[i] : regionMin[i]; + var max = (regionMax[i] < 0) ? renderData.parent.text.length - regionMax[i] : regionMax[i]; + if (index >= min && index <= max) + return true; + } + return false; + } + + public function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void {} +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/ShakeEffect.hx b/source/funkin/menus/ui/effects/ShakeEffect.hx new file mode 100644 index 00000000..1144edbf --- /dev/null +++ b/source/funkin/menus/ui/effects/ShakeEffect.hx @@ -0,0 +1,23 @@ +package funkin.menus.ui.effects; + +import funkin.menus.ui.effects.RegionEffect; + +class ShakeEffect extends RegionEffect { + public var intensityX:Float = 2; + public var intensityY:Float = 2; + + public function new(?x:Float = 2, ?y:Float = 2) { + super(); + intensityX = x; + intensityY = y; + } + + override function willModify(index:Int, lineIndex:Int, renderData:AlphabetRenderData) { + return (intensityX != 0 || intensityY != 0) && super.willModify(index, lineIndex, renderData); + } + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void { + renderData.offsetX += FlxG.random.float(-intensityX, intensityX); + renderData.offsetY += FlxG.random.float(-intensityY, intensityY); + } +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/TypeEffect.hx b/source/funkin/menus/ui/effects/TypeEffect.hx new file mode 100644 index 00000000..808877ba --- /dev/null +++ b/source/funkin/menus/ui/effects/TypeEffect.hx @@ -0,0 +1,52 @@ +package funkin.menus.ui.effects; + +import flixel.tweens.FlxEase; +import funkin.menus.ui.effects.RegionEffect; + +class TypeEffect extends RegionEffect { + public var interval:Float = 0.075; + var lastIndex:Int = -1; + // These let you make the text do a cool fade in when it appears. + public var alphaStart:Float = 1; + public var alphaEase:EaseFunction = FlxEase.quadOut; + // More stuff for fade in, this adds a y offset. + public var offsetStart:Float = 0.0; + public var offsetEase:EaseFunction = FlxEase.quadOut; + // Funny noises. You just kinda `effect.addSound(PATH)` and boom. + public var sounds:Array<flixel.sound.FlxSound> = []; + public var randomSound:Bool = true; // do them in order or play a random one. + var curSound:Int = -1; + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void { + if (effectTime < interval * index) { + renderData.alpha = 0.0; + return; + } + + if (index > lastIndex && sounds.length > 0) { + lastIndex = index; + curSound = (randomSound) ? FlxG.random.int(0, sounds.length - 1) : (curSound + 1) % sounds.length; + sounds[curSound].time = 0; + sounds[curSound].play(); + } + + var scale = Math.min((effectTime - interval * index) / interval, 1.0); + renderData.alpha *= FlxMath.lerp(alphaStart, 1.0, alphaEase(scale)); + renderData.offsetY += offsetStart * (1.0 - offsetEase(scale)); + } + + public function addSound(path:String) { + sounds.push(FlxG.sound.load(Paths.sound(path))); + } + + public function clearSounds() { + sounds.splice(0, sounds.length); + } + + public function resetTimer() { + // mainly for sound playing. + effectTime = 0; + lastIndex = -1; + curSound = -1; + } +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/WaveEffect.hx b/source/funkin/menus/ui/effects/WaveEffect.hx new file mode 100644 index 00000000..26b25fbb --- /dev/null +++ b/source/funkin/menus/ui/effects/WaveEffect.hx @@ -0,0 +1,26 @@ +package funkin.menus.ui.effects; + +import funkin.menus.ui.effects.RegionEffect; + +class WaveEffect extends RegionEffect { + public var intensityX:Float = 0; + public var intensityY:Float = 5; + public var period:Float = 10; + + public function new(?x:Float = 0, ?y:Float = 5, ?p:Float = 10) { + super(); + speed = 3; + intensityX = x; + intensityY = y; + period = p; + } + + override function willModify(index:Int, lineIndex:Int, renderData:AlphabetRenderData) { + return period != 0 && (intensityX != 0 || intensityY != 0) && super.willModify(index, lineIndex, renderData); + } + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void { + renderData.offsetX += intensityX * Math.cos((effectTime + lineIndex) * Math.PI * 2 / period); + renderData.offsetY += intensityY * Math.sin((effectTime + lineIndex) * Math.PI * 2 / period); + } +} \ No newline at end of file diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx index 9ca1dab7..a60d519d 100644 --- a/source/funkin/options/Options.hx +++ b/source/funkin/options/Options.hx @@ -29,16 +29,21 @@ class Options public static var autoPause:Bool = true; public static var antialiasing:Bool = true; public static var volume:Float = 1; + public static var volumeSFX:Float = 1; public static var week6PixelPerfect:Bool = true; 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; public static var songOffset:Float = 0; 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 @@ -52,6 +57,7 @@ class Options public static var editorSFX:Bool = true; public static var editorPrettyPrint:Bool = false; public static var editorsResizable:Bool = true; + public static var bypassEditorsResize:Bool = false; public static var maxUndos:Int = 120; /** @@ -94,6 +100,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]; @@ -101,16 +109,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> = [PLUS]; + public static var P1_VOLUME_DOWN:Array<FlxKey> = [MINUS]; + public static var P1_VOLUME_MUTE:Array<FlxKey> = [ZERO]; + + // 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]; @@ -118,16 +140,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> = [NUMPADPLUS]; + public static var P2_VOLUME_DOWN:Array<FlxKey> = [NUMPADMINUS]; + public static var P2_VOLUME_MUTE:Array<FlxKey> = [NUMPADZERO]; + + // 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>; @@ -135,8 +171,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(); @@ -145,7 +191,12 @@ class Options if (!__eventAdded) { Lib.application.onExit.add(function(i:Int) { - trace("Saving settings..."); + Logs.traceColored([ + Logs.getPrefix("Options"), + Logs.logText("Saving "), + Logs.logText("settings", GREEN), + Logs.logText("...") + ], VERBOSE); save(); }); __eventAdded = true; @@ -165,6 +216,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 2b8c0168..ace34aad 100644 --- a/source/funkin/options/OptionsMenu.hx +++ b/source/funkin/options/OptionsMenu.hx @@ -9,25 +9,38 @@ import haxe.xml.Access; class OptionsMenu extends TreeMenu { public static var mainOptions:Array<OptionCategory> = [ - { - name: 'Controls', - desc: 'Change Controls for Player 1 and Player 2!', + { // name and desc are actually the translations ids! - Nex + name: 'optionsTree.controls-name', + desc: 'optionsTree.controls-desc', state: null, - substate: funkin.options.keybinds.KeybindsOptions + substate: (name:String, desc:String) -> { + return new funkin.options.keybinds.KeybindsOptions(); + } }, { - name: 'Gameplay >', - desc: 'Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...', + name: 'optionsTree.gameplay-name', + desc: 'optionsTree.gameplay-desc', + suffix: " >", state: GameplayOptions }, { - name: 'Appearance >', - desc: 'Change Appearance options such as Flashing menus...', + name: 'optionsTree.appearance-name', + desc: 'optionsTree.appearance-desc', + suffix: " >", state: AppearanceOptions }, + #if TRANSLATIONS_SUPPORT { - name: 'Miscellaneous >', - desc: 'Use this menu to reset save data or engine settings.', + name: 'optionsTree.language-name', + desc: 'optionsTree.language-desc', + suffix: " >", + state: LanguageOptions + }, + #end + { + name: 'optionsTree.miscellaneous-name', + desc: 'optionsTree.miscellaneous-desc', + suffix: " >", state: MiscOptions } ]; @@ -50,40 +63,50 @@ class OptionsMenu extends TreeMenu { bg.antialiasing = true; add(bg); - main = new OptionsScreen("Options", "Select a category to continue.", [for(o in mainOptions) new TextOption(o.name, o.desc, function() { - if (o.substate != null) { - persistentUpdate = false; - persistentDraw = true; - if (o.substate is MusicBeatSubstate) { - openSubState(o.substate); + main = new OptionsScreen("optionsMenu.header.title", "optionsMenu.header.desc", [for(o in mainOptions) { + new TextOption(o.name, o.desc, o.suffix, function() { + if (o.substate != null) { + persistentUpdate = false; + persistentDraw = true; + if (o.substate is MusicBeatSubstate) { + openSubState(o.substate); + } else if(Reflect.isFunction(o.substate)) { + var substate:(name:String, desc:String) -> MusicBeatSubstate = o.substate; + openSubState(substate(o.name, o.desc)); + } else { // o.substate is Class<OptionsScreen> + openSubState(Type.createInstance(o.substate, [o.name, o.desc])); + } } else { - openSubState(Type.createInstance(o.substate, [])); + if (o.state is OptionsScreen) { + optionsTree.add(o.state); + } else if(Reflect.isFunction(o.state)) { + var state:(name:String, desc:String) -> OptionsScreen = o.state; + optionsTree.add(state(o.name, o.desc)); + } else { // o.state is Class<OptionsScreen> + optionsTree.add(Type.createInstance(o.state, [o.name, o.desc])); + } } - } else { - if (o.state is OptionsScreen) { - optionsTree.add(o.state); - } else { - optionsTree.add(Type.createInstance(o.state, [])); - } - } - })]); + }); + }]); - var xmlPath = Paths.xml("config/options"); - for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.AssetSource.MODS]) { - if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { + for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { + var xmlPath = Paths.xml('config/options/LIB_$i'); + + if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) { var access:Access = null; - try { - access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source))); - } catch(e) { - Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR); - } - - if (access != null) - for(o in parseOptionsFromXML(access)) - main.add(o); + 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); } } + } + public function reloadStrings() { + for(o in main.members) { + o.reloadStrings(); + } + optionsTree.reloadStrings(); + reloadLabels(); } public override function exit() { @@ -100,29 +123,29 @@ class OptionsMenu extends TreeMenu { for(node in xml.elements) { if (!node.has.name) { - Logs.trace("An option node requires a name attribute.", WARNING); + Logs.warn("An option node requires a name attribute."); continue; } var name = node.getAtt("name"); - var desc = node.getAtt("desc").getDefault("No Description"); + var desc = node.getAtt("desc").getDefault("optionsMenu.desc-missing"); switch(node.name) { case "checkbox": if (!node.has.id) { - Logs.trace("A checkbox option requires an \"id\" for option saving.", WARNING); + Logs.warn("A checkbox option requires an \"id\" for option saving."); continue; } options.push(new Checkbox(name, desc, node.att.id, FlxG.save.data)); case "number": if (!node.has.id) { - Logs.trace("A number option requires an \"id\" for option saving.", WARNING); + Logs.warn("A number option requires an \"id\" for option saving."); continue; } options.push(new NumOption(name, desc, Std.parseFloat(node.att.min), Std.parseFloat(node.att.max), Std.parseFloat(node.att.change), node.att.id, null, FlxG.save.data)); case "choice": if (!node.has.id) { - Logs.trace("A choice option requires an \"id\" for option saving.", WARNING); + Logs.warn("A choice option requires an \"id\" for option saving."); continue; } diff --git a/source/funkin/options/OptionsScreen.hx b/source/funkin/options/OptionsScreen.hx index 4a5eb806..53dd5b06 100644 --- a/source/funkin/options/OptionsScreen.hx +++ b/source/funkin/options/OptionsScreen.hx @@ -14,14 +14,60 @@ class OptionsScreen extends FlxTypedSpriteGroup<OptionType> { public var name:String; public var desc:String; + /** + * The prefix to add to the translations ids. + **/ + public var prefix:String = ""; - public function new(name:String, desc:String, ?options:Array<OptionType>) { + private var rawName(default, set):String; + private var rawDesc(default, set):String; + + public inline function getNameID(name):String { + return prefix + name + "-name"; + } + public inline function getDescID(name):String { + return prefix + name + "-desc"; + } + + public function getName(name:String, ?args:Array<Dynamic>):String { + return TU.translate(getNameID(name), args); + } + public function getDesc(name:String, ?args:Array<Dynamic>):String { + return TU.translate(getDescID(name), args); + } + public function translate(name:String, ?args:Array<Dynamic>):String { + return TU.translate(prefix + name, args); + } + + public function new(name:String, desc:String, ?prefix:String, ?options:Array<OptionType>) { super(); - this.name = name; - this.desc = desc; + rawName = name; + rawDesc = desc; + this.prefix = prefix != null ? prefix : ""; if (options != null) for(o in options) add(o); } + function set_rawName(v:String) { + rawName = v; + this.name = TU.exists(rawName) ? TU.translate(rawName) : rawName; + return v; + } + + function set_rawDesc(v:String) { + rawDesc = v; + this.desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc; + return v; + } + + public function reloadStrings() { + this.rawName = rawName; + this.rawDesc = rawDesc; + + for(o in members) { + o.reloadStrings(); + } + } + public override function update(elapsed:Float) { super.update(elapsed); @@ -45,8 +91,10 @@ class OptionsScreen extends FlxTypedSpriteGroup<OptionType> { if (members.length > 0) { members[curSelected].selected = true; - if (controls.ACCEPT || (FlxG.mouse.justReleased && Main.timeSinceFocus > 0.25)) + if (controls.ACCEPT || (FlxG.mouse.justReleased && Main.timeSinceFocus > 0.25)) { members[curSelected].onSelect(); + onSelect(members[curSelected]); + } if (controls.LEFT_P) members[curSelected].onChangeSelection(-1); if (controls.RIGHT_P) @@ -75,4 +123,17 @@ class OptionsScreen extends FlxTypedSpriteGroup<OptionType> { } public dynamic function onClose(o:OptionsScreen) {} + public dynamic function onSelect(o:OptionType) { + if(o is funkin.options.type.RadioButton) { + var orb:funkin.options.type.RadioButton = cast o; + for(e in members) { + if(e is funkin.options.type.RadioButton) { + var rb:funkin.options.type.RadioButton = cast e; + if(rb.forId == orb.forId && rb != o) { + rb.checked = false; + } + } + } + } + } } \ No newline at end of file diff --git a/source/funkin/options/OptionsTree.hx b/source/funkin/options/OptionsTree.hx index c457d52e..9fd9e590 100644 --- a/source/funkin/options/OptionsTree.hx +++ b/source/funkin/options/OptionsTree.hx @@ -3,6 +3,7 @@ package funkin.options; class OptionsTree extends FlxTypedGroup<OptionsScreen> { public var lastMenu:OptionsScreen; public var treeParent:TreeMenu; + public var wasClosing:Bool = false; //public override function new() { // super(); //} @@ -13,18 +14,33 @@ class OptionsTree extends FlxTypedGroup<OptionsScreen> { last.update(elapsed); } + public function updateAll(elapsed:Float) { + for (member in members) { + if(member == null) continue; + if(member.active && member.exists) { + member.update(elapsed); + } + } + } public override function draw() { super.draw(); - if (lastMenu != null) { + if (lastMenu != null) { // manually draw lastMenu since it got removed from the members list lastMenu.draw(); } } + public function reloadStrings() { + for(o in members) { + o.reloadStrings(); + } + } + public override function add(m:OptionsScreen) { super.add(m); setup(m); clearLastMenu(); + wasClosing = false; onMenuChange(); return m; } @@ -32,8 +48,10 @@ class OptionsTree extends FlxTypedGroup<OptionsScreen> { var last = members.last(); super.insert(pos, m); setup(m); - if (last != members.last()) + if (last != members.last()) { + wasClosing = false; onMenuChange(); + } return m; } @@ -45,6 +63,7 @@ class OptionsTree extends FlxTypedGroup<OptionsScreen> { } function __subMenuClose(m:OptionsScreen) { + wasClosing = true; clearLastMenu(); lastMenu = m; remove(m, true); diff --git a/source/funkin/options/TreeMenu.hx b/source/funkin/options/TreeMenu.hx index b7bbde3e..41717e88 100644 --- a/source/funkin/options/TreeMenu.hx +++ b/source/funkin/options/TreeMenu.hx @@ -3,6 +3,7 @@ package funkin.options; import flixel.FlxState; import flixel.tweens.FlxTween; import flixel.util.typeLimit.OneOfTwo; +import flixel.util.typeLimit.OneOfThree; import funkin.backend.FunkinText; import funkin.editors.ui.UIState; import funkin.menus.MainMenuState; @@ -76,16 +77,24 @@ class TreeMenu extends UIState { menuChangeTween = null; }}, (val:Float) -> screenScroll = val); - var t = ""; - for(o in optionsTree.members) - t += '${o.name} > '; - pathLabel.text = t; - - var idk:OptionsScreen = optionsTree.members.last(); - if (idk.members.length > 0) updateDesc(idk.members[idk.curSelected].desc); + reloadLabels(); } } + public function onMenuClose(m:OptionsScreen) { + CoolUtil.playMenuSFX(CANCEL); + } + + public function reloadLabels() { + var t = ""; + for(o in optionsTree.members) + t += '${o.name} > '; + pathLabel.text = t; + + var idk:OptionsScreen = optionsTree.members.last(); + if (idk.members.length > 0) updateDesc(idk.members[idk.curSelected].desc); + } + public function updateDesc(moreTxt:String = '') { pathDesc.text = optionsTree.members.last().desc; if (moreTxt != null && moreTxt.length > 0) pathDesc.text += '\n' + moreTxt; @@ -98,14 +107,9 @@ class TreeMenu extends UIState { lastState = null; } - public function onMenuClose(m:OptionsScreen) { - CoolUtil.playMenuSFX(CANCEL); - } - var menuChangeTween:FlxTween; public override function update(elapsed:Float) { super.update(elapsed); - Framerate.offset.y = pathBG.height; // in case path gets so long it goes offscreen @@ -133,8 +137,9 @@ class TreeMenu extends UIState { typedef OptionCategory = { var name:String; var desc:String; - var state:OneOfTwo<OptionsScreen, Class<OptionsScreen>>; - var ?substate:OneOfTwo<MusicBeatSubstate, Class<MusicBeatSubstate>>; + var state:OneOfThree<OptionsScreen, Class<OptionsScreen>, (name:String, desc:String) -> OptionsScreen>; + var ?substate:OneOfThree<MusicBeatSubstate, Class<MusicBeatSubstate>, (name:String, desc:String) -> MusicBeatSubstate>; + var ?suffix:String; } typedef OptionTypeDef = { diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx index 9b97e299..fd364bdd 100644 --- a/source/funkin/options/categories/AppearanceOptions.hx +++ b/source/funkin/options/categories/AppearanceOptions.hx @@ -1,52 +1,48 @@ package funkin.options.categories; class AppearanceOptions extends OptionsScreen { - public override function new() { - super("Appearance", "Change Appearance options such as Flashing menus..."); + public override function new(title:String, desc:String) { + super(title, desc, "AppearanceOptions."); add(new NumOption( - "Framerate", - "Pretty self explanatory, isn't it?", + getName("framerate"), + getDesc("framerate"), 30, // minimum 240, // maximum 10, // change "framerate", // save name or smth __changeFPS)); // callback add(new Checkbox( - "Antialiasing", - "If unchecked, will disable antialiasing on every sprite. Can boost performances at the cost of sharper, more pixelated sprites.", + getName("antialiasing"), + getDesc("antialiasing"), "antialiasing")); add(new Checkbox( - "Colored Healthbar", - "If unchecked, the game will use the original red/green health bar from the week 6 fnf game.", + getName("colorHealthBar"), + getDesc("colorHealthBar"), "colorHealthBar")); add(new Checkbox( - "Pixel Perfect Effect", - "If checked, Week 6 will have a pixel perfect effect to it enabled, aligning every pixel on the screen.", + getName("week6PixelPerfect"), + getDesc("week6PixelPerfect"), "week6PixelPerfect")); add(new Checkbox( - "Gameplay Shaders", - "If unchecked, gameplay shaders (visual effects like Thorns's Chromatic Aberration) wont be loaded; this may be helpful on weak devices.", + getName("gameplayShaders"), + getDesc("gameplayShaders"), "gameplayShaders")); add(new Checkbox( - "Flashing Menu", - "If unchecked, will disable menu flashing when you select an option in the Main Menu, and other flashes will be slower.", + getName("flashingMenu"), + getDesc("flashingMenu"), "flashingMenu")); add(new Checkbox( - "Low Memory Mode", - "If checked, will disable certain background elements in stages to reduce memory usage.", + getName("lowMemoryMode"), + getDesc("lowMemoryMode"), "lowMemoryMode")); #if sys if (!Main.forceGPUOnlyBitmapsOff) { add(new Checkbox( - "VRAM-Only Sprites", - "If checked, will only store the bitmaps in the GPU, freeing a LOT of memory (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on bigger sprites. If you aren't sure, leave this on.", + getName("gpuOnlyBitmaps"), + getDesc("gpuOnlyBitmaps"), "gpuOnlyBitmaps")); } #end - add(new Checkbox( - "Auto Pause", - "If checked, switching windows will pause the game.", - "autoPause")); } private function __changeFPS(change:Float) { diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 15c0ba60..4e1f8a7e 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -8,32 +8,45 @@ class GameplayOptions extends OptionsScreen { var offsetSetting:NumOption; - public override function new() { - super("Gameplay", 'Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...'); + public override function new(title:String, desc:String) { + super(title, desc, "GameplayOptions."); add(new Checkbox( - "Downscroll", - "If checked, notes will go from up to down instead of down to up, as if they're falling.", + getName("downscroll"), + getDesc("downscroll"), "downscroll")); add(new Checkbox( - "Ghost Tapping", - "If unchecked, trying to hit any strum that have no note that can be hit will cause a miss.", + getName("ghostTapping"), + getDesc("ghostTapping"), "ghostTapping")); + add(new Checkbox( + getName("naughtyness"), + getDesc("naughtyness"), + "naughtyness")); + add(new Checkbox( + getName("camZoomOnBeat"), + getDesc("camZoomOnBeat"), + "camZoomOnBeat")); + add(new Checkbox( + getName("autoPause"), + getDesc("autoPause"), + "autoPause")); + add(new MeterOption( + getName("volumeSFX"), + getDesc("volumeSFX"), + 0, // minimum + 1, // maximum + 0.1, // change + "volumeSFX")); add(offsetSetting = new NumOption( - "Song Offset", - "Changes the offset that songs should start with.", + getName("songOffset"), + getDesc("songOffset"), -999, // minimum 999, // maximum 1, // change "songOffset", // save name or smth __changeOffset)); // callback - add(new Checkbox( - "Naughtyness", - "If unchecked, will censor the Week 7 cutscenes.", - "naughtyness")); - add(new Checkbox( - "Camera Zoom on Beat", - "If unchecked, will stop the camera from zooming in every 4 beats", - "camZoomOnBeat")); + add(new TextOption('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc', ' >', () -> + parent.add(new AdvancedGameplayOptions('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc')))); } private function __changeOffset(offset) @@ -53,21 +66,33 @@ 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; } } - else FlxG.sound.music.volume = 1; + else + FlxG.sound.music.volume = 1; } - public override function close() { - // To remove this timer move super.update(elapsed); to the end, but im afraid it will cause stuff to break - new FlxTimer().start(0.0000001, (_) -> { - FlxG.camera.zoom = 1; - FlxG.sound.music.volume = 1; - }); + override function close() { + FlxG.camera.zoom = 1; + FlxG.sound.music.volume = 1; super.close(); } -} \ No newline at end of file +} + +class AdvancedGameplayOptions extends OptionsScreen { + public override function new(title:String, desc:String) { + super(title, desc, "GameplayOptions.Advanced."); + add(new Checkbox( + getName("streamedMusic"), + getDesc("streamedMusic"), + "streamedMusic")); + add(new Checkbox( + getName("streamedVocals"), + getDesc("streamedVocals"), + "streamedVocals")); + } +} diff --git a/source/funkin/options/categories/LanguageOptions.hx b/source/funkin/options/categories/LanguageOptions.hx new file mode 100644 index 00000000..b3e64433 --- /dev/null +++ b/source/funkin/options/categories/LanguageOptions.hx @@ -0,0 +1,50 @@ +package funkin.options.categories; + +class LanguageRadio extends RadioButton { + public var langID:String; + + public function new(name:String, langID:String) { + this.langID = langID; + super(name, "LanguageOptions.language-desc", null, langID, null, "languageSelector"); + + checked = langID == TU.curLanguage; + } + + override function reloadStrings() { + super.reloadStrings(); + } + + override function set_rawDesc(v:String) { + rawDesc = v; + var config = TU.getConfig(langID); + this.desc = TU.exists(rawDesc) ? TU.translate( + rawDesc, [config["credits"], config["version"]] + ) : rawDesc; + return v; + } + + override dynamic function onSet(value:Dynamic) { + TranslationUtil.setLanguage(value); + if(FlxG.state is OptionsMenu) { + var menu:OptionsMenu = cast FlxG.state; + menu.reloadStrings(); + trace("Reloading strings"); + } + } +} + +class LanguageOptions extends OptionsScreen { + public override function new(title:String, desc:String) { + super(title, desc, "LanguageOptions."); + + var langArray:Array<String> = TranslationUtil.foundLanguages; + + for(lang in langArray) { + var split = lang.split("/"); + var langId = split.first(); + var langName = split.last(); + var radio = new LanguageRadio(langName, langId); + add(radio); + } + } +} diff --git a/source/funkin/options/categories/MiscOptions.hx b/source/funkin/options/categories/MiscOptions.hx index 2aecb20f..95b55aef 100644 --- a/source/funkin/options/categories/MiscOptions.hx +++ b/source/funkin/options/categories/MiscOptions.hx @@ -1,30 +1,33 @@ package funkin.options.categories; - class MiscOptions extends OptionsScreen { - public override function new() { - super("Miscellaneous", "Use this menu to reset save data or engine settings."); + public override function new(title:String, desc:String) { + super(title, desc, "MiscOptions."); + add(new Checkbox( + getName("devMode"), + getDesc("devMode"), + "devMode")); #if UPDATE_CHECKING add(new Checkbox( - "Enable Nightly Updates", - "If checked, will also include nightly builds in the update checking.", - "betaUpdates"));, + getName("betaUpdates"), + getDesc("betaUpdates"), + "betaUpdates")); add(new TextOption( - "Check for Updates", - "Select this option to check for new engine updates.", + getName("checkForUpdates"), + getDesc("checkForUpdates"), function() { var report = funkin.backend.system.updating.UpdateUtil.checkForUpdates(); if (report.newUpdate) { FlxG.switchState(new funkin.backend.system.updating.UpdateAvailableScreen(report)); } else { CoolUtil.playMenuSFX(CANCEL); - updateDescText("No update found."); + updateDescText(TU.translate(prefix + "checkForUpdates-noUpdateFound")); } - }));, + })); #end add(new TextOption( - "Reset Save Data", - "Select this option to reset save data. This will remove all of your highscores.", + getName("resetSaveData"), + getDesc("resetSaveData"), function() { // TODO: SAVE DATA RESETTING })); diff --git a/source/funkin/options/keybinds/KeybindSetting.hx b/source/funkin/options/keybinds/KeybindSetting.hx index 5250fbc5..c075fc8d 100644 --- a/source/funkin/options/keybinds/KeybindSetting.hx +++ b/source/funkin/options/keybinds/KeybindSetting.hx @@ -34,12 +34,13 @@ class KeybindSetting extends FlxTypedSpriteGroup<FlxSprite> { for(i in 1...3) { var b = null; + var bx = FlxG.width * (0.25 * (i+1)) - x; if (i == 1) - b = bind1 = new Alphabet(0, 0, "", false); + b = bind1 = new Alphabet(bx, 0, "", "normal"); else - b = bind2 = new Alphabet(0, 0, "", false); + b = bind2 = new Alphabet(bx, 0, "", "normal"); - b.setPosition(FlxG.width * (0.25 * (i+1)) - x, -60); + //b.setPosition(FlxG.width * (0.25 * (i+1)) - x, -60); add(b); } updateText(); @@ -113,7 +114,7 @@ class KeybindSetting extends FlxTypedSpriteGroup<FlxSprite> { } public function updateText() { - bind1.text = '${CoolUtil.keyToString(option1)}'; - bind2.text = '${CoolUtil.keyToString(option2)}'; + bind1.text = CoolUtil.keyToString(option1); + bind2.text = CoolUtil.keyToString(option2); } } \ No newline at end of file diff --git a/source/funkin/options/keybinds/KeybindsOptions.hx b/source/funkin/options/keybinds/KeybindsOptions.hx index afd552a0..fc68c838 100644 --- a/source/funkin/options/keybinds/KeybindsOptions.hx +++ b/source/funkin/options/keybinds/KeybindsOptions.hx @@ -8,75 +8,10 @@ using StringTools; class KeybindsOptions extends MusicBeatSubstate { public static var instance:KeybindsOptions; - public var categories:Array<ControlsCategory> = [ - { - name: 'Notes', - settings: [ - { - name: '{noteLeft}', - control: 'NOTE_LEFT' - }, - { - name: '{noteDown}', - control: 'NOTE_DOWN' - }, - { - name: '{noteUp}', - control: 'NOTE_UP' - }, - { - name: '{noteRight}', - control: 'NOTE_RIGHT' - }, - ] - }, - { - name: 'UI', - settings: [ - { - name: 'Left', - control: 'LEFT' - }, - { - name: 'Down', - control: 'DOWN' - }, - { - name: 'Up', - control: 'UP' - }, - { - name: 'Right', - control: 'RIGHT' - }, - { - name: 'Accept', - control: 'ACCEPT' - }, - { - name: 'Back', - control: 'BACK' - }, - { - name: 'Reset', - control: 'RESET' - }, - { - name: 'Pause', - control: 'PAUSE' - }, - ] - }, - { - name: 'Engine', - settings: [ - { - name: 'Switch Mod', - control: 'SWITCHMOD' - }, - ] - } - ]; + public function translate(id:String, ?args:Array<Dynamic>) + return TU.translate("KeybindsOptions." + id, args); + + public var categories:Array<ControlsCategory> = []; public var settingCam:FlxCamera; @@ -97,6 +32,119 @@ class KeybindsOptions extends MusicBeatSubstate { var isSubState:Bool = false; public override function create() { + categories = [ + { + name: translate("category.notes"), + settings: [ + { + sparrowIcon: "game/notes/default", + sparrowAnim: "purple0", + name: translate("left"), + control: 'NOTE_LEFT' + }, + { + sparrowIcon: "game/notes/default", + sparrowAnim: "blue0", + name: translate("down"), + control: 'NOTE_DOWN' + }, + { + sparrowIcon: "game/notes/default", + sparrowAnim: "green0", + name: translate("up"), + control: 'NOTE_UP' + }, + { + sparrowIcon: "game/notes/default", + sparrowAnim: "red0", + name: translate("right"), + control: 'NOTE_RIGHT' + }, + ] + }, + { + name: translate("category.ui"), + settings: [ + { + name: translate("left"), + control: 'LEFT' + }, + { + name: translate("down"), + control: 'DOWN' + }, + { + name: translate("up"), + control: 'UP' + }, + { + name: translate("right"), + control: 'RIGHT' + }, + { + name: translate("ui.accept"), + control: 'ACCEPT' + }, + { + name: translate("ui.back"), + control: 'BACK' + }, + { + name: translate("ui.reset"), + control: 'RESET' + }, + { + name: translate("ui.pause"), + control: 'PAUSE' + }, + ] + }, + { + name: translate("category.volume"), + settings: [ + { + name: translate("up"), + control: 'VOLUME_UP' + }, + { + name: translate("down"), + control: 'VOLUME_DOWN' + }, + { + name: translate("volume.mute"), + control: 'VOLUME_MUTE' + }, + ] + }, + { + name: translate("category.engine"), + settings: [ + { + name: translate("engine.switchMod"), + control: 'SWITCHMOD' + }, + ] + }, + { + name: translate("category.developer"), + devModeOnly: true, + settings: [ + { + name: translate("developer.devMenus"), + control: 'DEV_ACCESS' + }, + { + name: translate("developer.openConsole"), + control: 'DEV_CONSOLE' + }, + { + name: translate("developer.reloadState"), + control: 'DEV_RELOAD' + }, + ] + } + ]; + super.create(); instance = this; @@ -132,9 +180,11 @@ class KeybindsOptions extends MusicBeatSubstate { 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, true); + var title = new Alphabet(0, k * 75, category.name, "bold"); title.screenCenter(X); add(title); @@ -142,20 +192,6 @@ class KeybindsOptions extends MusicBeatSubstate { for(e in category.settings) { var sparrowIcon:String = null; var sparrowAnim:String = null; - if (e.name.startsWith('{note')) {// is actually a note!! - sparrowIcon = "game/notes/default"; - sparrowAnim = switch(e.name) { - case '{noteLeft}': - "purple0"; - case '{noteDown}': - "blue0"; - case '{noteUp}': - "green0"; - default: - "red0"; - }; - e.name = e.name.substring(5, e.name.length - 1); - } if (e.sparrowIcon != null) sparrowIcon = e.sparrowIcon; if (e.sparrowAnim != null) sparrowAnim = e.sparrowAnim; @@ -168,6 +204,10 @@ class KeybindsOptions extends MusicBeatSubstate { } add(alphabets); add(camFollow); + + FlxG.sound.volumeUpKeys = []; + FlxG.sound.volumeDownKeys = []; + FlxG.sound.muteKeys = []; } public override function destroy() { @@ -181,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]; @@ -200,11 +238,11 @@ 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(); @@ -251,7 +289,7 @@ 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, CoolUtil.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); } @@ -261,7 +299,7 @@ class KeybindsOptions extends MusicBeatSubstate { var customCategories:Array<ControlsCategory> = []; var xmlPath = Paths.xml("config/controls"); - for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.AssetSource.MODS]) { + 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 { @@ -311,4 +349,5 @@ typedef KeybindSettingData = { 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 55636b7a..885d0335 100644 --- a/source/funkin/options/type/ArrayOption.hx +++ b/source/funkin/options/type/ArrayOption.hx @@ -18,6 +18,8 @@ class ArrayOption extends OptionType { public var parent:Dynamic; + private var rawText(default, set):String; + public var text(get, set):String; private function get_text() {return __text.text;} private function set_text(v:String) {return __text.text = v;} @@ -38,8 +40,21 @@ class ArrayOption extends OptionType { this.optionName = optionName; - add(__text = new Alphabet(100, 20, text, true)); - add(__selectiontext = new Alphabet(__text.width + 120, -30, formatTextOption(), false)); + add(__text = new Alphabet(100, 20, text, "bold")); + add(__selectiontext = new Alphabet(__text.width + 120, 20, formatTextOption(), "normal")); + + rawText = text; + } + + override function reloadStrings() { + super.reloadStrings(); + this.rawText = rawText; + } + + function set_rawText(v:String) { + rawText = v; + __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText; + return v; } public override function draw() { diff --git a/source/funkin/options/type/Checkbox.hx b/source/funkin/options/type/Checkbox.hx index b7480ccd..77947bd7 100644 --- a/source/funkin/options/type/Checkbox.hx +++ b/source/funkin/options/type/Checkbox.hx @@ -6,23 +6,19 @@ import flixel.math.FlxPoint; * Option type that allows you to toggle a checkbox. **/ class Checkbox extends TextOption { - public var checkbox:FlxSprite; - public var checked:Bool; + public var checked(default, set):Bool; public var parent:Dynamic; - private function get_selected() { - - } public var optionName:String; private var offsets:Map<String, FlxPoint> = [ - "checked" => FlxPoint.get(1, -1), - "unchecked" => FlxPoint.get(-12, -72), - "checking" => FlxPoint.get(12, 30) + "unchecked" => FlxPoint.get(0, -70), + "checked" => FlxPoint.get(23, -30), + "unchecking" => FlxPoint.get(25, -12), + "checking" => FlxPoint.get(35, 31) ]; - private var baseCheckboxOffset:FlxPoint = FlxPoint.get(); public function new(text:String, desc:String, optionName:String, ?parent:Dynamic) { super(text, desc, null); @@ -34,40 +30,72 @@ class Checkbox extends TextOption { checkbox = new FlxSprite(10, -40); checkbox.frames = Paths.getFrames('menus/options/checkboxThingie'); - checkbox.animation.addByPrefix("checked", "Check Box Selected Static", 24); - checkbox.animation.addByPrefix("unchecked", "Check Box unselected", 24); - checkbox.animation.addByPrefix("checking", "Check Box selecting animation", 24, false); + checkbox.animation.addByPrefix("unchecked", "Check Box unselected0", 24); + checkbox.animation.addByPrefix("checked", "Check Box Selected Static0", 24); + checkbox.animation.addByPrefix("unchecking", "Check Box deselect animation0", 24, false); + checkbox.animation.addByPrefix("checking", "Check Box selecting animation0", 24, false); checkbox.antialiasing = true; checkbox.scale.set(0.75, 0.75); checkbox.updateHitbox(); add(checkbox); - baseCheckboxOffset.set(checkbox.offset.x, checkbox.offset.y); - this.optionName = optionName; - checked = Reflect.field(parent, optionName); + if(optionName != null) { + checked = Reflect.field(parent, optionName); + } else { + checked = false; + } } + public var firstFrame:Bool = true; + public override function update(elapsed:Float) { - if (checkbox.animation.curAnim == null || (checkbox.animation.curAnim.finished || (checkbox.animation.curAnim.reversed && checkbox.animation.curAnim.curFrame <= 0))) { + if(checkbox.animation.curAnim == null) { checkbox.animation.play(checked ? "checked" : "unchecked", true); } super.update(elapsed); - var offset = offsets[checkbox.animation.curAnim.name]; - if (offset != null) - checkbox.offset.set( - (offset.x * checkbox.scale.x) + baseCheckboxOffset.x, - (offset.y * checkbox.scale.y) + baseCheckboxOffset.y); + switch(checkbox.animation.curAnim.name) { + case "unchecking": + if(checkbox.animation.curAnim.finished) { + checkbox.animation.play("unchecked", true); + } + case "checking": + if(checkbox.animation.curAnim.finished) { + checkbox.animation.play("checked", true); + } + } + + firstFrame = false; + } + + override function draw() { + if(checkbox.animation.curAnim != null) { + var offset = offsets[checkbox.animation.curAnim.name]; + if (offset != null) + checkbox.frameOffset.set(offset.x, offset.y); + } + super.draw(); + } + + function set_checked(v:Bool) { + if(checked != v) { + checked = v; + if(!firstFrame) { + checkbox.animation.play(checked ? "checking" : "unchecking", true); + } + } + return v; } public override function onSelect() { - Reflect.setField(parent, optionName, checked = !checked); - checkbox.animation.play("checking", true, !checked); + checked = !checked; + if(optionName != null) { + Reflect.setField(parent, optionName, checked); + } } public override function destroy() { super.destroy(); for(e in offsets) e.put(); - baseCheckboxOffset.put(); } } \ No newline at end of file diff --git a/source/funkin/options/type/GithubIconOption.hx b/source/funkin/options/type/GithubIconOption.hx index 15a063cd..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; @@ -65,7 +79,7 @@ class GithubUserIcon extends FlxSprite var bytes = null; if(unfLink) { try bytes = HttpUtil.requestBytes('${user.avatar_url}?size=$size') - catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)} - (Retrying using the api..)', RED)], ERROR); + catch(e) Logs.error('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)} - (Retrying using the api..)'); if(bytes != null) { bmap = BitmapData.fromBytes(bytes); @@ -74,27 +88,27 @@ class GithubUserIcon extends FlxSprite } if(planB) { - if(unfLink) user = cast GitHub.getUser(user.login, function(e) Logs.traceColored([Logs.logText('Failed to download github user info for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR)); // Api part - Nex + if(unfLink) user = cast GitHub.getUser(user.login, function(e) Logs.error('Failed to download github user info for ${user.login}: ${CoolUtil.removeIP(e.message)}')); // Api part - Nex try bytes = HttpUtil.requestBytes('${user.avatar_url}&size=$size') - catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR); + catch(e) Logs.error('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)}'); if(bytes != null) bmap = BitmapData.fromBytes(bytes); } 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.traceColored([Logs.logText('Failed to update the pfp for ${user.login}: ${e.message}', RED)], ERROR); + 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/MeterOption.hx b/source/funkin/options/type/MeterOption.hx new file mode 100644 index 00000000..7902e09e --- /dev/null +++ b/source/funkin/options/type/MeterOption.hx @@ -0,0 +1,208 @@ +package funkin.options.type; + +// TODO: RECODE ALL OF THISS!! ITS SO SHITTY + +import flixel.graphics.frames.FlxFrame; +import flixel.math.FlxMatrix; +import flixel.math.FlxRect; + +@:access(flixel.FlxSprite._frame) +class MeterOption extends OptionType { + public var selectCallback:Float->Void; + + public var min:Float; + public var max:Float; + + public var currentSelection:Float; + public var changeVal:Float; + + public var parent:Dynamic; + + public var barWidth(default, set):Int; + + private var rawText(default, set):String; + + public var text(get, set):String; + private function get_text() {return __text.text;} + private function set_text(v:String) {return __text.text = v;} + + private var __text:Alphabet; + var optionName:String; + + var barScale:Float = 0.75; + var realBarWidth:Float; + var cornerWidth:Float; + var slider:FlxSprite; + var cornerEmpty:FlxSprite; + var barEmpty:FlxSprite; + var cornerFill:FlxSprite; + var barFill:FlxSprite; + var valueWidth:Float; + var cornerClipRect:FlxRect; + + public function new(text:String, desc:String, min:Float, max:Float, changeVal:Float, optionName:String, barWidth:Int = 520, + ?selectCallback:Float->Void = null, ?parent:Dynamic) + { + super(desc); + + this.selectCallback = selectCallback; + this.min = min; + this.max = max; + if (parent == null) parent = Options; + + this.parent = parent; + + if (Reflect.field(parent, optionName) != null) this.currentSelection = Reflect.field(parent, optionName); + this.changeVal = changeVal; + this.optionName = optionName; + + add(__text = new Alphabet(20, 20, "", "bold")); + rawText = text; + + var frames = Paths.getFrames('menus/options/meter'); + add(slider = new FlxSprite(0, 24)); + slider.frames = frames; + slider.animation.addByPrefix('slider', 'meter slider', 24); + slider.animation.play('slider'); + slider.antialiasing = true; + slider.scale.set(barScale, barScale); + slider.updateHitbox(); + + (cornerEmpty = new FlxSprite()).frames = (barEmpty = new FlxSprite()).frames = frames; + (cornerFill = new FlxSprite()).frames = (barFill = new FlxSprite()).frames = frames; + cornerEmpty.animation.addByPrefix('corner', 'meter empty corner', 24); + cornerEmpty.animation.play('corner'); + barEmpty.animation.addByPrefix('bar', 'meter empty piece', 24); + barEmpty.animation.play('bar'); + cornerFill.animation.addByPrefix('corner', 'meter filled corner', 24); + cornerFill.animation.play('corner'); + barFill.animation.addByPrefix('bar', 'meter filled piece', 24); + barFill.animation.play('bar'); + + cornerClipRect = new FlxRect(); + cornerEmpty.rawClipRect = cornerClipRect; + cornerFill.rawClipRect = cornerClipRect; + + _matrix = new FlxMatrix(); + cornerWidth = Math.max(cornerFill.frameWidth, cornerEmpty.frameWidth); + this.barWidth = barWidth; + } + + function set_barWidth(value:Int):Int { + if (value == barWidth) return value; + realBarWidth = Math.max(cornerWidth + value / barScale, cornerWidth * 2); + updateBar(); + + __text.x = x + 20 + realBarWidth * barScale; + + return barWidth = value; + } + + function updateBar() { + var w = (currentSelection - min) / (max - min) * (realBarWidth - cornerWidth * 2) + cornerWidth; + slider.x = x + w * barScale - slider.width * 0.5; + valueWidth = w; + } + + inline function drawObject(sprite:FlxSprite, camera:FlxCamera, x:Float, y:Float) { + getScreenPosition(_point, camera).subtractPoint(offset); + _matrix.translate(_point.x + x, _point.y + y); + + /*if (isPixelPerfectRender(camera)) { + _matrix.tx = Math.floor(_matrix.tx); + _matrix.ty = Math.floor(_matrix.ty); + }*/ + + if (layer != null) layer.drawPixels(sprite, camera, sprite._frame, sprite.framePixels, _matrix, colorTransform, blend, true); + else camera.drawPixels(sprite._frame, sprite.framePixels, _matrix, colorTransform, blend, true); + } + + var lastX:Float; + var temp:Float; + function drawCorner(width:Float, flip:Bool, camera:FlxCamera) { + if ((temp = Math.max(Math.min(width, cornerFill.frameWidth), 0)) > 0) { + cornerClipRect.set(0, 0, temp, cornerFill.frameHeight); + cornerFill.frame = cornerFill.frames.frames[cornerFill.animation.frameIndex]; + cornerFill._frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, flip != camera.flipX, flipY != camera.flipY); + _matrix.scale(barScale, barScale); + drawObject(cornerFill, camera, 0, 37); + } + + if (temp < cornerEmpty.frameWidth) { + cornerClipRect.set(temp, 0, cornerEmpty.frameWidth - temp, cornerEmpty.frameHeight); + cornerEmpty.frame = cornerEmpty.frames.frames[cornerEmpty.animation.frameIndex]; + cornerEmpty._frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, flip != camera.flipX, flipY != camera.flipY); + _matrix.scale(barScale, barScale); + drawObject(cornerEmpty, camera, temp * barScale, 37); + } + + x += cornerWidth * barScale; + } + + function drawBar(width:Float, camera:FlxCamera) { + temp = realBarWidth - cornerWidth * 2; + if (width > 0) { + barFill._frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, camera.flipX, flipY != camera.flipY); + _matrix.scale((Math.min(width, temp) + 2) / barFill.frameWidth * barScale, barScale); + drawObject(barFill, camera, -1, 37); + } + + if (width < temp) { + barEmpty._frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, camera.flipX, flipY != camera.flipY); + _matrix.scale((Math.max(temp - width, 0) + 1) / barEmpty.frameWidth * barScale, barScale); + drawObject(barEmpty, camera, width * barScale, 37); + } + + /*if (width < (temp = realBarWidth - cornerWidth * 2)) { + barEmpty._frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, camera.flipX, flipY != camera.flipY); + _matrix.scale((width - realBarWidth + cornerWidth + 1) / barFill.frameWidth, 1); + drawObject(barEmpty, camera, realBarWidth - width, 37); + }*/ + + x += temp * barScale; + } + + function set_rawText(v:String) { + rawText = v; + __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText; + return v; + } + + override function draw() { + lastX = x; + + for (camera in cameras) { + if (!camera.visible || !camera.exists) continue; + + drawCorner(currentSelection == min ? 0 : valueWidth, false, camera); + drawBar(valueWidth - cornerWidth, camera); + drawCorner(currentSelection == max ? cornerWidth : valueWidth - realBarWidth + cornerWidth, true, camera); + + x = lastX; + } + super.draw(); + } + + override function destroy() { + super.destroy(); + cornerEmpty.destroy(); + barEmpty.destroy(); + cornerFill.destroy(); + barFill.destroy(); + } + + override function reloadStrings() { + super.reloadStrings(); + this.rawText = rawText; + } + + override function onChangeSelection(change:Float) { + if (currentSelection <= min && change == -1 || currentSelection >= max && change == 1) return; + currentSelection = FlxMath.roundDecimal(currentSelection + (change * changeVal), FlxMath.getDecimals(changeVal)); + + updateBar(); + + Reflect.setField(parent, optionName, currentSelection); + if (selectCallback != null) selectCallback(currentSelection); + } +} \ No newline at end of file diff --git a/source/funkin/options/type/NewOption.hx b/source/funkin/options/type/NewOption.hx index 0008566e..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. diff --git a/source/funkin/options/type/NumOption.hx b/source/funkin/options/type/NumOption.hx index 77aa3af2..e570e197 100644 --- a/source/funkin/options/type/NumOption.hx +++ b/source/funkin/options/type/NumOption.hx @@ -21,6 +21,8 @@ class NumOption extends OptionType { public var parent:Dynamic; + private var rawText(default, set):String; + public var text(get, set):String; private function get_text() {return __text.text;} private function set_text(v:String) {return __text.text = v;} @@ -40,11 +42,24 @@ class NumOption extends OptionType { this.changeVal = changeVal; this.optionName = optionName; - add(__text = new Alphabet(100, 20, text, true)); - add(__number = new Alphabet(__text.width + 120, -30, ': $currentSelection', false)); + add(__text = new Alphabet(20, 20, "", "bold")); + add(__number = new Alphabet(0, 20, ': $currentSelection', "bold")); + rawText = text; } - public override function onChangeSelection(change:Float):Void + override function reloadStrings() { + super.reloadStrings(); + this.rawText = rawText; + } + + function set_rawText(v:String) { + rawText = v; + __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText; + __number.x = __text.x + __text.width + 12; + return v; + } + + override function onChangeSelection(change:Float):Void { if(currentSelection <= min && change == -1 || currentSelection >= max && change == 1) return; currentSelection = FlxMath.roundDecimal(currentSelection + (change * changeVal), FlxMath.getDecimals(changeVal)); diff --git a/source/funkin/options/type/OptionType.hx b/source/funkin/options/type/OptionType.hx index 59d07e3b..44cd94e1 100644 --- a/source/funkin/options/type/OptionType.hx +++ b/source/funkin/options/type/OptionType.hx @@ -11,10 +11,21 @@ class OptionType extends FlxSpriteGroup { public var controls(get, never):Controls; public var selected:Bool = false; public var desc:String; + public var rawDesc(default, set):String; public function new(desc:String) { super(); - this.desc = desc; + rawDesc = desc; + } + + function set_rawDesc(v:String) { + rawDesc = v; + this.desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc; + return v; + } + + public function reloadStrings() { + this.rawDesc = rawDesc; } private function get_controls() {return PlayerSettings.solo.controls;} diff --git a/source/funkin/options/type/RadioButton.hx b/source/funkin/options/type/RadioButton.hx new file mode 100644 index 00000000..d43240ac --- /dev/null +++ b/source/funkin/options/type/RadioButton.hx @@ -0,0 +1,121 @@ +package funkin.options.type; + +import flixel.math.FlxPoint; + +class RadioButton extends TextOption { + public var radio:FlxSprite; + public var checked(default, set):Bool; + + public var parent:Dynamic; + public var value:Dynamic; + public var forId:String; + + public var optionName:String; + + private var offsets:Map<String, FlxPoint> = [ + "unchecked" => FlxPoint.get(0, -65), + "checked" => FlxPoint.get(0, -65), + "unchecking" => FlxPoint.get(15, -55), + "checking" => FlxPoint.get(17, -40) + ]; + + public function new(text:String, desc:String, optionName:String, value:Dynamic, ?parent:Dynamic, ?forId:String, ?options:OptionsScreen) { + super(text, desc, null); + + if (parent == null) + parent = Options; + + if(forId == null) + forId = optionName; + + this.parent = parent; + this.forId = forId; + + if(options != null) { + if(FlxG.state is OptionsMenu) { + var menu:OptionsMenu = cast FlxG.state; + options = menu.optionsTree.members.last(); + } + } + + radio = new FlxSprite(10, -40); + radio.frames = Paths.getFrames('menus/options/radioCrank'); + radio.animation.addByPrefix("unchecked", "Radio unselected0", 24); + radio.animation.addByPrefix("checked", "Radio Selected Static0", 24); + radio.animation.addByPrefix("unchecking", "Radio deselect animation0", 24, false); + radio.animation.addByPrefix("checking", "Radio selecting animation0", 24, false); + radio.antialiasing = true; + radio.scale.set(0.75, 0.75); + radio.updateHitbox(); + add(radio); + + this.value = value; + + this.optionName = optionName; + if(optionName != null) { + checked = Reflect.field(parent, optionName) == value; + } else { + checked = false; + } + } + + public var firstFrame:Bool = true; + + public override function update(elapsed:Float) { + if(radio.animation.curAnim == null) { + radio.animation.play(checked ? "checked" : "unchecked", true); + } + super.update(elapsed); + switch(radio.animation.curAnim.name) { + case "unchecking": + if(radio.animation.curAnim.finished) { + radio.animation.play("unchecked", true); + trace("UNCHECKING finished"); + } + case "checking": + if(radio.animation.curAnim.finished) { + radio.animation.play("checked", true); + trace("CHECKING finished"); + } + } + + firstFrame = false; + } + + override function draw() { + if(radio.animation.curAnim != null) { + var offset = offsets[radio.animation.curAnim.name]; + if (offset != null) + radio.frameOffset.set(offset.x, offset.y); + } + super.draw(); + } + + function set_checked(v:Bool) { + if(checked != v) { + checked = v; + if(!firstFrame) { + radio.animation.play(checked ? "checking" : "unchecking", true); + } + } + return v; + } + + public override function onSelect() { + checked = true; + if(checked) { + onSet(value); + } + } + + public dynamic function onSet(value:Dynamic) { + if(optionName != null) { + Reflect.setField(parent, optionName, value); + } + } + + public override function destroy() { + super.destroy(); + for(e in offsets) e.put(); + } +} \ No newline at end of file diff --git a/source/funkin/options/type/TextOption.hx b/source/funkin/options/type/TextOption.hx index 31d44508..2d2b5187 100644 --- a/source/funkin/options/type/TextOption.hx +++ b/source/funkin/options/type/TextOption.hx @@ -9,15 +9,34 @@ class TextOption extends OptionType { public var selectCallback:Void->Void; private var __text:Alphabet; + private var rawText(default, set):String; public var text(get, set):String; private function get_text() {return __text.text;} private function set_text(v:String) {return __text.text = v;} - public function new(text:String, desc:String, selectCallback:Void->Void) { + /** + * If you change this afterwards you will need to call `reloadStrings()`, or manually set rawText to rawText. + **/ + public var suffix:String; + + public function new(text:String, desc:String, ?suffix:String = "", ?selectCallback:Void->Void = null) { super(desc); this.selectCallback = selectCallback; - add(__text = new Alphabet(100, 20, text, true)); + add(__text = new Alphabet(100, 20, "", "bold")); + this.suffix = suffix; + rawText = text; + } + + override function reloadStrings() { + super.reloadStrings(); + this.rawText = rawText; + } + + function set_rawText(v:String) { + rawText = v; + __text.text = (TU.exists(rawText) ? TU.translate(rawText) : rawText) + suffix; + return v; } public override function draw() { diff --git a/source/funkin/savedata/FunkinSave.hx b/source/funkin/savedata/FunkinSave.hx index b48811ee..dca3701c 100644 --- a/source/funkin/savedata/FunkinSave.hx +++ b/source/funkin/savedata/FunkinSave.hx @@ -32,7 +32,12 @@ class FunkinSave { if (!__eventAdded) { Lib.application.onExit.add(function(i:Int) { - trace("Saving save data..."); + Logs.traceColored([ + Logs.getPrefix("FunkinSave"), + Logs.logText("Saving "), + Logs.logText("save data", GREEN), + Logs.logText("...") + ], VERBOSE); flush(); }); __eventAdded = true; 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/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..aba05b8b --- /dev/null +++ b/source/lime/media/AudioBuffer.hx @@ -0,0 +1,497 @@ +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 lime_vorbis // CNE + if (funkin.options.Options.streamedMusic) { + var vorbisFile = VorbisFile.fromBytes(bytes); + if (vorbisFile != null) return fromVorbisFile(vorbisFile); + } + #end + #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/system/System.hx b/source/lime/system/System.hx index 64f8c771..2184f5fe 100644 --- a/source/lime/system/System.hx +++ b/source/lime/system/System.hx @@ -605,9 +605,7 @@ class System { // executes first!! #if (sys && !macro) - funkin.backend.utils.NativeAPI.registerAsDPICompatible(); - funkin.backend.system.CommandLineHandler.parseCommandLine(Sys.args()); - funkin.backend.system.Main.fixWorkingDirectory(); + funkin.backend.system.Main.preInit(); #end if (__applicationEntryPoint == null) 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/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); } /**