mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-21 01:19:26 +00:00
Merge branch 'rewrite/master' into feature/pico-playable-christmas
This commit is contained in:
commit
b27aa4c94d
4
.gitattributes
vendored
4
.gitattributes
vendored
|
@ -1 +1,3 @@
|
|||
* text=auto eol=lf
|
||||
* text=auto eol=lf
|
||||
*.hxc linguist-language=Haxe
|
||||
*.hxp linguist-language=Haxe
|
||||
|
|
21
.github/actions/setup-haxe/action.yml
vendored
21
.github/actions/setup-haxe/action.yml
vendored
|
@ -44,7 +44,7 @@ runs:
|
|||
g++ \
|
||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||
libgl-dev libgl1-mesa-dev \
|
||||
libasound2-dev
|
||||
libasound2-dev libpulse-dev
|
||||
ln -s /usr/lib/x86_64-linux-gnu/libffi.so.8 /usr/lib/x86_64-linux-gnu/libffi.so.6 || true
|
||||
- name: Install linux-specific dependencies
|
||||
if: ${{ runner.os == 'Linux' && contains(inputs.targets, 'linux') }}
|
||||
|
@ -56,12 +56,17 @@ runs:
|
|||
shell: bash
|
||||
run: |
|
||||
echo "TIMER_HAXELIB=$(date +%s)" >> "$GITHUB_ENV"
|
||||
haxelib --debug --never install haxelib 4.1.0 --global
|
||||
haxelib --debug --never deleterepo || true
|
||||
haxelib fixrepo --global || true
|
||||
haxelib --debug --never --global install haxelib 4.1.0
|
||||
haxelib --debug --global set haxelib 4.1.0
|
||||
haxelib --global remove haxelib git || true
|
||||
haxelib --global remove hmm || true
|
||||
rm -rf .haxelib
|
||||
haxelib --debug --never --global git haxelib https://github.com/FunkinCrew/haxelib.git funkin-patches --skip-dependencies
|
||||
haxelib --debug --never --global git hmm https://github.com/FunkinCrew/hmm funkin-patches
|
||||
haxelib --debug --never newrepo
|
||||
haxelib version
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
haxelib --debug --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib --debug --global install hmm
|
||||
echo "TIMER_DEPS=$(date +%s)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore cached dependencies
|
||||
|
@ -75,7 +80,11 @@ runs:
|
|||
name: Prep git for dependency install
|
||||
uses: gacts/run-and-post-run@v1
|
||||
with:
|
||||
run: git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
|
||||
run: |
|
||||
git config --global --name-only --get-regexp 'url\.https\:\/\/x-access-token:.+@github\.com\/\.insteadOf' \
|
||||
| xargs git config --global --unset
|
||||
git config -l --show-scope --show-origin
|
||||
git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
|
||||
post: git config --global --unset 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf'
|
||||
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
|
|
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*
|
||||
- '**/*.md'
|
||||
|
|
8
.github/workflows/build-game.yml
vendored
8
.github/workflows/build-game.yml
vendored
|
@ -45,7 +45,11 @@ jobs:
|
|||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
gh-token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
- name: Setup HXCPP dev commit
|
||||
run: |
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Build game
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
|
@ -107,7 +111,9 @@ jobs:
|
|||
name: Install dependencies
|
||||
run: |
|
||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||
git config --global advice.detachedHead false
|
||||
haxelib --global run hmm install -q
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||
|
||||
- if: ${{ matrix.target != 'html5' }}
|
||||
name: Restore hxcpp cache
|
||||
|
|
30
.vscode/settings.json
vendored
30
.vscode/settings.json
vendored
|
@ -94,12 +94,12 @@
|
|||
{
|
||||
"label": "Windows / Debug",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Linux / Debug",
|
||||
"target": "linux",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug",
|
||||
|
@ -109,7 +109,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (FlxAnimate Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMATE", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DANIMATE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (FlxAnimate Test)",
|
||||
|
@ -119,7 +119,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Straight to Freeplay)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFREEPLAY", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFREEPLAY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Freeplay)",
|
||||
|
@ -132,13 +132,13 @@
|
|||
"args": [
|
||||
"-debug",
|
||||
"-DSONG=bopeebo -DDIFFICULTY=normal",
|
||||
"-DFORCE_DEBUG_VERSION"
|
||||
"-DFEATURE_DEBUG_FUNCTIONS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Play - 2hot)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSONG=2hot", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DSONG=2hot", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
||||
|
@ -148,7 +148,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Conversation Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DDIALOGUE", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DDIALOGUE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Conversation Test)",
|
||||
|
@ -163,7 +163,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DCHARTING", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Chart Editor)",
|
||||
|
@ -173,12 +173,12 @@
|
|||
{
|
||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Debug hxCodec)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||
|
@ -188,7 +188,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Latency Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DLATENCY", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DLATENCY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Latency Test)",
|
||||
|
@ -198,7 +198,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Waveform Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release",
|
||||
|
@ -218,17 +218,17 @@
|
|||
{
|
||||
"label": "HTML5 / Debug",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug (Watch)",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Debug",
|
||||
"target": "mac",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Release",
|
||||
|
|
56
CHANGELOG.md
56
CHANGELOG.md
|
@ -4,6 +4,62 @@ All notable changes will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.0] - 2024-08-??
|
||||
### Added
|
||||
- Added a new Character Select screen to switch between playable characters in Freeplay
|
||||
- Modding isn't 100% there but we're working on it!
|
||||
- Added Pico as a playable character! Unlock him by completing Weekend 1 (if you haven't already done that)
|
||||
- The songs from Weekend 1 have moved; you must now switch to Pico in Freeplay to access them
|
||||
- Added ## new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||
- Added 2 new Erect remixes! Access them by switching difficulty on the song
|
||||
- Implemented support for a new Instrumental Selector in Freeplay
|
||||
- Beating a Pico remix lets you use that instrumental when playing as Boyfriend
|
||||
- Added the first batch of Erect Stages! These graphical overhauls of the original stages will be used when playing Erect remixes and Pico remixes
|
||||
- Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1)
|
||||
- Implemented a new Strumline Background option, to display a darkened background behind the strumline with your choice of opacity.
|
||||
- Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand)
|
||||
## Changed
|
||||
- Girlfriend and Nene now perform previously unused animations when you achieve a large combo, or drop a large combo.
|
||||
- The pixel character icons in the Freeplay menu now display an animation!
|
||||
- Altered how Week 6 displays sprites to make things look more retro.
|
||||
- Character offsets are now independent of the character's scale.
|
||||
- This should resolve issues with offsets when porting characters from older mods.
|
||||
- Pixel character offsets have been modified to compensate.
|
||||
- Note style data can now specify custom combo count graphics, judgement graphics, countdown graphics, and countdown audio. (community feature by anysad)
|
||||
- These were previously using hardcoded values based on whether the stage was `school` or `schoolEvil`.
|
||||
- The `danceEvery` property of characters and stage props can now use values with a precision of `0.25`, to play their idle animation up to four times per beat.
|
||||
- Reworked the JSON merging system in Polymod; you can now include JSONPatch files under `_merge` in your mod folder to add, modify, or remove values in a JSON without replacing it entirely!
|
||||
- Cutscenes now automatically pause when tabbing out (community fix by AbnormalPoof)
|
||||
- Characters will now respect the `danceEvery` property (community fix by gamerbross)
|
||||
- The F5 function now reloads the current song's chart data from disc (community feature by gamerbross)
|
||||
- Refactored the compilation guide and added common troubleshooting steps (community fix by Hundrec)
|
||||
- Made several layout improvements and fixes to the Animation Offsets editor in the Debug menu (community fix by gamerbross)
|
||||
- Fixed a bug where the Back sound would be not played when leaving the Story menu and Options menu (community fix by AppleHair)
|
||||
- Animation offsets no longer directly modify the `x` and `y` position of props, which makes props work better with tweens (community fix by Sword352)
|
||||
- The YEAH! events in Tutorial now use chart events rather than being hard-coded (community fix by anysad)
|
||||
- The player's Score now displays commas in it (community fix by loggo)
|
||||
## Fixed
|
||||
- Fixed an issue where songs with no notes would crash on the Results screen.
|
||||
- Fixed an issue where the old icon easter egg would not work properly on pixel levels.
|
||||
- Fixed an issue where you could play notes during the Thorns cutscene.
|
||||
- Fixed an issue where the Heart icon when favoriting a song in Freeplay would be malformed.
|
||||
- Fixed an issue where Pico's death animation displays a faint blue background (community fix by doggogit)
|
||||
- Fixed an issue where mod songs would not play a preview in the Freeplay menu (community fix by KarimAkra)
|
||||
- Fixed an issue where the Memory Usage counter could overflow and display a negative number (community fix by KarimAkra)
|
||||
- Fixed an issue where pressing the Chart Editor keybind while playtesting a chart would reset the chart editor (community fix by gamerbross)
|
||||
- Fixed a crash bug when pressing F5 after seeing the sticker transition (community fix by gamerbross)
|
||||
- Fixed an issue where the Story Mode menu couldn't be scrolled with a mouse (community fix by JVNpixels)
|
||||
- Fixed an issue causing the song to majorly desync sometimes (community fix by Burgerballs)
|
||||
- Fixed an issue where the Freeplay song preview would not respect the instrumental ID specified in the song metadata (community fix by AppleHair)
|
||||
- Fixed an issue where Tankman's icon wouldn't display in the Chart Editor (community fix by hundrec)
|
||||
- Fixed an issue where pausing the game during a camera zoom would zoom the pause menu. (community fix by gamerbros)
|
||||
- Fixed an issue where certain UI elements would not flash at a consistent rate (community fix by cyn0x8)
|
||||
- Fixed an issue where the game would not use the placeholder health icon as a fallback (community fix by gamerbross)
|
||||
- Fixed an issue where the chart editor could get stuck creating a hold note when using Live Inputs (community fix by gamerbross)
|
||||
- Fixed an issue where character graphics could not be placed in week folders (community fix by 7oltan)
|
||||
- Fixed a crash issue when a Freeplay song has no `Normal` difficulty (community fix by Applehair and gamerbross)
|
||||
- Fixed an issue in Story Mode where a song that isn't valid for the current variation could be selected (community fix by Applehair)
|
||||
|
||||
## [0.4.1] - 2024-06-12
|
||||
### Added
|
||||
- Pressing ESCAPE on the title screen on desktop now exits the game, allowing you to exit the game while in fullscreen on desktop
|
||||
|
|
269
Project.xml
269
Project.xml
|
@ -1,269 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd">
|
||||
<!-- _________________________ Application Settings _________________________ -->
|
||||
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.5.0" company="ninjamuffin99" />
|
||||
<!--Switch Export with Unique ApplicationID and Icon-->
|
||||
<set name="APP_ID" value="0x0100f6c013bbc000" />
|
||||
|
||||
<!--
|
||||
Define the OpenFL sprite which displays the preloader.
|
||||
You can't replace the preloader's logic here, sadly, but you can extend it.
|
||||
Basic preloading logic is done by `openfl.display.Preloader`.
|
||||
-->
|
||||
<app preloader="funkin.ui.transition.preload.FunkinPreloader" />
|
||||
|
||||
<!--Minimum without FLX_NO_GAMEPAD: 11.8, without FLX_NO_NATIVE_CURSOR: 11.2-->
|
||||
<set name="SWF_VERSION" value="11.8" />
|
||||
<!-- ____________________________ Window Settings ___________________________ -->
|
||||
<!--These window settings apply to all targets-->
|
||||
<window width="1280" height="720" fps="60" background="#000000" hardware="true" vsync="false" />
|
||||
<!--HTML5-specific-->
|
||||
<window if="html5" resizable="true" />
|
||||
<!--Desktop-specific-->
|
||||
<window if="desktop" orientation="landscape" fullscreen="false" resizable="true" vsync="false" />
|
||||
<!--Mobile-specific-->
|
||||
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false" />
|
||||
<!-- _____________________________ Path Settings ____________________________ -->
|
||||
|
||||
<set name="BUILD_DIR" value="export/debug" if="debug" />
|
||||
<set name="BUILD_DIR" value="export/release" unless="debug" />
|
||||
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
|
||||
<source path="source" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.mp3|*.wav" unless="web" />
|
||||
<define name="PRELOAD_ALL" unless="web" />
|
||||
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL" />
|
||||
<section if="PRELOAD_ALL">
|
||||
<library name="songs" preload="true" />
|
||||
<library name="shared" preload="true" />
|
||||
<library name="tutorial" preload="true" />
|
||||
<library name="week1" preload="true" />
|
||||
<library name="week2" preload="true" />
|
||||
<library name="week3" preload="true" />
|
||||
<library name="week4" preload="true" />
|
||||
<library name="week5" preload="true" />
|
||||
<library name="week6" preload="true" />
|
||||
<library name="week7" preload="true" />
|
||||
<library name="weekend1" preload="true" />
|
||||
<library name="videos" preload="true" />
|
||||
</section>
|
||||
<section if="NO_PRELOAD_ALL">
|
||||
<library name="songs" preload="false" />
|
||||
<library name="shared" preload="false" />
|
||||
<library name="tutorial" preload="false" />
|
||||
<library name="week1" preload="false" />
|
||||
<library name="week2" preload="false" />
|
||||
<library name="week3" preload="false" />
|
||||
<library name="week4" preload="false" />
|
||||
<library name="week5" preload="false" />
|
||||
<library name="week6" preload="false" />
|
||||
<library name="week7" preload="false" />
|
||||
<library name="weekend1" preload="false" />
|
||||
<library name="videos" preload="false" />
|
||||
</section>
|
||||
<library name="art" preload="false" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<!-- Videos go in their own library because web never needs to preload them, they can just be streamed. -->
|
||||
<assets path="assets/videos" library="videos" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
||||
<!--
|
||||
AUTOMATICALLY MOVING EXAMPLE MODS INTO THE BUILD CAUSES ISSUES
|
||||
Currently, this line will add the mod files to the library manifest,
|
||||
which causes issues if the mod is not enabled.
|
||||
If we can exclude the `mods` folder from the manifest, we can re-enable this line.
|
||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md" />
|
||||
-->
|
||||
<assets path="art/readme.txt" rename="do NOT readme.txt" library="art"/>
|
||||
<assets path="CHANGELOG.md" rename="changelog.txt" library="art"/>
|
||||
<!-- NOTE FOR FUTURE SELF SINCE FONTS ARE ALWAYS FUCKY
|
||||
TO FIX ONE OF THEM, I CONVERTED IT TO OTF. DUNNO IF YOU NEED TO
|
||||
THEN UHHH I USED THE NAME OF THE FONT WITH SETFORMAT() ON THE TEXT!!!
|
||||
NOT USING A DIRECT THING TO THE ASSET!!!
|
||||
-->
|
||||
<assets path="assets/fonts" embed="true" />
|
||||
|
||||
<!-- If compiled via github actions, show debug version number. -->
|
||||
<define name="FORCE_DEBUG_VERSION" if="GITHUB_BUILD" />
|
||||
<define name="NO_REDIRECT_ASSETS_FOLDER" if="GITHUB_BUILD" />
|
||||
<define name="TOUCH_HERE_TO_PLAY" if="web" />
|
||||
|
||||
<!-- _______________________________ Libraries ______________________________ -->
|
||||
<haxelib name="lime" /> <!-- Game engine backend -->
|
||||
<haxelib name="openfl" /> <!-- Game engine backend -->
|
||||
<haxelib name="flixel" /> <!-- Game engine -->
|
||||
|
||||
<haxedev set="webgl" />
|
||||
|
||||
<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
|
||||
<haxelib name="hscript" /> <!-- Scripting -->
|
||||
<haxelib name="flixel-ui" /> <!-- UI framework (DEPRECATED) -->
|
||||
<haxelib name="haxeui-core" /> <!-- UI framework -->
|
||||
<haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel -->
|
||||
<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
|
||||
<haxelib name="polymod" /> <!-- Modding framework -->
|
||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
||||
<haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback -->
|
||||
<haxelib name="funkin.vis"/>
|
||||
<haxelib name="grig.audio" />
|
||||
|
||||
<haxelib name="FlxPartialSound" /> <!-- Loading partial sound data -->
|
||||
|
||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
||||
<haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" -->
|
||||
<haxelib name="thx.semver" /> <!-- Version string handling -->
|
||||
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support -->
|
||||
|
||||
<!--Disable the Flixel core focus lost screen-->
|
||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
||||
<!--Disable the Flixel core debugger. Automatically gets set whenever you compile in release mode!-->
|
||||
<haxedef name="FLX_NO_DEBUG" unless="debug || FORCE_DEBUG_VERSION" />
|
||||
<!--Enable this for Nape release builds for a serious peformance improvement-->
|
||||
<haxedef name="NAPE_RELEASE_BUILD" unless="debug" />
|
||||
|
||||
<!--
|
||||
Hide deprecation warnings until they're fixed.
|
||||
TODO: REMOVE THIS!!!!
|
||||
<haxeflag name="-w" value="-WDeprecated" />
|
||||
-->
|
||||
|
||||
<!-- Haxe 4.3.0+: Enable pretty syntax errors and stuff. -->
|
||||
<haxedef name="message.reporting" value="pretty" />
|
||||
|
||||
<!-- _________________________________ Custom _______________________________ -->
|
||||
<!-- Disable trace() calls in release builds to bump up performance.
|
||||
<haxeflag name="- -no-traces" unless="debug" />-->
|
||||
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
||||
<haxeflag name="-dce no" />
|
||||
<!-- Ensure all Funkin' classes are available at runtime. -->
|
||||
<haxeflag name="--macro" value="include('funkin')" />
|
||||
<!-- Ensure all UI components are available at runtime. -->
|
||||
<haxeflag name="--macro" value="include('haxe.ui.backend.flixel.components')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.dialogs')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.properties')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.core')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
||||
<!--
|
||||
Ensure additional class packages are available at runtime (some only really used by scripts).
|
||||
Ignore packages we can't include.
|
||||
-->
|
||||
<haxeflag name="--macro" value="include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])" />
|
||||
<!-- Necessary to provide stack traces for HScript. -->
|
||||
<haxedef name="hscriptPos" />
|
||||
<haxedef name="safeMode"/>
|
||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||
<haxedef name="HXCPP_STACK_LINE" />
|
||||
<haxedef name="HXCPP_STACK_TRACE" />
|
||||
<!-- This macro allows addition of new functionality to existing Flixel. -->
|
||||
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
||||
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
|
||||
<icon path="art/icon16.png" size="16" />
|
||||
<icon path="art/icon32.png" size="32" />
|
||||
<icon path="art/icon64.png" size="64" />
|
||||
<icon path="art/iconOG.png" />
|
||||
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
|
||||
<haxedef name="CAN_CHEAT" if="switch debug" />
|
||||
<!-- I don't remember what this is for. -->
|
||||
<haxedef name="haxeui_no_mouse_reset" />
|
||||
<!-- Clicking outside a dialog should deselect the current focused component. -->
|
||||
<haxedef name="haxeui_focus_out_on_click" />
|
||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
||||
<haxedef name="haxeui_dont_impose_base_class" />
|
||||
<haxedef name="HARDCODED_CREDITS" />
|
||||
|
||||
<!-- Skip the Intro -->
|
||||
<section if="debug">
|
||||
<!-- Starts the game at the specified week, at the first song -->
|
||||
<!-- <haxedef name="week" value="1" if="debug"/> -->
|
||||
<!-- Starts the game at the specified song -->
|
||||
<!-- <haxedef name="song" value="bopeebo" if="debug"/> -->
|
||||
<!-- Difficulty, only used for week or song, defaults to 1 -->
|
||||
<!-- <haxedef name="dif" value="2" if="debug"/> -->
|
||||
</section>
|
||||
<section if="newgrounds">
|
||||
<!-- Enables Ng.core.verbose -->
|
||||
<!-- <haxedef name="NG_VERBOSE" /> -->
|
||||
<!-- Enables a NG debug session, so medals don't permently unlock -->
|
||||
<!-- <haxedef name="NG_DEBUG" /> -->
|
||||
<!-- pretends that the saved session Id was expired, forcing the reconnect prompt -->
|
||||
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
|
||||
</section>
|
||||
|
||||
<!-- Uncomment this to wipe your input settings. -->
|
||||
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
|
||||
|
||||
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5 || GITHUB_BUILD">
|
||||
<!--
|
||||
Use the parent assets folder rather than the exported one
|
||||
No more will we accidentally undo our changes!
|
||||
-->
|
||||
<haxedef name="REDIRECT_ASSETS_FOLDER" />
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<!--
|
||||
This flag enables the popup/crashlog error handler.
|
||||
However, it also messes with breakpoints on some platforms.
|
||||
-->
|
||||
<haxedef name="openfl-enable-handle-error" />
|
||||
</section>
|
||||
|
||||
<!-- Run a script before and after building. -->
|
||||
<prebuild haxe="source/Prebuild.hx"/> -->
|
||||
<postbuild haxe="source/Postbuild.hx"/> -->
|
||||
|
||||
<!-- Enable this on platforms which do not support dropping files onto the window. -->
|
||||
<haxedef name="FILE_DROP_UNSUPPORTED" if="mac" />
|
||||
<section unless="FILE_DROP_UNSUPPORTED">
|
||||
<haxedef name="FILE_DROP_SUPPORTED" />
|
||||
</section>
|
||||
|
||||
<!-- Enable this on platforms which do not support the edsior views. -->
|
||||
<haxedef name="CHART_EDITOR_UNSUPPORTED" if="web" />
|
||||
<haxedef name="CHART_EDITOR_SUPPORTED" unless="web"/>
|
||||
|
||||
<!-- Options for Polymod -->
|
||||
<section if="polymod">
|
||||
<!-- Turns on additional debug logging. -->
|
||||
<haxedef name="POLYMOD_DEBUG" value="true" if="debug" />
|
||||
<!-- The file extension to use for script files. -->
|
||||
<haxedef name="POLYMOD_SCRIPT_EXT" value=".hscript" />
|
||||
<!-- Which asset library to use for scripts. -->
|
||||
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
|
||||
<!-- The base path from which scripts should be accessed. -->
|
||||
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
|
||||
<!-- Determines the subdirectory of the mod folder used for file appending. -->
|
||||
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
|
||||
<!-- Determines the subdirectory of the mod folder used for file merges. -->
|
||||
<haxedef name="POLYMOD_MERGE_FOLDER" value="_merge" />
|
||||
<!-- Determines the file in the mod folder used for metadata. -->
|
||||
<haxedef name="POLYMOD_MOD_METADATA_FILE" value="_polymod_meta.json" />
|
||||
<!-- Determines the file in the mod folder used for the icon. -->
|
||||
<haxedef name="POLYMOD_MOD_ICON_FILE" value="_polymod_icon.png" />
|
||||
</section>
|
||||
</project>
|
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit faeba700c5526bd4fd57ccc927d875c82b9d3553
|
||||
Subproject commit 0bb988c49788fd25a230b56dd9e4448838bc79c9
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit c7589a95af2709d240e1b1a2994e68a04565b00a
|
||||
Subproject commit c559b25e9a294837e6ba98397dad0ef32f325fd6
|
|
@ -83,7 +83,7 @@ apt-fast install -y --no-install-recommends \
|
|||
libc6-dev libffi-dev \
|
||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||
libgl-dev libgl1-mesa-dev \
|
||||
libasound2-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libvlc-dev libvlccore-dev
|
||||
EOF
|
||||
|
||||
|
@ -137,8 +137,8 @@ ENV PATH="$HAXEPATH:$PATH"
|
|||
RUN <<EOF
|
||||
HOME=/etc haxelib setup "$HAXEPATH/lib"
|
||||
haxelib --global --never install haxelib $haxelib_version
|
||||
haxelib --global --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib --global --never install hmm
|
||||
haxelib --global --never git haxelib https://github.com/FunkinCrew/haxelib.git funkin-patches --skip-dependencies
|
||||
haxelib --global --never git hmm https://github.com/FunkinCrew/hmm funkin-patches
|
||||
EOF
|
||||
|
||||
# hxcpp
|
||||
|
|
70
hmm.json
70
hmm.json
|
@ -1,5 +1,12 @@
|
|||
{
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"type": "git",
|
||||
|
@ -11,41 +18,36 @@
|
|||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a7d8e3bad89a0a3506a4714121f73d8e34522c49",
|
||||
"ref": "599f38eeb502a8ba6439784036c2cfdc7b485260",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a523c3b56622f0640933944171efed46929e360e",
|
||||
"ref": "9c6fb47968e894eb36bf10e94725cd7640c49281",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
"name": "flixel-text-input",
|
||||
"type": "haxelib",
|
||||
"version": "1.1.0"
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||
},
|
||||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "719b4f10d94186ed55f6fef1b6618d32abec8c15",
|
||||
"ref": "27f1ba626f80a6282fa8a187115e79a4a2133dc2",
|
||||
"url": "https://github.com/HaxeFlixel/flixel-ui"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "17e0d59fdbc2b6283a5c0e4df41f1c7f27b71c49",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
"ref": "768740a56b26aa0c072720e0d1236b94afe68e3e",
|
||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
|
@ -56,7 +58,7 @@
|
|||
"name": "funkin.vis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "38261833590773cb1de34ac5d11e0825696fc340",
|
||||
"ref": "22b1ce089dd924f15cdc4632397ef3504d464e90",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
|
@ -75,20 +77,22 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "5dc4c933bdc029f6139a47962e3b8c754060f210",
|
||||
"ref": "22f7c5a8ffca90d4677cffd6e570f53761709fbc",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "57c1604d6b5174839d7e0e012a4dd5dcbfc129da",
|
||||
"ref": "28bb710d0ae5d94b5108787593052165be43b980",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
"name": "hscript",
|
||||
"type": "haxelib",
|
||||
"version": "2.5.0"
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "12785398e2f07082f05034cb580682e5671442a2",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxCodec",
|
||||
|
@ -99,8 +103,10 @@
|
|||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "haxelib",
|
||||
"version": "4.3.2"
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "904ea40643b050a5a154c5e4c33a83fd2aec18b1",
|
||||
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
|
@ -121,11 +127,25 @@
|
|||
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
||||
"url": "https://github.com/FunkinCrew/json2object"
|
||||
},
|
||||
{
|
||||
"name": "jsonpatch",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f9b83215acd586dc28754b4ae7f69d4c06c3b4d3",
|
||||
"url": "https://github.com/EliteMasterEric/jsonpatch"
|
||||
},
|
||||
{
|
||||
"name": "jsonpath",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7a24193717b36393458c15c0435bb7c4470ecdda",
|
||||
"url": "https://github.com/EliteMasterEric/jsonpath"
|
||||
},
|
||||
{
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "872ff6db2f2d27c0243d4ff76802121ded550dd7",
|
||||
"ref": "e0b2339e02fff91168789dbd1a0dd019ea3dda39",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
@ -160,21 +180,21 @@
|
|||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
|
||||
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "bfbe30d81601b3543d80dce580108ad6b7e182c7",
|
||||
"ref": "96cfc5fa693b017e47f7cb13b765cc68698fa6b6",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
"name": "thx.core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "6240b6e136f7490d9298edbe8c1891374bd7cdf2",
|
||||
"ref": "76d87418fadd92eb8e1b61f004cff27d656e53dd",
|
||||
"url": "https://github.com/fponticelli/thx.core"
|
||||
},
|
||||
{
|
||||
|
|
1109
project.hxp
Normal file
1109
project.hxp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -113,7 +113,7 @@ class Main extends Sprite
|
|||
|
||||
addChild(game);
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
|
|
|
@ -27,13 +27,14 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
|
|||
import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
||||
|
@ -122,7 +123,7 @@ class InitState extends FlxState
|
|||
//
|
||||
// DISCORD API SETUP
|
||||
//
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.initialize();
|
||||
|
||||
Application.current.onExit.add(function(exitCode) {
|
||||
|
@ -143,7 +144,7 @@ class InitState extends FlxState
|
|||
// Plugins provide a useful interface for globally active Flixel objects,
|
||||
// that receive update events regardless of the current state.
|
||||
// TODO: Move scripted Module behavior to a Flixel plugin.
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
funkin.util.plugins.MemoryGCPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
|
@ -176,6 +177,8 @@ class InitState extends FlxState
|
|||
// Move it to use a BaseRegistry.
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
|
||||
NoteKindManager.loadScripts();
|
||||
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
ModuleHandler.callOnCreate();
|
||||
|
@ -241,11 +244,11 @@ class InitState extends FlxState
|
|||
totalNotesHit: 140,
|
||||
totalNotes: 190
|
||||
}
|
||||
// 2000 = loss
|
||||
// 240 = good
|
||||
// 230 = great
|
||||
// 210 = excellent
|
||||
// 190 = perfect
|
||||
// 2400 total notes = 7% = LOSS
|
||||
// 240 total notes = 79% = GOOD
|
||||
// 230 total notes = 82% = GREAT
|
||||
// 210 total notes = 91% = EXCELLENT
|
||||
// 190 total notes = PERFECT
|
||||
},
|
||||
}));
|
||||
#elseif ANIMDEBUG
|
||||
|
@ -371,11 +374,16 @@ class InitState extends FlxState
|
|||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Initializing Flixel debugger...');
|
||||
|
||||
#if !debug
|
||||
// Make errors less annoying on release builds.
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
#end
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package funkin.api.discord;
|
||||
|
||||
import Sys.sleep;
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
public function new()
|
||||
{
|
||||
trace("Discord Client starting...");
|
||||
|
|
|
@ -24,7 +24,7 @@ class NGUnsafe
|
|||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
@ -39,7 +39,7 @@ class NGUnsafe
|
|||
if (!medal.unlocked) medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
@ -63,7 +63,7 @@ class NGUnsafe
|
|||
}
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
|
|
@ -239,7 +239,7 @@ class NGio
|
|||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
@ -254,7 +254,7 @@ class NGio
|
|||
if (!medal.unlocked) medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
@ -278,7 +278,7 @@ class NGio
|
|||
}
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
|
|
@ -54,7 +54,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
public function initAnalyzer()
|
||||
{
|
||||
@:privateAccess
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 40);
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
||||
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
|
|
|
@ -117,7 +117,7 @@ class VisShit
|
|||
{
|
||||
// Math.pow3
|
||||
@:privateAccess
|
||||
var buf = snd._channel.__source.buffer;
|
||||
var buf = snd._channel.__audioSource.buffer;
|
||||
|
||||
// @:privateAccess
|
||||
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
||||
|
|
|
@ -16,7 +16,7 @@ class WaveformDataParser
|
|||
|
||||
// Method 1. This only works if the sound has been played before.
|
||||
@:privateAccess
|
||||
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__source?.buffer;
|
||||
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__audioSource?.buffer;
|
||||
|
||||
if (soundBuffer == null)
|
||||
{
|
||||
|
|
|
@ -263,7 +263,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
||||
* @return The created entry.
|
||||
*/
|
||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
||||
public function parseEntryDataWithMigration(id:String, version:Null<thx.semver.Version>):Null<J>
|
||||
{
|
||||
if (version == null)
|
||||
{
|
||||
|
|
|
@ -46,7 +46,7 @@ class SongEventRegistry
|
|||
|
||||
if (event != null)
|
||||
{
|
||||
trace(' Loaded built-in song event: (${event.id})');
|
||||
trace(' Loaded built-in song event: ${event.id}');
|
||||
eventCache.set(event.id, event);
|
||||
}
|
||||
else
|
||||
|
@ -59,9 +59,9 @@ class SongEventRegistry
|
|||
static function registerScriptedEvents()
|
||||
{
|
||||
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
||||
|
||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||
for (eventCls in scriptedEventClassNames)
|
||||
{
|
||||
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||
|
|
|
@ -38,6 +38,17 @@ class PlayerData
|
|||
@:optional
|
||||
public var freeplayDJ:Null<PlayerFreeplayDJData> = null;
|
||||
|
||||
/**
|
||||
* Data for displaying this character in the Character Select menu.
|
||||
* If null, exclude from Character Select.
|
||||
*/
|
||||
@:optional
|
||||
public var charSelect:Null<PlayerCharSelectData> = null;
|
||||
|
||||
/**
|
||||
* Data for displaying this character in the results screen.
|
||||
*/
|
||||
@:optional
|
||||
public var results:Null<PlayerResultsData> = null;
|
||||
|
||||
/**
|
||||
|
@ -97,6 +108,9 @@ class PlayerFreeplayDJData
|
|||
@:optional
|
||||
var cartoon:Null<PlayerFreeplayDJCartoonData>;
|
||||
|
||||
@:optional
|
||||
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
animationMap = new Map();
|
||||
|
@ -183,6 +197,58 @@ class PlayerFreeplayDJData
|
|||
{
|
||||
return cartoon?.channelChangeFrame ?? 60;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroStartFrame():Int
|
||||
{
|
||||
return fistPump?.introStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroEndFrame():Int
|
||||
{
|
||||
return fistPump?.introEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopStartFrame():Int
|
||||
{
|
||||
return fistPump?.loopStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopEndFrame():Int
|
||||
{
|
||||
return fistPump?.loopEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroBadStartFrame():Int
|
||||
{
|
||||
return fistPump?.introBadStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroBadEndFrame():Int
|
||||
{
|
||||
return fistPump?.introBadEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopBadStartFrame():Int
|
||||
{
|
||||
return fistPump?.loopBadStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopBadEndFrame():Int
|
||||
{
|
||||
return fistPump?.loopBadEndFrame ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerCharSelectData
|
||||
{
|
||||
/**
|
||||
* A zero-indexed number for the character's preferred position in the grid.
|
||||
* 0 = top left, 4 = center, 8 = bottom right
|
||||
* In the event of a conflict, the first character alphabetically gets it,
|
||||
* and others get shifted over.
|
||||
*/
|
||||
@:optional
|
||||
public var position:Null<Int>;
|
||||
}
|
||||
|
||||
typedef PlayerResultsData =
|
||||
|
@ -242,3 +308,30 @@ typedef PlayerFreeplayDJCartoonData =
|
|||
var loopFrame:Int;
|
||||
var channelChangeFrame:Int;
|
||||
}
|
||||
|
||||
typedef PlayerFreeplayDJFistPumpData =
|
||||
{
|
||||
@:default(0)
|
||||
var introStartFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var introEndFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var loopStartFrame:Int;
|
||||
|
||||
@:default(-1)
|
||||
var loopEndFrame:Int;
|
||||
|
||||
@:default(0)
|
||||
var introBadStartFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var introBadEndFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var loopBadStartFrame:Int;
|
||||
|
||||
@:default(-1)
|
||||
var loopBadEndFrame:Int;
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ package funkin.data.freeplay.player;
|
|||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||
import funkin.save.Save;
|
||||
|
||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||
{
|
||||
|
@ -53,13 +54,49 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||
log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.');
|
||||
}
|
||||
|
||||
public function countUnlockedCharacters():Int
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (player.isUnlocked()) count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public function hasNewCharacter():Bool
|
||||
{
|
||||
var characters = Save.instance.charactersSeen.clone();
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (!player.isUnlocked()) continue;
|
||||
if (characters.contains(charId)) continue;
|
||||
|
||||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallthrough case.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playable character associated with a given stage character.
|
||||
* @param characterId The stage character ID.
|
||||
* @return The playable character.
|
||||
*/
|
||||
public function getCharacterOwnerId(characterId:String):Null<String>
|
||||
public function getCharacterOwnerId(characterId:Null<String>):Null<String>
|
||||
{
|
||||
if (characterId == null) return null;
|
||||
return ownedCharacterIds[characterId];
|
||||
}
|
||||
|
||||
|
|
31
source/funkin/data/notestyle/CHANGELOG.md
Normal file
31
source/funkin/data/notestyle/CHANGELOG.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Note Style Data Schema Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.1.0]
|
||||
### Added
|
||||
- Added several new `assets`:
|
||||
- `countdownThree`
|
||||
- `countdownTwo`
|
||||
- `countdownOne`
|
||||
- `countdownGo`
|
||||
- `judgementSick`
|
||||
- `judgementGood`
|
||||
- `judgementBad`
|
||||
- `judgementShit`
|
||||
- `comboNumber0`
|
||||
- `comboNumber1`
|
||||
- `comboNumber2`
|
||||
- `comboNumber3`
|
||||
- `comboNumber4`
|
||||
- `comboNumber5`
|
||||
- `comboNumber6`
|
||||
- `comboNumber7`
|
||||
- `comboNumber8`
|
||||
- `comboNumber9`
|
||||
|
||||
## [1.0.0]
|
||||
Initial version.
|
|
@ -74,6 +74,84 @@ typedef NoteStyleAssetsData =
|
|||
*/
|
||||
@:optional
|
||||
var holdNoteCover:NoteStyleAssetData<NoteStyleData_HoldNoteCover>;
|
||||
|
||||
/**
|
||||
* The THREE sound (and an optional pre-READY graphic).
|
||||
*/
|
||||
@:optional
|
||||
var countdownThree:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The TWO sound and READY graphic.
|
||||
*/
|
||||
@:optional
|
||||
var countdownTwo:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The ONE sound and SET graphic.
|
||||
*/
|
||||
@:optional
|
||||
var countdownOne:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The GO sound and GO! graphic.
|
||||
*/
|
||||
@:optional
|
||||
var countdownGo:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The SICK! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementSick:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
/**
|
||||
* The GOOD! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementGood:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
/**
|
||||
* The BAD! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementBad:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
/**
|
||||
* The SHIT! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementShit:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
@:optional
|
||||
var comboNumber0:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber1:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber2:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber3:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber4:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber5:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber6:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber7:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber8:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber9:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,10 +187,19 @@ typedef NoteStyleAssetData<T> =
|
|||
@:optional
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* If true, animations will be played on the graphic.
|
||||
* @default `false` to save performance.
|
||||
*/
|
||||
@:default(false)
|
||||
@:optional
|
||||
var animated:Bool;
|
||||
|
||||
/**
|
||||
* The structure of this data depends on the asset.
|
||||
*/
|
||||
var data:T;
|
||||
@:optional
|
||||
var data:Null<T>;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_Note =
|
||||
|
@ -123,7 +210,14 @@ typedef NoteStyleData_Note =
|
|||
var right:UnnamedAnimationData;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_Countdown =
|
||||
{
|
||||
var audioPath:String;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_HoldNote = {}
|
||||
typedef NoteStyleData_Judgement = {}
|
||||
typedef NoteStyleData_ComboNum = {}
|
||||
|
||||
/**
|
||||
* Data on animations for each direction of the strumline.
|
||||
|
|
|
@ -11,9 +11,9 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateNoteStyleData()` function.
|
||||
*/
|
||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
|
||||
public static var instance(get, never):NoteStyleRegistry;
|
||||
static var _instance:Null<NoteStyleRegistry> = null;
|
||||
|
|
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.2.4]
|
||||
### Added
|
||||
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
||||
- If the value isn't present, it will use the `playData.characters.opponent`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the opponent)
|
||||
- Added `playData.characters.playerVocals` to specify which vocal track(s) to play for the player.
|
||||
- If the value isn't present, it will use the `playData.characters.player`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the player)
|
||||
|
||||
## [2.2.3]
|
||||
### Added
|
||||
- Added `charter` field to denote authorship of a chart.
|
||||
|
|
|
@ -529,12 +529,26 @@ class SongCharacterData implements ICloneable<SongCharacterData>
|
|||
@:default([])
|
||||
public var altInstrumentals:Array<String> = [];
|
||||
|
||||
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '')
|
||||
@:optional
|
||||
public var opponentVocals:Null<Array<String>> = null;
|
||||
|
||||
@:optional
|
||||
public var playerVocals:Null<Array<String>> = null;
|
||||
|
||||
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '', ?altInstrumentals:Array<String>,
|
||||
?opponentVocals:Array<String>, ?playerVocals:Array<String>)
|
||||
{
|
||||
this.player = player;
|
||||
this.girlfriend = girlfriend;
|
||||
this.opponent = opponent;
|
||||
this.instrumental = instrumental;
|
||||
|
||||
this.altInstrumentals = altInstrumentals;
|
||||
this.opponentVocals = opponentVocals;
|
||||
this.playerVocals = playerVocals;
|
||||
|
||||
if (opponentVocals == null) this.opponentVocals = [opponent];
|
||||
if (playerVocals == null) this.playerVocals = [player];
|
||||
}
|
||||
|
||||
public function clone():SongCharacterData
|
||||
|
@ -722,18 +736,6 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
|||
{
|
||||
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongEventData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward(time, eventKind, value, activated, getStepTime, clone)
|
||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||
{
|
||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||
{
|
||||
this = new SongEventDataRaw(time, eventKind, value);
|
||||
}
|
||||
|
||||
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||
{
|
||||
|
@ -757,27 +759,27 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
}
|
||||
}
|
||||
|
||||
public inline function getHandler():Null<SongEvent>
|
||||
public function getHandler():Null<SongEvent>
|
||||
{
|
||||
return SongEventRegistry.getEvent(this.eventKind);
|
||||
}
|
||||
|
||||
public inline function getSchema():Null<SongEventSchema>
|
||||
public function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventRegistry.getEventSchema(this.eventKind);
|
||||
}
|
||||
|
||||
public inline function getDynamic(key:String):Null<Dynamic>
|
||||
public function getDynamic(key:String):Null<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getBool(key:String):Null<Bool>
|
||||
public function getBool(key:String):Null<Bool>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getInt(key:String):Null<Int>
|
||||
public function getInt(key:String):Null<Int>
|
||||
{
|
||||
if (this.value == null) return null;
|
||||
var result = Reflect.field(this.value, key);
|
||||
|
@ -787,7 +789,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
return cast result;
|
||||
}
|
||||
|
||||
public inline function getFloat(key:String):Null<Float>
|
||||
public function getFloat(key:String):Null<Float>
|
||||
{
|
||||
if (this.value == null) return null;
|
||||
var result = Reflect.field(this.value, key);
|
||||
|
@ -797,17 +799,17 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
return cast result;
|
||||
}
|
||||
|
||||
public inline function getString(key:String):String
|
||||
public function getString(key:String):String
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getArray(key:String):Array<Dynamic>
|
||||
public function getArray(key:String):Array<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getBoolArray(key:String):Array<Bool>
|
||||
public function getBoolArray(key:String):Array<Bool>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
@ -839,6 +841,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongEventData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward(time, eventKind, value, activated, getStepTime, clone, getHandler, getSchema, getDynamic, getBool, getInt, getFloat, getString, getArray,
|
||||
getBoolArray, buildTooltip, valueAsStruct)
|
||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||
{
|
||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||
{
|
||||
this = new SongEventDataRaw(time, eventKind, value);
|
||||
}
|
||||
|
||||
public function clone():SongEventData
|
||||
{
|
||||
|
@ -951,12 +966,18 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
return this.kind = value;
|
||||
}
|
||||
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
||||
@:alias("p")
|
||||
@:default([])
|
||||
@:optional
|
||||
public var params:Array<NoteParamData>;
|
||||
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
|
||||
{
|
||||
this.time = time;
|
||||
this.data = data;
|
||||
this.length = length;
|
||||
this.kind = kind;
|
||||
this.params = params ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1051,9 +1072,19 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
_stepLength = null;
|
||||
}
|
||||
|
||||
public function cloneParams():Array<NoteParamData>
|
||||
{
|
||||
var params:Array<NoteParamData> = [];
|
||||
for (param in this.params)
|
||||
{
|
||||
params.push(param.clone());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
public function clone():SongNoteDataRaw
|
||||
{
|
||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
|
||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
|
@ -1069,9 +1100,9 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
@:forward
|
||||
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
{
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
|
||||
{
|
||||
this = new SongNoteDataRaw(time, data, length, kind);
|
||||
this = new SongNoteDataRaw(time, data, length, kind, params);
|
||||
}
|
||||
|
||||
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
|
||||
|
@ -1115,7 +1146,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
if (other.kind == '' || this.kind == null) return false;
|
||||
}
|
||||
|
||||
return this.time == other.time && this.data == other.data && this.length == other.length;
|
||||
return this.time == other.time && this.data == other.data && this.length == other.length && this.params == other.params;
|
||||
}
|
||||
|
||||
@:op(A != B)
|
||||
|
@ -1134,7 +1165,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
if (other.kind == '') return true;
|
||||
}
|
||||
|
||||
return this.time != other.time || this.data != other.data || this.length != other.length;
|
||||
return this.time != other.time || this.data != other.data || this.length != other.length || this.params != other.params;
|
||||
}
|
||||
|
||||
@:op(A > B)
|
||||
|
@ -1171,7 +1202,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
|
||||
public function clone():SongNoteData
|
||||
{
|
||||
return new SongNoteData(this.time, this.data, this.length, this.kind);
|
||||
return new SongNoteData(this.time, this.data, this.length, this.kind, this.params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1183,3 +1214,30 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
}
|
||||
|
||||
class NoteParamData implements ICloneable<NoteParamData>
|
||||
{
|
||||
@:alias("n")
|
||||
public var name:String;
|
||||
|
||||
@:alias("v")
|
||||
@:jcustomparse(funkin.data.DataParse.dynamicValue)
|
||||
@:jcustomwrite(funkin.data.DataWrite.dynamicValue)
|
||||
public var value:Dynamic;
|
||||
|
||||
public function new(name:String, value:Dynamic)
|
||||
{
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public function clone():NoteParamData
|
||||
{
|
||||
return new NoteParamData(this.name, this.value);
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'NoteParamData(${this.name}, ${this.value})';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,6 +199,8 @@ class FNFLegacyImporter
|
|||
{
|
||||
// Handle the dumb logic for mustHitSection.
|
||||
var noteData = note.data;
|
||||
if (noteData < 0) continue; // Exclude Psych event notes.
|
||||
if (noteData > (STRUMLINE_SIZE * 2)) noteData = noteData % (2 * STRUMLINE_SIZE); // Handle other engine event notes.
|
||||
|
||||
// Flip notes if mustHitSection is FALSE (not true lol).
|
||||
if (!mustHitSection)
|
||||
|
|
|
@ -93,8 +93,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
|||
public function listBaseGameStageIds():Array<String>
|
||||
{
|
||||
return [
|
||||
"mainStage", "spookyMansion", "phillyTrain", "limoRide", "mallXmas", "mallEvil", "school", "schoolEvil", "tankmanBattlefield", "phillyStreets",
|
||||
"phillyBlazin",
|
||||
"mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallEvil", "school",
|
||||
"schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyBlazin",
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -93,9 +93,9 @@ typedef LevelPropData =
|
|||
* The frequency to bop at, in beats.
|
||||
* 1 = every beat, 2 = every other beat, etc.
|
||||
* Supports up to 0.25 precision.
|
||||
* @default 0.0
|
||||
* @default 1.0
|
||||
*/
|
||||
@:default(0.0)
|
||||
@:default(1.0)
|
||||
@:optional
|
||||
var danceEvery:Float;
|
||||
|
||||
|
|
|
@ -4,8 +4,11 @@ import flixel.util.FlxSignal.FlxTypedSignal;
|
|||
import flxanimate.FlxAnimate;
|
||||
import flxanimate.FlxAnimate.Settings;
|
||||
import flxanimate.frames.FlxAnimateFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
/**
|
||||
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
||||
|
@ -18,16 +21,26 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
FrameRate: 24.0,
|
||||
Reversed: false,
|
||||
// ?OnComplete:Void -> Void,
|
||||
ShowPivot: #if debug false #else false #end,
|
||||
ShowPivot: false,
|
||||
Antialiasing: true,
|
||||
ScrollFactor: null,
|
||||
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
|
||||
};
|
||||
|
||||
/**
|
||||
* Signal dispatched when an animation finishes playing.
|
||||
* Signal dispatched when an animation advances to the next frame.
|
||||
*/
|
||||
public var onAnimationFinish:FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
||||
public var onAnimationFrame:FlxTypedSignal<String->Int->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Signal dispatched when a non-looping animation finishes playing.
|
||||
*/
|
||||
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Signal dispatched when a looping animation finishes playing
|
||||
*/
|
||||
public var onAnimationLoopComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||
|
||||
var currentAnimation:String;
|
||||
|
||||
|
@ -44,17 +57,20 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
|
||||
super(x, y, path, settings);
|
||||
|
||||
if (this.anim.curInstance == null)
|
||||
if (this.anim.stageInstance == null)
|
||||
{
|
||||
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
||||
}
|
||||
|
||||
onAnimationFinish.add(cleanupAnimation);
|
||||
onAnimationComplete.add(cleanupAnimation);
|
||||
|
||||
// This defaults the sprite to play the first animation in the atlas,
|
||||
// then pauses it. This ensures symbols are intialized properly.
|
||||
this.anim.play('');
|
||||
this.anim.pause();
|
||||
|
||||
this.anim.onComplete.add(_onAnimationComplete);
|
||||
this.anim.onFrame.add(_onAnimationFrame);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,9 +78,13 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
*/
|
||||
public function listAnimations():Array<String>
|
||||
{
|
||||
if (this.anim == null) return [];
|
||||
return this.anim.getFrameLabels();
|
||||
// return [""];
|
||||
var mainSymbol = this.anim.symbolDictionary[this.anim.stageInstance.symbol.name];
|
||||
if (mainSymbol == null)
|
||||
{
|
||||
FlxG.log.error('FlxAtlasSprite does not have its main symbol!');
|
||||
return [];
|
||||
}
|
||||
return mainSymbol.getFrameLabels().map(keyFrame -> keyFrame.name).filterNull();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,12 +127,11 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
* @param restart Whether to restart the animation if it is already playing.
|
||||
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
||||
* @param loop Whether to loop the animation
|
||||
* @param startFrame The frame to start the animation on
|
||||
* NOTE: `loop` and `ignoreOther` are not compatible with each other!
|
||||
*/
|
||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void
|
||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, loop:Bool = false, startFrame:Int = 0):Void
|
||||
{
|
||||
if (loop == null) loop = false;
|
||||
|
||||
// Skip if not allowed to play animations.
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
|
||||
|
@ -128,7 +147,7 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
else
|
||||
{
|
||||
// Resume animation if it's paused.
|
||||
anim.play('', false, false);
|
||||
anim.play('', restart, false, startFrame);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -141,31 +160,27 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
}
|
||||
}
|
||||
|
||||
anim.callback = function(_, frame:Int) {
|
||||
var offset = loop ? 0 : -1;
|
||||
|
||||
var frameLabel = anim.getFrameLabel(id);
|
||||
if (frame == (frameLabel.duration + offset) + frameLabel.index)
|
||||
anim.onComplete.removeAll();
|
||||
anim.onComplete.add(function() {
|
||||
if (loop)
|
||||
{
|
||||
if (loop)
|
||||
{
|
||||
playAnimation(id, true, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
onAnimationFinish.dispatch(id);
|
||||
}
|
||||
onAnimationLoopComplete.dispatch(id);
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
this.currentAnimation = id;
|
||||
}
|
||||
};
|
||||
|
||||
anim.onComplete = function() {
|
||||
onAnimationFinish.dispatch(id);
|
||||
};
|
||||
else
|
||||
{
|
||||
onAnimationComplete.dispatch(id);
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent other animations from playing if `ignoreOther` is true.
|
||||
if (ignoreOther) canPlayOtherAnims = false;
|
||||
|
||||
// Move to the first frame of the animation.
|
||||
// goToFrameLabel(id);
|
||||
trace('Playing animation $id');
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
goToFrameLabel(id);
|
||||
this.currentAnimation = id;
|
||||
}
|
||||
|
@ -175,6 +190,24 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
super.update(elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has finished playing.
|
||||
* Never true if animation is configured to loop.
|
||||
*/
|
||||
public function isAnimationFinished():Bool
|
||||
{
|
||||
return this.anim.finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has reached the last frame.
|
||||
* Can be true even if animation is configured to loop.
|
||||
*/
|
||||
public function isLoopComplete():Bool
|
||||
{
|
||||
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the current animation.
|
||||
*/
|
||||
|
@ -219,4 +252,76 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
// this.currentAnimation = null;
|
||||
this.anim.pause();
|
||||
}
|
||||
|
||||
function _onAnimationFrame(frame:Int):Void
|
||||
{
|
||||
if (currentAnimation != null)
|
||||
{
|
||||
onAnimationFrame.dispatch(currentAnimation, frame);
|
||||
if (isLoopComplete()) onAnimationLoopComplete.dispatch(currentAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
function _onAnimationComplete():Void
|
||||
{
|
||||
if (currentAnimation != null)
|
||||
{
|
||||
onAnimationComplete.dispatch(currentAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
var prevFrames:Map<Int, FlxFrame> = [];
|
||||
|
||||
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
||||
{
|
||||
if (graphic == null || !Assets.exists(graphic))
|
||||
{
|
||||
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
|
||||
if (prevFrame == null) return;
|
||||
|
||||
prevFrame.copyTo(frames.getByIndex(index));
|
||||
return;
|
||||
}
|
||||
|
||||
var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
|
||||
prevFrames.set(index, prevFrame);
|
||||
|
||||
var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
|
||||
frame.copyTo(frames.getByIndex(index));
|
||||
|
||||
// Additional sizing fix.
|
||||
@:privateAccess
|
||||
if (true)
|
||||
{
|
||||
var frame = frames.getByIndex(index);
|
||||
frame.tileMatrix[0] = prevFrame.frame.width / frame.frame.width;
|
||||
frame.tileMatrix[3] = prevFrame.frame.height / frame.frame.height;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBasePosition():Null<FlxPoint>
|
||||
{
|
||||
var stagePos = new FlxPoint(anim.stageInstance.matrix.tx, anim.stageInstance.matrix.ty);
|
||||
var instancePos = new FlxPoint(anim.curInstance.matrix.tx, anim.curInstance.matrix.ty);
|
||||
var firstElement = anim.curSymbol.timeline?.get(0)?.get(0)?.get(0);
|
||||
if (firstElement == null) return instancePos;
|
||||
var firstElementPos = new FlxPoint(firstElement.matrix.tx, firstElement.matrix.ty);
|
||||
|
||||
return instancePos + firstElementPos;
|
||||
}
|
||||
|
||||
public function getPivotPosition():Null<FlxPoint>
|
||||
{
|
||||
return anim.curInstance.symbol.transformationPoint;
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
for (prevFrameId in prevFrames.keys())
|
||||
{
|
||||
replaceFrameGraphic(prevFrameId, null);
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
|
55
source/funkin/graphics/shaders/AdjustColorShader.hx
Normal file
55
source/funkin/graphics/shaders/AdjustColorShader.hx
Normal file
|
@ -0,0 +1,55 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class AdjustColorShader extends FlxRuntimeShader
|
||||
{
|
||||
public var hue(default, set):Float;
|
||||
public var saturation(default, set):Float;
|
||||
public var brightness(default, set):Float;
|
||||
public var contrast(default, set):Float;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super(Assets.getText(Paths.frag('adjustColor')));
|
||||
// FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'brightness', 'contrast']));
|
||||
hue = 0;
|
||||
saturation = 0;
|
||||
brightness = 0;
|
||||
contrast = 0;
|
||||
}
|
||||
|
||||
function set_hue(value:Float):Float
|
||||
{
|
||||
this.setFloat('hue', value);
|
||||
this.hue = value;
|
||||
|
||||
return this.hue;
|
||||
}
|
||||
|
||||
function set_saturation(value:Float):Float
|
||||
{
|
||||
this.setFloat('saturation', value);
|
||||
this.saturation = value;
|
||||
|
||||
return this.saturation;
|
||||
}
|
||||
|
||||
function set_brightness(value:Float):Float
|
||||
{
|
||||
this.setFloat('brightness', value);
|
||||
this.brightness = value;
|
||||
|
||||
return this.brightness;
|
||||
}
|
||||
|
||||
function set_contrast(value:Float):Float
|
||||
{
|
||||
this.setFloat('contrast', value);
|
||||
this.contrast = value;
|
||||
|
||||
return this.contrast;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package funkin.graphics.shaders;
|
|||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import lime.graphics.opengl.GLProgram;
|
||||
import lime.utils.Log;
|
||||
|
@ -32,6 +33,9 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
|||
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
||||
uniform vec4 uCameraBounds;
|
||||
|
||||
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||
uniform vec4 uFrameBounds;
|
||||
|
||||
// screen coord -> world coord conversion
|
||||
// returns world coord in px
|
||||
vec2 screenToWorld(vec2 screenCoord) {
|
||||
|
@ -56,6 +60,25 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
|||
return (worldCoord - offset) / scale;
|
||||
}
|
||||
|
||||
// screen coord -> frame coord conversion
|
||||
// returns normalized frame coord
|
||||
vec2 screenToFrame(vec2 screenCoord) {
|
||||
float left = uFrameBounds.x;
|
||||
float top = uFrameBounds.y;
|
||||
float right = uFrameBounds.z;
|
||||
float bottom = uFrameBounds.w;
|
||||
float width = right - left;
|
||||
float height = bottom - top;
|
||||
|
||||
float clampedX = clamp(screenCoord.x, left, right);
|
||||
float clampedY = clamp(screenCoord.y, top, bottom);
|
||||
|
||||
return vec2(
|
||||
(clampedX - left) / (width),
|
||||
(clampedY - top) / (height)
|
||||
);
|
||||
}
|
||||
|
||||
// internally used to get the maximum `openfl_TextureCoordv`
|
||||
vec2 bitmapCoordScale() {
|
||||
return openfl_TextureCoordv / screenCoord;
|
||||
|
@ -80,6 +103,8 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
|||
{
|
||||
super(fragmentSource, null, glVersion);
|
||||
uScreenResolution.value = [FlxG.width, FlxG.height];
|
||||
uCameraBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||
uFrameBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||
}
|
||||
|
||||
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
||||
|
@ -89,6 +114,12 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
|||
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
||||
}
|
||||
|
||||
public function updateFrameInfo(frame:FlxFrame)
|
||||
{
|
||||
// NOTE: uv.width is actually the right pos and uv.height is the bottom pos
|
||||
uFrameBounds.value = [frame.uv.x, frame.uv.y, frame.uv.width, frame.uv.height];
|
||||
}
|
||||
|
||||
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
||||
{
|
||||
try
|
||||
|
|
|
@ -32,6 +32,14 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
|||
return time = value;
|
||||
}
|
||||
|
||||
public var spriteMode(default, set):Bool = false;
|
||||
|
||||
function set_spriteMode(value:Bool):Bool
|
||||
{
|
||||
this.setBool('uSpriteMode', value);
|
||||
return spriteMode = value;
|
||||
}
|
||||
|
||||
// The scale of the rain depends on the world coordinate system, so higher resolution makes
|
||||
// the raindrops smaller. This parameter can be used to adjust the total scale of the scene.
|
||||
// The size of the raindrops is proportional to the value of this parameter.
|
||||
|
|
|
@ -356,9 +356,10 @@ class Controls extends FlxActionSet
|
|||
|
||||
public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool
|
||||
{
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (!byName.exists(name)) throw 'Invalid name: $name';
|
||||
#end
|
||||
|
||||
var action = byName[name];
|
||||
if (gamepadOnly) return action.checkFiltered(trigger, GAMEPAD);
|
||||
else
|
||||
|
@ -367,7 +368,7 @@ class Controls extends FlxActionSet
|
|||
|
||||
public function getKeysForAction(name:Action):Array<FlxKey>
|
||||
{
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (!byName.exists(name)) throw 'Invalid name: $name';
|
||||
#end
|
||||
|
||||
|
@ -382,7 +383,7 @@ class Controls extends FlxActionSet
|
|||
|
||||
public function getButtonsForAction(name:Action):Array<FlxGamepadInputID>
|
||||
{
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (!byName.exists(name)) throw 'Invalid name: $name';
|
||||
#end
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
|
|||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
|
@ -27,11 +28,10 @@ class PolymodHandler
|
|||
{
|
||||
/**
|
||||
* The API version that mods should comply with.
|
||||
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
||||
* Bug fixes increment the patch version, new features increment the minor version.
|
||||
* Changes that break old mods increment the major version.
|
||||
* Indicates which mods are compatible with this version of the game.
|
||||
* Minor updates rarely impact mods but major versions often do.
|
||||
*/
|
||||
static final API_VERSION:String = '0.1.0';
|
||||
static final API_VERSION:String = "0.5.0"; // Constants.VERSION;
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
|
@ -177,7 +177,7 @@ class PolymodHandler
|
|||
loadedModIds.push(mod.id);
|
||||
}
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
var fileList:Array<String> = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||
trace('Installed mods have replaced ${fileList.length} images.');
|
||||
for (item in fileList)
|
||||
|
@ -233,6 +233,8 @@ class PolymodHandler
|
|||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
||||
|
||||
Polymod.addImportAlias('funkin.data.event.SongEventSchema', funkin.data.event.SongEventSchema.SongEventSchemaRaw);
|
||||
|
||||
// Add blacklisting for prohibited classes and packages.
|
||||
|
||||
// `Sys`
|
||||
|
@ -251,8 +253,33 @@ class PolymodHandler
|
|||
// Lib.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('cpp.Lib');
|
||||
|
||||
// `Unserializer`
|
||||
// Unserializerr.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||
Polymod.blacklistImport('Unserializer');
|
||||
|
||||
// `lime.system.CFFI`
|
||||
// Can load and execute compiled binaries.
|
||||
Polymod.blacklistImport('lime.system.CFFI');
|
||||
|
||||
// `lime.system.JNI`
|
||||
// Can load and execute compiled binaries.
|
||||
Polymod.blacklistImport('lime.system.JNI');
|
||||
|
||||
// `lime.system.System`
|
||||
// System.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('lime.system.System');
|
||||
|
||||
// `lime.utils.Assets`
|
||||
// Literally just has a private `resolveClass` function for some reason?
|
||||
Polymod.blacklistImport('lime.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.utils.Assets');
|
||||
|
||||
// `openfl.desktop.NativeProcess`
|
||||
// Can load native processes on the host operating system.
|
||||
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||
|
||||
// `polymod.*`
|
||||
// You can probably unblacklist a module
|
||||
// Contains functions which may allow for un-blacklisting other modules.
|
||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
|
@ -261,6 +288,7 @@ class PolymodHandler
|
|||
}
|
||||
|
||||
// `sys.*`
|
||||
// Access to system utilities such as the file system.
|
||||
for (cls in ClassMacro.listClassesInPackage('sys'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
|
@ -383,6 +411,7 @@ class PolymodHandler
|
|||
StageRegistry.instance.loadEntries();
|
||||
|
||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||
NoteKindManager.loadScripts();
|
||||
ModuleHandler.loadModuleCache();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package funkin.modding.base;
|
||||
|
||||
/**
|
||||
* A script that can be tied to an FlxUIState.
|
||||
* Create a scripted class that extends FlxUIState to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedFlxUIState extends flixel.addons.ui.FlxUIState implements HScriptedClass {}
|
|
@ -11,6 +11,9 @@ import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
|||
import flixel.util.FlxTimer;
|
||||
import funkin.util.EaseUtil;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
class Countdown
|
||||
{
|
||||
|
@ -19,6 +22,24 @@ class Countdown
|
|||
*/
|
||||
public static var countdownStep(default, null):CountdownStep = BEFORE;
|
||||
|
||||
/**
|
||||
* Which alternate graphic/sound on countdown to use.
|
||||
* This is set via the current notestyle.
|
||||
* For example, in Week 6 it is `pixel`.
|
||||
*/
|
||||
public static var soundSuffix:String = '';
|
||||
|
||||
/**
|
||||
* Which alternate graphic on countdown to use.
|
||||
* You can set this via script.
|
||||
* For example, in Week 6 it is `-pixel`.
|
||||
*/
|
||||
public static var graphicSuffix:String = '';
|
||||
|
||||
static var noteStyle:NoteStyle;
|
||||
|
||||
static var fallbackNoteStyle:Null<NoteStyle>;
|
||||
|
||||
/**
|
||||
* The currently running countdown. This will be null if there is no countdown running.
|
||||
*/
|
||||
|
@ -30,7 +51,7 @@ class Countdown
|
|||
* This will automatically stop and restart the countdown if it is already running.
|
||||
* @returns `false` if the countdown was cancelled by a script.
|
||||
*/
|
||||
public static function performCountdown(isPixelStyle:Bool):Bool
|
||||
public static function performCountdown():Bool
|
||||
{
|
||||
countdownStep = BEFORE;
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
|
@ -65,10 +86,10 @@ class Countdown
|
|||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// Countdown graphic.
|
||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
||||
showCountdownGraphic(countdownStep);
|
||||
|
||||
// Countdown sound.
|
||||
playCountdownSound(countdownStep, isPixelStyle);
|
||||
playCountdownSound(countdownStep);
|
||||
|
||||
// Event handling bullshit.
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
|
@ -177,122 +198,69 @@ class Countdown
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves the graphic to use for this step of the countdown.
|
||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
||||
*
|
||||
* This is public so modules can do lol funny shit.
|
||||
* Reset the countdown configuration to the default.
|
||||
*/
|
||||
public static function showCountdownGraphic(index:CountdownStep, isPixelStyle:Bool):Void
|
||||
public static function reset()
|
||||
{
|
||||
var spritePath:String = null;
|
||||
noteStyle = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the note style data (if we haven't already)
|
||||
* @param noteStyleId The id of the note style to fetch. Defaults to the one used by the current PlayState.
|
||||
* @param force Fetch the note style from the registry even if we've already fetched it.
|
||||
*/
|
||||
static function fetchNoteStyle(?noteStyleId:String, force:Bool = false):Void
|
||||
{
|
||||
if (noteStyle != null && !force) return;
|
||||
|
||||
if (noteStyleId == null) noteStyleId = PlayState.instance?.currentChart?.noteStyle;
|
||||
|
||||
noteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the graphic to use for this step of the countdown.
|
||||
*/
|
||||
public static function showCountdownGraphic(index:CountdownStep):Void
|
||||
{
|
||||
fetchNoteStyle();
|
||||
|
||||
var countdownSprite = noteStyle.buildCountdownSprite(index);
|
||||
if (countdownSprite == null) return;
|
||||
|
||||
var fadeEase = FlxEase.cubeInOut;
|
||||
|
||||
if (isPixelStyle)
|
||||
{
|
||||
fadeEase = EaseUtil.stepped(8);
|
||||
switch (index)
|
||||
{
|
||||
case TWO:
|
||||
spritePath = 'weeb/pixelUI/ready-pixel';
|
||||
case ONE:
|
||||
spritePath = 'weeb/pixelUI/set-pixel';
|
||||
case GO:
|
||||
spritePath = 'weeb/pixelUI/date-pixel';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case TWO:
|
||||
spritePath = 'ready';
|
||||
case ONE:
|
||||
spritePath = 'set';
|
||||
case GO:
|
||||
spritePath = 'go';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
|
||||
if (spritePath == null) return;
|
||||
|
||||
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
|
||||
countdownSprite.scrollFactor.set(0, 0);
|
||||
|
||||
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
||||
|
||||
countdownSprite.antialiasing = !isPixelStyle;
|
||||
|
||||
countdownSprite.updateHitbox();
|
||||
countdownSprite.screenCenter();
|
||||
if (noteStyle.isCountdownSpritePixel(index)) fadeEase = EaseUtil.stepped(8);
|
||||
|
||||
// Fade sprite in, then out, then destroy it.
|
||||
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100}, Conductor.instance.beatLengthMs / 1000,
|
||||
FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000,
|
||||
{
|
||||
ease: FlxEase.cubeInOut,
|
||||
ease: fadeEase,
|
||||
onComplete: function(twn:FlxTween) {
|
||||
countdownSprite.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000,
|
||||
{
|
||||
ease: fadeEase
|
||||
});
|
||||
|
||||
countdownSprite.cameras = [PlayState.instance.camHUD];
|
||||
PlayState.instance.add(countdownSprite);
|
||||
countdownSprite.screenCenter();
|
||||
|
||||
var offsets = noteStyle.getCountdownSpriteOffsets(index);
|
||||
countdownSprite.x += offsets[0];
|
||||
countdownSprite.y += offsets[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sound file to use for this step of the countdown.
|
||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
||||
*
|
||||
* This is public so modules can do lol funny shit.
|
||||
*/
|
||||
public static function playCountdownSound(index:CountdownStep, isPixelStyle:Bool):Void
|
||||
public static function playCountdownSound(step:CountdownStep):FunkinSound
|
||||
{
|
||||
var soundPath:String = null;
|
||||
fetchNoteStyle();
|
||||
var path = noteStyle.getCountdownSoundPath(step);
|
||||
if (path == null) return null;
|
||||
|
||||
if (isPixelStyle)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case THREE:
|
||||
soundPath = 'intro3-pixel';
|
||||
case TWO:
|
||||
soundPath = 'intro2-pixel';
|
||||
case ONE:
|
||||
soundPath = 'intro1-pixel';
|
||||
case GO:
|
||||
soundPath = 'introGo-pixel';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case THREE:
|
||||
soundPath = 'intro3';
|
||||
case TWO:
|
||||
soundPath = 'intro2';
|
||||
case ONE:
|
||||
soundPath = 'intro1';
|
||||
case GO:
|
||||
soundPath = 'introGo';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
|
||||
if (soundPath == null) return;
|
||||
|
||||
FunkinSound.playOnce(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME);
|
||||
return FunkinSound.playOnce(path, Constants.COUNTDOWN_VOLUME);
|
||||
}
|
||||
|
||||
public static function decrement(step:CountdownStep):CountdownStep
|
||||
|
|
|
@ -306,7 +306,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||
if (PlayState.instance?.currentDifficulty != null)
|
||||
{
|
||||
metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
|
||||
metadataDifficulty.text += PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase();
|
||||
}
|
||||
metadataDifficulty.scrollFactor.set(0, 0);
|
||||
metadata.add(metadataDifficulty);
|
||||
|
@ -430,7 +430,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
resume(this);
|
||||
}
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// to pause the game and get screenshots easy, press H on pause menu!
|
||||
if (FlxG.keys.justPressed.H)
|
||||
{
|
||||
|
|
|
@ -49,6 +49,7 @@ import funkin.play.notes.NoteSprite;
|
|||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.play.notes.SustainTrail;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.stage.Stage;
|
||||
|
@ -66,7 +67,7 @@ import lime.ui.Haptic;
|
|||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
import openfl.Lib;
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
||||
|
@ -444,7 +445,7 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
public var vocals:VoicesGroup;
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Discord RPC variables
|
||||
var storyDifficultyText:String = '';
|
||||
var iconRPC:String = '';
|
||||
|
@ -503,7 +504,7 @@ class PlayState extends MusicBeatSubState
|
|||
public var camGame:FlxCamera;
|
||||
|
||||
/**
|
||||
* The camera which contains, and controls visibility of, a video cutscene.
|
||||
* The camera which contains, and controls visibility of, a video cutscene, dialogue, pause menu and sticker transition.
|
||||
*/
|
||||
public var camCutscene:FlxCamera;
|
||||
|
||||
|
@ -578,7 +579,8 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// TODO: Refactor or document
|
||||
var generatedMusic:Bool = false;
|
||||
var perfectMode:Bool = false;
|
||||
|
||||
var skipEndingTransition:Bool = false;
|
||||
|
||||
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
|
||||
|
||||
|
@ -694,14 +696,9 @@ class PlayState extends MusicBeatSubState
|
|||
initMinimalMode();
|
||||
}
|
||||
initStrumlines();
|
||||
initPopups();
|
||||
|
||||
// Initialize the judgements and combo meter.
|
||||
comboPopUps = new PopUpStuff();
|
||||
comboPopUps.zIndex = 900;
|
||||
add(comboPopUps);
|
||||
comboPopUps.cameras = [camHUD];
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Initialize Discord Rich Presence.
|
||||
initDiscord();
|
||||
#end
|
||||
|
@ -741,7 +738,7 @@ class PlayState extends MusicBeatSubState
|
|||
rightWatermarkText.cameras = [camHUD];
|
||||
|
||||
// Initialize some debug stuff.
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Display the version number (and git commit hash) in the bottom right corner.
|
||||
this.rightWatermarkText.text = Constants.VERSION;
|
||||
|
||||
|
@ -900,7 +897,7 @@ class PlayState extends MusicBeatSubState
|
|||
health = Constants.HEALTH_STARTING;
|
||||
songScore = 0;
|
||||
Highscore.tallies.combo = 0;
|
||||
Countdown.performCountdown(currentStageId.startsWith('school'));
|
||||
Countdown.performCountdown();
|
||||
|
||||
needsReset = false;
|
||||
}
|
||||
|
@ -975,12 +972,12 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
pauseSubState.camera = camHUD;
|
||||
pauseSubState.camera = camCutscene;
|
||||
openSubState(pauseSubState);
|
||||
// boyfriendPos.put(); // TODO: Why is this here?
|
||||
}
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
#end
|
||||
}
|
||||
|
@ -1039,7 +1036,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// Disable updates, preventing animations in the background from playing.
|
||||
persistentUpdate = false;
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (FlxG.keys.pressed.THREE)
|
||||
{
|
||||
// TODO: Change the key or delete this?
|
||||
|
@ -1050,7 +1047,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
#end
|
||||
persistentDraw = false;
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
}
|
||||
#end
|
||||
|
||||
|
@ -1069,7 +1066,7 @@ class PlayState extends MusicBeatSubState
|
|||
moveToGameOver();
|
||||
}
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Game Over doesn't get his own variable because it's only used here
|
||||
DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
#end
|
||||
|
@ -1165,6 +1162,9 @@ class PlayState extends MusicBeatSubState
|
|||
// super.dispatchEvent(event) dispatches event to module scripts.
|
||||
super.dispatchEvent(event);
|
||||
|
||||
// Dispatch event to note kind scripts
|
||||
NoteKindManager.callEvent(event);
|
||||
|
||||
// Dispatch event to stage script.
|
||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
||||
|
||||
|
@ -1176,14 +1176,12 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// Dispatch event to conversation script.
|
||||
ScriptEventDispatcher.callEvent(currentConversation, event);
|
||||
|
||||
// TODO: Dispatch event to note scripts
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called before opening a new substate.
|
||||
* @param subState The substate to open.
|
||||
*/
|
||||
* Function called before opening a new substate.
|
||||
* @param subState The substate to open.
|
||||
*/
|
||||
public override function openSubState(subState:FlxSubState):Void
|
||||
{
|
||||
// If there is a substate which requires the game to continue,
|
||||
|
@ -1239,9 +1237,9 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Function called before closing the current substate.
|
||||
* @param subState
|
||||
*/
|
||||
* Function called before closing the current substate.
|
||||
* @param subState
|
||||
*/
|
||||
public override function closeSubState():Void
|
||||
{
|
||||
if (Std.isOfType(subState, PauseSubState))
|
||||
|
@ -1280,7 +1278,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Resume the countdown.
|
||||
Countdown.resumeCountdown();
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (startTimer.finished)
|
||||
{
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
|
||||
|
@ -1303,8 +1301,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Function called when the game window gains focus.
|
||||
*/
|
||||
* Function called when the game window gains focus.
|
||||
*/
|
||||
public override function onFocus():Void
|
||||
{
|
||||
if (VideoCutscene.isPlaying() && FlxG.autoPause && isGamePaused) VideoCutscene.pauseVideo();
|
||||
|
@ -1313,7 +1311,7 @@ class PlayState extends MusicBeatSubState
|
|||
VideoCutscene.resumeVideo();
|
||||
#end
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
|
||||
{
|
||||
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
|
||||
|
@ -1331,15 +1329,15 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Function called when the game window loses focus.
|
||||
*/
|
||||
* Function called when the game window loses focus.
|
||||
*/
|
||||
public override function onFocusLost():Void
|
||||
{
|
||||
#if html5
|
||||
if (FlxG.autoPause) VideoCutscene.pauseVideo();
|
||||
#end
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText,
|
||||
currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
#end
|
||||
|
@ -1348,64 +1346,13 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes any references to the current stage, then clears the stage cache,
|
||||
* then reloads all the stages.
|
||||
*
|
||||
* This is useful for when you want to edit a stage without reloading the whole game.
|
||||
* Reloading works on both the JSON and the HXC, if applicable.
|
||||
*
|
||||
* Call this by pressing F5 on a debug build.
|
||||
*/
|
||||
override function debug_refreshModules():Void
|
||||
* Call this by pressing F5 on a debug build.
|
||||
*/
|
||||
override function reloadAssets():Void
|
||||
{
|
||||
// Prevent further gameplay updates, which will try to reference dead objects.
|
||||
criticalFailure = true;
|
||||
|
||||
// Remove the current stage. If the stage gets deleted while it's still in use,
|
||||
// it'll probably crash the game or something.
|
||||
if (this.currentStage != null)
|
||||
{
|
||||
remove(currentStage);
|
||||
var event:ScriptEvent = new ScriptEvent(DESTROY, false);
|
||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
||||
currentStage = null;
|
||||
}
|
||||
|
||||
if (!overrideMusic)
|
||||
{
|
||||
// Stop the instrumental.
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.destroy();
|
||||
FlxG.sound.music = null;
|
||||
}
|
||||
|
||||
// Stop the vocals.
|
||||
if (vocals != null && vocals.exists)
|
||||
{
|
||||
vocals.destroy();
|
||||
vocals = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop the instrumental.
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.stop();
|
||||
}
|
||||
|
||||
// Stop the vocals.
|
||||
if (vocals != null && vocals.exists)
|
||||
{
|
||||
vocals.stop();
|
||||
}
|
||||
}
|
||||
|
||||
super.debug_refreshModules();
|
||||
|
||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
||||
ScriptEventDispatcher.callEvent(currentSong, event);
|
||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
||||
LoadingState.loadPlayState(lastParams);
|
||||
}
|
||||
|
||||
override function stepHit():Bool
|
||||
|
@ -1417,17 +1364,6 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
if (isGamePaused) return false;
|
||||
|
||||
if (!startingSong
|
||||
&& FlxG.sound.music != null
|
||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200
|
||||
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200))
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
resyncVocals();
|
||||
}
|
||||
|
||||
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||
|
||||
|
@ -1449,6 +1385,17 @@ class PlayState extends MusicBeatSubState
|
|||
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
|
||||
}
|
||||
|
||||
if (!startingSong
|
||||
&& FlxG.sound.music != null
|
||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100
|
||||
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100))
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
resyncVocals();
|
||||
}
|
||||
|
||||
// Only bop camera if zoom level is below 135%
|
||||
if (Preferences.zoomCamera
|
||||
&& FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
|
||||
|
@ -1501,9 +1448,6 @@ class PlayState extends MusicBeatSubState
|
|||
if (playerStrumline != null) playerStrumline.onBeatHit();
|
||||
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
||||
|
||||
// Make the characters dance on the beat
|
||||
danceOnBeat();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1515,28 +1459,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles characters dancing to the beat of the current song.
|
||||
*
|
||||
* TODO: Move some of this logic into `Bopper.hx`, or individual character scripts.
|
||||
*/
|
||||
function danceOnBeat():Void
|
||||
{
|
||||
if (currentStage == null) return;
|
||||
|
||||
// TODO: Add HEY! song events to Tutorial.
|
||||
if (Conductor.instance.currentBeat % 16 == 15
|
||||
&& currentStage.getDad().characterId == 'gf'
|
||||
&& Conductor.instance.currentBeat > 16
|
||||
&& Conductor.instance.currentBeat < 48)
|
||||
{
|
||||
currentStage.getBoyfriend().playAnimation('hey', true);
|
||||
currentStage.getDad().playAnimation('cheer', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the game and HUD cameras.
|
||||
*/
|
||||
* Initializes the game and HUD cameras.
|
||||
*/
|
||||
function initCameras():Void
|
||||
{
|
||||
camGame = new FunkinCamera('playStateCamGame');
|
||||
|
@ -1560,8 +1484,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Initializes the health bar on the HUD.
|
||||
*/
|
||||
* Initializes the health bar on the HUD.
|
||||
*/
|
||||
function initHealthBar():Void
|
||||
{
|
||||
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
|
||||
|
@ -1592,8 +1516,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Generates the stage and all its props.
|
||||
*/
|
||||
* Generates the stage and all its props.
|
||||
*/
|
||||
function initStage():Void
|
||||
{
|
||||
loadStage(currentStageId);
|
||||
|
@ -1613,10 +1537,10 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Loads stage data from cache, assembles the props,
|
||||
* and adds it to the state.
|
||||
* @param id
|
||||
*/
|
||||
* Loads stage data from cache, assembles the props,
|
||||
* and adds it to the state.
|
||||
* @param id
|
||||
*/
|
||||
function loadStage(id:String):Void
|
||||
{
|
||||
currentStage = StageRegistry.instance.fetchEntry(id);
|
||||
|
@ -1634,7 +1558,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Add the stage to the scene.
|
||||
this.add(currentStage);
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
FlxG.console.registerObject('stage', currentStage);
|
||||
#end
|
||||
}
|
||||
|
@ -1657,8 +1581,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Generates the character sprites and adds them to the stage.
|
||||
*/
|
||||
* Generates the character sprites and adds them to the stage.
|
||||
*/
|
||||
function initCharacters():Void
|
||||
{
|
||||
if (currentSong == null || currentChart == null)
|
||||
|
@ -1737,7 +1661,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
currentStage.addCharacter(girlfriend, GF);
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
FlxG.console.registerObject('gf', girlfriend);
|
||||
#end
|
||||
}
|
||||
|
@ -1746,7 +1670,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
currentStage.addCharacter(boyfriend, BF);
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
FlxG.console.registerObject('bf', boyfriend);
|
||||
#end
|
||||
}
|
||||
|
@ -1757,7 +1681,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Camera starts at dad.
|
||||
cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y);
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
FlxG.console.registerObject('dad', dad);
|
||||
#end
|
||||
}
|
||||
|
@ -1768,8 +1692,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs the strumlines for each player.
|
||||
*/
|
||||
* Constructs the strumlines for each player.
|
||||
*/
|
||||
function initStrumlines():Void
|
||||
{
|
||||
var noteStyleId:String = currentChart.noteStyle;
|
||||
|
@ -1801,11 +1725,26 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Initializes the Discord Rich Presence.
|
||||
*/
|
||||
* Configures the judgement and combo popups.
|
||||
*/
|
||||
function initPopups():Void
|
||||
{
|
||||
var noteStyleId:String = currentChart.noteStyle;
|
||||
var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||
// Initialize the judgements and combo meter.
|
||||
comboPopUps = new PopUpStuff(noteStyle);
|
||||
comboPopUps.zIndex = 900;
|
||||
add(comboPopUps);
|
||||
comboPopUps.cameras = [camHUD];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Discord Rich Presence.
|
||||
*/
|
||||
function initDiscord():Void
|
||||
{
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
storyDifficultyText = difficultyString();
|
||||
iconRPC = currentSong.player2;
|
||||
|
||||
|
@ -1836,9 +1775,9 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Initializes the song (applying the chart, generating the notes, etc.)
|
||||
* Should be done before the countdown starts.
|
||||
*/
|
||||
* Initializes the song (applying the chart, generating the notes, etc.)
|
||||
* Should be done before the countdown starts.
|
||||
*/
|
||||
function generateSong():Void
|
||||
{
|
||||
if (currentChart == null)
|
||||
|
@ -1869,8 +1808,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Read note data from the chart and generate the notes.
|
||||
*/
|
||||
* Read note data from the chart and generate the notes.
|
||||
*/
|
||||
function regenNoteData(startTime:Float = 0):Void
|
||||
{
|
||||
Highscore.tallies.combo = 0;
|
||||
|
@ -1923,27 +1862,26 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Prepares to start the countdown.
|
||||
* Ends any running cutscenes, creates the strumlines, and starts the countdown.
|
||||
* This is public so that scripts can call it.
|
||||
*/
|
||||
* Prepares to start the countdown.
|
||||
* Ends any running cutscenes, creates the strumlines, and starts the countdown.
|
||||
* This is public so that scripts can call it.
|
||||
*/
|
||||
public function startCountdown():Void
|
||||
{
|
||||
// If Countdown.performCountdown returns false, then the countdown was canceled by a script.
|
||||
var result:Bool = Countdown.performCountdown(currentStageId.startsWith('school'));
|
||||
var result:Bool = Countdown.performCountdown();
|
||||
if (!result) return;
|
||||
|
||||
isInCutscene = false;
|
||||
camCutscene.visible = false;
|
||||
|
||||
// TODO: Maybe tween in the camera after any cutscenes.
|
||||
camHUD.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialogue cutscene with the given ID.
|
||||
* This is used by song scripts to display dialogue.
|
||||
*/
|
||||
* Displays a dialogue cutscene with the given ID.
|
||||
* This is used by song scripts to display dialogue.
|
||||
*/
|
||||
public function startConversation(conversationId:String):Void
|
||||
{
|
||||
isInCutscene = true;
|
||||
|
@ -1963,8 +1901,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handler function called when a conversation ends.
|
||||
*/
|
||||
* Handler function called when a conversation ends.
|
||||
*/
|
||||
function onConversationComplete():Void
|
||||
{
|
||||
isInCutscene = false;
|
||||
|
@ -1983,8 +1921,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Starts playing the song after the countdown has completed.
|
||||
*/
|
||||
* Starts playing the song after the countdown has completed.
|
||||
*/
|
||||
function startSong():Void
|
||||
{
|
||||
startingSong = false;
|
||||
|
@ -2000,7 +1938,9 @@ class PlayState extends MusicBeatSubState
|
|||
return;
|
||||
}
|
||||
|
||||
FlxG.sound.music.onComplete = endSong.bind(false);
|
||||
FlxG.sound.music.onComplete = function() {
|
||||
endSong(skipEndingTransition);
|
||||
};
|
||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||
FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset);
|
||||
|
@ -2017,7 +1957,7 @@ class PlayState extends MusicBeatSubState
|
|||
vocals.pitch = playbackRate;
|
||||
resyncVocals();
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence (with Time Left)
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
|
||||
#end
|
||||
|
@ -2032,26 +1972,28 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Resyncronize the vocal tracks if they have become offset from the instrumental.
|
||||
*/
|
||||
* Resyncronize the vocal tracks if they have become offset from the instrumental.
|
||||
*/
|
||||
function resyncVocals():Void
|
||||
{
|
||||
if (vocals == null) return;
|
||||
|
||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||
if (!FlxG.sound.music.playing) return;
|
||||
|
||||
if (!(FlxG.sound.music?.playing ?? false)) return;
|
||||
var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset;
|
||||
FlxG.sound.music.pause();
|
||||
vocals.pause();
|
||||
|
||||
FlxG.sound.music.play(FlxG.sound.music.time);
|
||||
FlxG.sound.music.time = timeToPlayAt;
|
||||
FlxG.sound.music.play(false, timeToPlayAt);
|
||||
|
||||
vocals.time = FlxG.sound.music.time;
|
||||
vocals.play(false, FlxG.sound.music.time);
|
||||
vocals.time = timeToPlayAt;
|
||||
vocals.play(false, timeToPlayAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position and contents of the score display.
|
||||
*/
|
||||
* Updates the position and contents of the score display.
|
||||
*/
|
||||
function updateScoreText():Void
|
||||
{
|
||||
// TODO: Add functionality for modules to update the score text.
|
||||
|
@ -2068,8 +2010,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the values of the health bar.
|
||||
*/
|
||||
* Updates the values of the health bar.
|
||||
*/
|
||||
function updateHealthBar():Void
|
||||
{
|
||||
if (isBotPlayMode)
|
||||
|
@ -2083,8 +2025,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Callback executed when one of the note keys is pressed.
|
||||
*/
|
||||
* Callback executed when one of the note keys is pressed.
|
||||
*/
|
||||
function onKeyPress(event:PreciseInputEvent):Void
|
||||
{
|
||||
if (isGamePaused) return;
|
||||
|
@ -2094,8 +2036,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Callback executed when one of the note keys is released.
|
||||
*/
|
||||
* Callback executed when one of the note keys is released.
|
||||
*/
|
||||
function onKeyRelease(event:PreciseInputEvent):Void
|
||||
{
|
||||
if (isGamePaused) return;
|
||||
|
@ -2105,8 +2047,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles opponent note hits and player note misses.
|
||||
*/
|
||||
* Handles opponent note hits and player note misses.
|
||||
*/
|
||||
function processNotes(elapsed:Float):Void
|
||||
{
|
||||
if (playerStrumline?.notes?.members == null || opponentStrumline?.notes?.members == null) return;
|
||||
|
@ -2279,10 +2221,14 @@ class PlayState extends MusicBeatSubState
|
|||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
if (event.eventCanceled) continue;
|
||||
|
||||
// Skip handling the miss in botplay!
|
||||
if (!isBotPlayMode)
|
||||
{
|
||||
// Judge the miss.
|
||||
// NOTE: This is what handles the scoring.
|
||||
trace('Missed note! ${note.noteData}');
|
||||
onNoteMiss(note, event.playSound, event.healthChange);
|
||||
}
|
||||
|
||||
note.handledMiss = true;
|
||||
}
|
||||
|
@ -2324,8 +2270,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Spitting out the input for ravy 🙇♂️!!
|
||||
*/
|
||||
* Spitting out the input for ravy 🙇♂️!!
|
||||
*/
|
||||
var inputSpitter:Array<ScoreInput> = [];
|
||||
|
||||
function handleSkippedNotes():Void
|
||||
|
@ -2349,9 +2295,9 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* PreciseInputEvents are put into a queue between update() calls,
|
||||
* and then processed here.
|
||||
*/
|
||||
* PreciseInputEvents are put into a queue between update() calls,
|
||||
* and then processed here.
|
||||
*/
|
||||
function processInputQueue():Void
|
||||
{
|
||||
if (inputPressQueue.length + inputReleaseQueue.length == 0) return;
|
||||
|
@ -2379,9 +2325,16 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
playerStrumline.pressKey(input.noteDirection);
|
||||
|
||||
// Don't credit or penalize inputs in Bot Play.
|
||||
if (isBotPlayMode) continue;
|
||||
|
||||
var notesInDirection:Array<NoteSprite> = notesByDirection[input.noteDirection];
|
||||
|
||||
if (!Constants.GHOST_TAPPING && notesInDirection.length == 0)
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
if ((!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
|
||||
#else
|
||||
if (notesInDirection.length == 0)
|
||||
#end
|
||||
{
|
||||
// Pressed a wrong key with no notes nearby.
|
||||
// Perform a ghost miss (anti-spam).
|
||||
|
@ -2391,16 +2344,6 @@ class PlayState extends MusicBeatSubState
|
|||
playerStrumline.playPress(input.noteDirection);
|
||||
trace('PENALTY Score: ${songScore}');
|
||||
}
|
||||
else if (Constants.GHOST_TAPPING && (!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
|
||||
{
|
||||
// Pressed a wrong key with notes visible on-screen.
|
||||
// Perform a ghost miss (anti-spam).
|
||||
ghostNoteMiss(input.noteDirection, notesInRange.length > 0);
|
||||
|
||||
// Play the strumline animation.
|
||||
playerStrumline.playPress(input.noteDirection);
|
||||
trace('PENALTY Score: ${songScore}');
|
||||
}
|
||||
else if (notesInDirection.length == 0)
|
||||
{
|
||||
// Press a key with no penalty.
|
||||
|
@ -2493,9 +2436,9 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when a note leaves the screen and is considered missed by the player.
|
||||
* @param note
|
||||
*/
|
||||
* Called when a note leaves the screen and is considered missed by the player.
|
||||
* @param note
|
||||
*/
|
||||
function onNoteMiss(note:NoteSprite, playSound:Bool = false, healthChange:Float):Void
|
||||
{
|
||||
// If we are here, we already CALLED the onNoteMiss script hook!
|
||||
|
@ -2551,13 +2494,13 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when a player presses a key with no note present.
|
||||
* Scripts can modify the amount of health/score lost, whether player animations or sounds are used,
|
||||
* or even cancel the event entirely.
|
||||
*
|
||||
* @param direction
|
||||
* @param hasPossibleNotes
|
||||
*/
|
||||
* Called when a player presses a key with no note present.
|
||||
* Scripts can modify the amount of health/score lost, whether player animations or sounds are used,
|
||||
* or even cancel the event entirely.
|
||||
*
|
||||
* @param direction
|
||||
* @param hasPossibleNotes
|
||||
*/
|
||||
function ghostNoteMiss(direction:NoteDirection, hasPossibleNotes:Bool = true):Void
|
||||
{
|
||||
var event:GhostMissNoteScriptEvent = new GhostMissNoteScriptEvent(direction, // Direction missed in.
|
||||
|
@ -2606,17 +2549,11 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Debug keys. Disabled while in cutscenes.
|
||||
*/
|
||||
* Debug keys. Disabled while in cutscenes.
|
||||
*/
|
||||
function debugKeyShit():Void
|
||||
{
|
||||
#if !debug
|
||||
perfectMode = false;
|
||||
#else
|
||||
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
|
||||
#end
|
||||
|
||||
#if CHART_EDITOR_SUPPORTED
|
||||
#if FEATURE_CHART_EDITOR
|
||||
// Open the stage editor overlaying the current state.
|
||||
if (controls.DEBUG_STAGE)
|
||||
{
|
||||
|
@ -2646,7 +2583,10 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
#end
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// H: Hide the HUD.
|
||||
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
|
||||
|
||||
// 1: End the song immediately.
|
||||
if (FlxG.keys.justPressed.ONE) endSong(true);
|
||||
|
||||
|
@ -2660,7 +2600,7 @@ class PlayState extends MusicBeatSubState
|
|||
// 9: Toggle the old icon.
|
||||
if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon();
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// PAGEUP: Skip forward two sections.
|
||||
// SHIFT+PAGEUP: Skip forward twenty sections.
|
||||
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||
|
@ -2673,8 +2613,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles applying health, score, and ratings.
|
||||
*/
|
||||
* Handles applying health, score, and ratings.
|
||||
*/
|
||||
function applyScore(score:Int, daRating:String, healthChange:Float, isComboBreak:Bool)
|
||||
{
|
||||
switch (daRating)
|
||||
|
@ -2706,8 +2646,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles rating popups when a note is hit.
|
||||
*/
|
||||
* Handles rating popups when a note is hit.
|
||||
*/
|
||||
function popUpScore(daRating:String, ?combo:Int):Void
|
||||
{
|
||||
if (daRating == 'miss')
|
||||
|
@ -2764,10 +2704,10 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle keyboard inputs during cutscenes.
|
||||
* This includes advancing conversations and skipping videos.
|
||||
* @param elapsed Time elapsed since last game update.
|
||||
*/
|
||||
* Handle keyboard inputs during cutscenes.
|
||||
* This includes advancing conversations and skipping videos.
|
||||
* @param elapsed Time elapsed since last game update.
|
||||
*/
|
||||
function handleCutsceneKeys(elapsed:Float):Void
|
||||
{
|
||||
if (isGamePaused) return;
|
||||
|
@ -2811,20 +2751,20 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle logic for actually skipping a video cutscene after it has been held.
|
||||
*/
|
||||
* Handle logic for actually skipping a video cutscene after it has been held.
|
||||
*/
|
||||
function skipVideoCutscene():Void
|
||||
{
|
||||
VideoCutscene.finishVideo();
|
||||
}
|
||||
|
||||
/**
|
||||
* End the song. Handle saving high scores and transitioning to the results screen.
|
||||
*
|
||||
* Broadcasts an `onSongEnd` event, which can be cancelled to prevent the song from ending (for a cutscene or something).
|
||||
* Remember to call `endSong` again when the song should actually end!
|
||||
* @param rightGoddamnNow If true, don't play the fancy animation where you zoom onto Girlfriend. Used after a cutscene.
|
||||
*/
|
||||
* End the song. Handle saving high scores and transitioning to the results screen.
|
||||
*
|
||||
* Broadcasts an `onSongEnd` event, which can be cancelled to prevent the song from ending (for a cutscene or something).
|
||||
* Remember to call `endSong` again when the song should actually end!
|
||||
* @param rightGoddamnNow If true, don't play the fancy animation where you zoom onto Girlfriend. Used after a cutscene.
|
||||
*/
|
||||
public function endSong(rightGoddamnNow:Bool = false):Void
|
||||
{
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
|
||||
|
@ -2979,11 +2919,16 @@ class PlayState extends MusicBeatSubState
|
|||
FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() {
|
||||
// no camFollow so it centers on horror tree
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
var targetVariation:String = currentVariation;
|
||||
if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation))
|
||||
{
|
||||
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||
}
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetVariation: currentVariation,
|
||||
targetVariation: targetVariation,
|
||||
cameraFollowPoint: cameraFollowPoint.getPosition(),
|
||||
});
|
||||
});
|
||||
|
@ -2991,11 +2936,16 @@ class PlayState extends MusicBeatSubState
|
|||
else
|
||||
{
|
||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
var targetVariation:String = currentVariation;
|
||||
if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation))
|
||||
{
|
||||
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||
}
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetVariation: currentVariation,
|
||||
targetVariation: targetVariation,
|
||||
cameraFollowPoint: cameraFollowPoint.getPosition(),
|
||||
});
|
||||
}
|
||||
|
@ -3029,8 +2979,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Perform necessary cleanup before leaving the PlayState.
|
||||
*/
|
||||
* Perform necessary cleanup before leaving the PlayState.
|
||||
*/
|
||||
function performCleanup():Void
|
||||
{
|
||||
// If the camera is being tweened, stop it.
|
||||
|
@ -3081,14 +3031,15 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
GameOverSubState.reset();
|
||||
PauseSubState.reset();
|
||||
Countdown.reset();
|
||||
|
||||
// Clear the static reference to this state.
|
||||
instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the camera zoom animation and then move to the results screen once it's done.
|
||||
*/
|
||||
* Play the camera zoom animation and then move to the results screen once it's done.
|
||||
*/
|
||||
function zoomIntoResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void
|
||||
{
|
||||
trace('WENT TO RESULTS SCREEN!');
|
||||
|
@ -3152,17 +3103,17 @@ class PlayState extends MusicBeatSubState
|
|||
// Zoom over to the Results screen.
|
||||
// TODO: Re-enable this.
|
||||
/*
|
||||
FlxTween.tween(FlxG.camera, {zoom: 1200}, 1.1,
|
||||
{
|
||||
ease: FlxEase.expoIn,
|
||||
});
|
||||
*/
|
||||
FlxTween.tween(FlxG.camera, {zoom: 1200}, 1.1,
|
||||
{
|
||||
ease: FlxEase.expoIn,
|
||||
});
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the results screen right goddamn now.
|
||||
*/
|
||||
* Move to the results screen right goddamn now.
|
||||
*/
|
||||
function moveToResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void
|
||||
{
|
||||
persistentUpdate = false;
|
||||
|
@ -3202,8 +3153,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Pauses music and vocals easily.
|
||||
*/
|
||||
* Pauses music and vocals easily.
|
||||
*/
|
||||
public function pauseMusic():Void
|
||||
{
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause();
|
||||
|
@ -3211,8 +3162,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Resets the camera's zoom level and focus point.
|
||||
*/
|
||||
* Resets the camera's zoom level and focus point.
|
||||
*/
|
||||
public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true):Void
|
||||
{
|
||||
// Cancel camera tweens if any are active.
|
||||
|
@ -3234,8 +3185,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the camera follow point's position and tweens the camera there.
|
||||
*/
|
||||
* Sets the camera follow point's position and tweens the camera there.
|
||||
*/
|
||||
public function tweenCameraToPosition(?x:Float, ?y:Float, ?duration:Float, ?ease:Null<Float->Float>):Void
|
||||
{
|
||||
cameraFollowPoint.setPosition(x, y);
|
||||
|
@ -3243,8 +3194,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Disables camera following and tweens the camera to the follow point manually.
|
||||
*/
|
||||
* Disables camera following and tweens the camera to the follow point manually.
|
||||
*/
|
||||
public function tweenCameraToFollowPoint(?duration:Float, ?ease:Null<Float->Float>):Void
|
||||
{
|
||||
// Cancel the current tween if it's active.
|
||||
|
@ -3281,8 +3232,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Tweens the camera zoom to the desired amount.
|
||||
*/
|
||||
* Tweens the camera zoom to the desired amount.
|
||||
*/
|
||||
public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?direct:Bool, ?ease:Null<Float->Float>):Void
|
||||
{
|
||||
// Cancel the current tween if it's active.
|
||||
|
@ -3313,8 +3264,8 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
/**
|
||||
* Cancel all active camera tweens simultaneously.
|
||||
*/
|
||||
* Cancel all active camera tweens simultaneously.
|
||||
*/
|
||||
public function cancelAllCameraTweens()
|
||||
{
|
||||
cancelCameraFollowTween();
|
||||
|
@ -3324,8 +3275,8 @@ class PlayState extends MusicBeatSubState
|
|||
var prevScrollTargets:Array<Dynamic> = []; // used to snap scroll speed when things go unruely
|
||||
|
||||
/**
|
||||
* The magical function that shall tween the scroll speed.
|
||||
*/
|
||||
* The magical function that shall tween the scroll speed.
|
||||
*/
|
||||
public function tweenScrollSpeed(?speed:Float, ?duration:Float, ?ease:Null<Float->Float>, strumlines:Array<String>):Void
|
||||
{
|
||||
// Cancel the current tween if it's active.
|
||||
|
@ -3375,12 +3326,12 @@ class PlayState extends MusicBeatSubState
|
|||
scrollSpeedTweens = [];
|
||||
}
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
/**
|
||||
* Jumps forward or backward a number of sections in the song.
|
||||
* Accounts for BPM changes, does not prevent death from skipped notes.
|
||||
* @param sections The number of sections to jump, negative to go backwards.
|
||||
*/
|
||||
* Jumps forward or backward a number of sections in the song.
|
||||
* Accounts for BPM changes, does not prevent death from skipped notes.
|
||||
* @param sections The number of sections to jump, negative to go backwards.
|
||||
*/
|
||||
function changeSection(sections:Int):Void
|
||||
{
|
||||
// FlxG.sound.music.pause();
|
||||
|
|
|
@ -70,6 +70,8 @@ class ResultState extends MusicBeatSubState
|
|||
delay:Float
|
||||
}> = [];
|
||||
|
||||
var playerCharacterId:Null<String>;
|
||||
|
||||
var rankBg:FunkinSprite;
|
||||
final cameraBG:FunkinCamera;
|
||||
final cameraScroll:FunkinCamera;
|
||||
|
@ -164,7 +166,7 @@ class ResultState extends MusicBeatSubState
|
|||
add(soundSystem);
|
||||
|
||||
// Fetch playable character data. Default to BF on the results screen if we can't find it.
|
||||
var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
|
||||
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
|
||||
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
|
||||
|
||||
trace('Got playable character: ${playerCharacter?.getName()}');
|
||||
|
@ -189,7 +191,7 @@ class ResultState extends MusicBeatSubState
|
|||
if (!(animData.looped ?? true))
|
||||
{
|
||||
// Animation is not looped.
|
||||
animation.onAnimationFinish.add((_name:String) -> {
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
if (animation != null)
|
||||
{
|
||||
animation.anim.pause();
|
||||
|
@ -198,7 +200,7 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
else if (animData.loopFrameLabel != null)
|
||||
{
|
||||
animation.onAnimationFinish.add((_name:String) -> {
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
if (animation != null)
|
||||
{
|
||||
animation.playAnimation(animData.loopFrameLabel ?? ''); // unpauses this anim, since it's on PlayOnce!
|
||||
|
@ -207,7 +209,7 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
else if (animData.loopFrame != null)
|
||||
{
|
||||
animation.onAnimationFinish.add((_name:String) -> {
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
if (animation != null)
|
||||
{
|
||||
animation.anim.curFrame = animData.loopFrame ?? 0;
|
||||
|
@ -742,6 +744,7 @@ class ResultState extends MusicBeatSubState
|
|||
FlxG.switchState(FreeplayState.build(
|
||||
{
|
||||
{
|
||||
character: playerCharacterId ?? "bf",
|
||||
fromResults:
|
||||
{
|
||||
oldRank: Scoring.calculateRank(params?.prevScoreData),
|
||||
|
@ -799,8 +802,9 @@ typedef ResultsStateParams =
|
|||
|
||||
/**
|
||||
* The character ID for the song we just played.
|
||||
* @default `bf`
|
||||
*/
|
||||
var characterId:String;
|
||||
var ?characterId:String;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is a new highscore
|
||||
|
|
|
@ -109,8 +109,6 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
var loop:Bool = animData.looped;
|
||||
|
||||
this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop);
|
||||
|
||||
animFinished = false;
|
||||
}
|
||||
|
||||
public override function hasAnimation(name:String):Bool
|
||||
|
@ -124,17 +122,17 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
*/
|
||||
public override function isAnimationFinished():Bool
|
||||
{
|
||||
return animFinished;
|
||||
return mainSprite.isAnimationFinished();
|
||||
}
|
||||
|
||||
function loadAtlasSprite():FlxAtlasSprite
|
||||
{
|
||||
trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.');
|
||||
|
||||
var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas(_data.assetPath, 'shared'));
|
||||
var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas(_data.assetPath));
|
||||
|
||||
sprite.onAnimationFinish.removeAll();
|
||||
sprite.onAnimationFinish.add(this.onAnimationFinished);
|
||||
// sprite.onAnimationComplete.removeAll();
|
||||
sprite.onAnimationComplete.add(this.onAnimationFinished);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
@ -152,7 +150,6 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
// Make the game hold on the last frame.
|
||||
this.mainSprite.cleanupAnimation(prefix);
|
||||
// currentAnimName = null;
|
||||
animFinished = true;
|
||||
|
||||
// Fallback to idle!
|
||||
// playAnimation('idle', true, false);
|
||||
|
@ -165,6 +162,11 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
|
||||
this.mainSprite = sprite;
|
||||
|
||||
// This forces the atlas to recalcuate its width and height
|
||||
this.mainSprite.alpha = 0.0001;
|
||||
this.mainSprite.draw();
|
||||
this.mainSprite.alpha = 1.0;
|
||||
|
||||
var feetPos:FlxPoint = feetPosition;
|
||||
this.updateHitbox();
|
||||
|
||||
|
|
|
@ -118,22 +118,6 @@ class BaseCharacter extends Bopper
|
|||
*/
|
||||
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
|
||||
|
||||
override function set_animOffsets(value:Array<Float>):Array<Float>
|
||||
{
|
||||
if (animOffsets == null) value = [0, 0];
|
||||
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
|
||||
|
||||
// Make sure animOffets are halved when scale is 0.5.
|
||||
var xDiff = (animOffsets[0] * this.scale.x / (this.isPixel ? 6 : 1)) - value[0];
|
||||
var yDiff = (animOffsets[1] * this.scale.y / (this.isPixel ? 6 : 1)) - value[1];
|
||||
|
||||
// Call the super function so that camera focus point is not affected.
|
||||
super.set_x(this.x + xDiff);
|
||||
super.set_y(this.y + yDiff);
|
||||
|
||||
return animOffsets = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the x position changes, other than via changing the animation offset,
|
||||
* then we need to update the camera focus point.
|
||||
|
@ -434,7 +418,6 @@ class BaseCharacter extends Bopper
|
|||
else
|
||||
{
|
||||
// Play the idle animation.
|
||||
// trace('${characterId}: attempting dance');
|
||||
dance(true);
|
||||
}
|
||||
}
|
||||
|
@ -528,6 +511,9 @@ class BaseCharacter extends Bopper
|
|||
{
|
||||
super.onNoteHit(event);
|
||||
|
||||
// If another script cancelled the event, don't do anything.
|
||||
if (event.eventCanceled) return;
|
||||
|
||||
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
||||
{
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
|
@ -560,6 +546,9 @@ class BaseCharacter extends Bopper
|
|||
{
|
||||
super.onNoteMiss(event);
|
||||
|
||||
// If another script cancelled the event, don't do anything.
|
||||
if (event.eventCanceled) return;
|
||||
|
||||
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
||||
{
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
|
@ -611,7 +600,7 @@ class BaseCharacter extends Bopper
|
|||
/**
|
||||
* Every time a wrong key is pressed, play the miss animation if we are Boyfriend.
|
||||
*/
|
||||
public override function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
||||
public override function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void
|
||||
{
|
||||
super.onNoteGhostMiss(event);
|
||||
|
||||
|
@ -651,7 +640,6 @@ class BaseCharacter extends Bopper
|
|||
|
||||
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reversed:Bool = false):Void
|
||||
{
|
||||
// FlxG.watch.addQuick('playAnim(${characterName})', name);
|
||||
super.playAnimation(name, restart, ignoreOther, reversed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -305,6 +305,8 @@ class CharacterDataParser
|
|||
icon = "darnell";
|
||||
case "senpai-angry":
|
||||
icon = "senpai";
|
||||
case "spooky-dark":
|
||||
icon = "spooky";
|
||||
case "tankman-atlas":
|
||||
icon = "tankman";
|
||||
}
|
||||
|
|
|
@ -62,11 +62,13 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
}
|
||||
}
|
||||
|
||||
var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
||||
var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath);
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
trace('Multi-Sparrow atlas could not load PRIMARY texture: ${_data.assetPath}');
|
||||
FlxG.log.error('Multi-Sparrow atlas could not load PRIMARY texture: ${_data.assetPath}');
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -76,7 +78,7 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
|
||||
for (asset in assetList)
|
||||
{
|
||||
var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset, 'shared');
|
||||
var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset);
|
||||
// If we don't do this, the unused textures will be removed as soon as they're loaded.
|
||||
|
||||
if (subTexture == null)
|
||||
|
|
|
@ -30,7 +30,7 @@ class PackerCharacter extends BaseCharacter
|
|||
{
|
||||
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath, 'shared');
|
||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
{
|
||||
trace('Could not load Packer sprite: ${_data.assetPath}');
|
||||
|
|
|
@ -33,7 +33,7 @@ class SparrowCharacter extends BaseCharacter
|
|||
{
|
||||
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
{
|
||||
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
||||
|
|
|
@ -150,13 +150,17 @@ class HealthIcon extends FunkinSprite
|
|||
{
|
||||
if (characterId == 'bf-old')
|
||||
{
|
||||
isPixel = PlayState.instance.currentStage.getBoyfriend().isPixel;
|
||||
PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterId = 'bf-old';
|
||||
isPixel = false;
|
||||
loadCharacter(characterId);
|
||||
}
|
||||
|
||||
lerpIconSize(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,31 +204,45 @@ class HealthIcon extends FunkinSprite
|
|||
|
||||
if (bopEvery != 0)
|
||||
{
|
||||
// Lerp the health icon back to its normal size,
|
||||
// while maintaining aspect ratio.
|
||||
if (this.width > this.height)
|
||||
{
|
||||
// Apply linear interpolation while accounting for frame rate.
|
||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
|
||||
|
||||
setGraphicSize(targetSize, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
|
||||
|
||||
setGraphicSize(0, targetSize);
|
||||
}
|
||||
lerpIconSize();
|
||||
|
||||
// Lerp the health icon back to its normal angle.
|
||||
this.angle = MathUtil.coolLerp(this.angle, 0, 0.15);
|
||||
|
||||
this.updateHitbox();
|
||||
}
|
||||
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the calculation to lerp the icon size. Usually called every frame, but can be forced to the target size.
|
||||
* Mainly forced when changing to old icon to not have a weird lerp related to changing from pixel icon to non-pixel old icon
|
||||
* @param force Force the icon immedialtely to be the target size. Defaults to false.
|
||||
*/
|
||||
function lerpIconSize(force:Bool = false):Void
|
||||
{
|
||||
// Lerp the health icon back to its normal size,
|
||||
// while maintaining aspect ratio.
|
||||
if (this.width > this.height)
|
||||
{
|
||||
// Apply linear interpolation while accounting for frame rate.
|
||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
|
||||
|
||||
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.x);
|
||||
|
||||
setGraphicSize(targetSize, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
|
||||
|
||||
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.y);
|
||||
|
||||
setGraphicSize(0, targetSize);
|
||||
}
|
||||
|
||||
this.updateHitbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position (and status) of the health icon.
|
||||
*/
|
||||
|
@ -412,6 +430,8 @@ class HealthIcon extends FunkinSprite
|
|||
|
||||
isLegacyStyle = !isNewSpritesheet(charId);
|
||||
|
||||
trace(' Loading health icon for character: $charId (legacy: $isLegacyStyle)');
|
||||
|
||||
if (!isLegacyStyle)
|
||||
{
|
||||
loadSparrow('icons/icon-$charId');
|
||||
|
|
|
@ -8,58 +8,51 @@ import funkin.graphics.FunkinSprite;
|
|||
import funkin.play.PlayState;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.EaseUtil;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
@:nullSafety
|
||||
class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
||||
{
|
||||
public var offsets:Array<Int> = [0, 0];
|
||||
/**
|
||||
* The current note style to use. This determines which graphics to display.
|
||||
* For example, Week 6 uses the `pixel` note style, and mods can create their own.
|
||||
*/
|
||||
var noteStyle:NoteStyle;
|
||||
|
||||
override public function new()
|
||||
override public function new(noteStyle:NoteStyle)
|
||||
{
|
||||
super();
|
||||
|
||||
this.noteStyle = noteStyle;
|
||||
}
|
||||
|
||||
public function displayRating(daRating:String):Void
|
||||
public function displayRating(daRating:Null<String>)
|
||||
{
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
if (daRating == null) daRating = "good";
|
||||
|
||||
var ratingPath:String = daRating;
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
||||
|
||||
var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
|
||||
rating.scrollFactor.set(0.2, 0.2);
|
||||
var rating:Null<FunkinSprite> = noteStyle.buildJudgementSprite(daRating);
|
||||
if (rating == null) return;
|
||||
|
||||
rating.zIndex = 1000;
|
||||
rating.x = (FlxG.width * 0.474) + offsets[0];
|
||||
// rating.x -= FlxG.camera.scroll.x * 0.2;
|
||||
rating.y = (FlxG.camera.height * 0.45 - 60) + offsets[1];
|
||||
|
||||
rating.x = (FlxG.width * 0.474);
|
||||
rating.x -= rating.width / 2;
|
||||
rating.y = (FlxG.camera.height * 0.45 - 60);
|
||||
rating.y -= rating.height / 2;
|
||||
|
||||
var offsets = noteStyle.getJudgementSpriteOffsets(daRating);
|
||||
rating.x += offsets[0];
|
||||
rating.y += offsets[1];
|
||||
|
||||
rating.acceleration.y = 550;
|
||||
rating.velocity.y -= FlxG.random.int(140, 175);
|
||||
rating.velocity.x -= FlxG.random.int(0, 10);
|
||||
|
||||
add(rating);
|
||||
|
||||
var fadeEase = null;
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
|
||||
rating.antialiasing = false;
|
||||
rating.pixelPerfectRender = true;
|
||||
rating.pixelPerfectPosition = true;
|
||||
fadeEase = EaseUtil.stepped(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
rating.setGraphicSize(Std.int(rating.width * 0.65));
|
||||
rating.antialiasing = true;
|
||||
}
|
||||
rating.updateHitbox();
|
||||
|
||||
rating.x -= rating.width / 2;
|
||||
rating.y -= rating.height / 2;
|
||||
var fadeEase = noteStyle.isJudgementSpritePixel(daRating) ? EaseUtil.stepped(2) : null;
|
||||
|
||||
FlxTween.tween(rating, {alpha: 0}, 0.2,
|
||||
{
|
||||
|
@ -70,62 +63,10 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
|||
startDelay: Conductor.instance.beatLengthMs * 0.001,
|
||||
ease: fadeEase
|
||||
});
|
||||
|
||||
trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
|
||||
}
|
||||
|
||||
public function displayCombo(?combo:Int = 0):Int
|
||||
public function displayCombo(combo:Int = 0):Void
|
||||
{
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
if (combo == null) combo = 0;
|
||||
|
||||
var pixelShitPart1:String = "";
|
||||
var pixelShitPart2:String = '';
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
pixelShitPart1 = 'weeb/pixelUI/';
|
||||
pixelShitPart2 = '-pixel';
|
||||
}
|
||||
var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
|
||||
comboSpr.y = (FlxG.camera.height * 0.44) + offsets[1];
|
||||
comboSpr.x = (FlxG.width * 0.507) + offsets[0];
|
||||
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
||||
|
||||
comboSpr.acceleration.y = 600;
|
||||
comboSpr.velocity.y -= 150;
|
||||
comboSpr.velocity.x += FlxG.random.int(1, 10);
|
||||
|
||||
// add(comboSpr);
|
||||
|
||||
var fadeEase = null;
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 1));
|
||||
comboSpr.antialiasing = false;
|
||||
comboSpr.pixelPerfectRender = true;
|
||||
comboSpr.pixelPerfectPosition = true;
|
||||
fadeEase = EaseUtil.stepped(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * 0.7));
|
||||
comboSpr.antialiasing = true;
|
||||
}
|
||||
comboSpr.updateHitbox();
|
||||
|
||||
FlxTween.tween(comboSpr, {alpha: 0}, 0.2,
|
||||
{
|
||||
onComplete: function(tween:FlxTween) {
|
||||
remove(comboSpr, true);
|
||||
comboSpr.destroy();
|
||||
},
|
||||
startDelay: Conductor.instance.beatLengthMs * 0.001,
|
||||
ease: fadeEase
|
||||
});
|
||||
|
||||
var seperatedScore:Array<Int> = [];
|
||||
var tempCombo:Int = combo;
|
||||
|
||||
|
@ -140,31 +81,27 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
|||
// seperatedScore.reverse();
|
||||
|
||||
var daLoop:Int = 1;
|
||||
for (i in seperatedScore)
|
||||
for (digit in seperatedScore)
|
||||
{
|
||||
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2);
|
||||
var numScore:Null<FunkinSprite> = noteStyle.buildComboNumSprite(digit);
|
||||
if (numScore == null) continue;
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 1));
|
||||
numScore.antialiasing = false;
|
||||
numScore.pixelPerfectRender = true;
|
||||
numScore.pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
numScore.setGraphicSize(Std.int(numScore.width * 0.45));
|
||||
numScore.antialiasing = true;
|
||||
}
|
||||
numScore.updateHitbox();
|
||||
numScore.x = (FlxG.width * 0.507) - (36 * daLoop) - 65;
|
||||
trace('numScore($daLoop) = ${numScore.x}');
|
||||
numScore.y = (FlxG.camera.height * 0.44);
|
||||
|
||||
var offsets = noteStyle.getComboNumSpriteOffsets(digit);
|
||||
numScore.x += offsets[0];
|
||||
numScore.y += offsets[1];
|
||||
|
||||
numScore.x = comboSpr.x - (36 * daLoop) - 65; //- 90;
|
||||
numScore.acceleration.y = FlxG.random.int(250, 300);
|
||||
numScore.velocity.y -= FlxG.random.int(130, 150);
|
||||
numScore.velocity.x = FlxG.random.float(-5, 5);
|
||||
|
||||
add(numScore);
|
||||
|
||||
var fadeEase = noteStyle.isComboNumSpritePixel(digit) ? EaseUtil.stepped(2) : null;
|
||||
|
||||
FlxTween.tween(numScore, {alpha: 0}, 0.2,
|
||||
{
|
||||
onComplete: function(tween:FlxTween) {
|
||||
|
@ -177,9 +114,5 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
|||
|
||||
daLoop++;
|
||||
}
|
||||
|
||||
trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
|
||||
|
||||
return combo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,6 @@ class VideoCutscene
|
|||
// Trigger the cutscene. Don't play the song in the background.
|
||||
PlayState.instance.isInCutscene = true;
|
||||
PlayState.instance.camHUD.visible = false;
|
||||
PlayState.instance.camCutscene.visible = true;
|
||||
|
||||
// Display a black screen to hide the game while the video is playing.
|
||||
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
|
@ -305,7 +304,6 @@ class VideoCutscene
|
|||
vid = null;
|
||||
#end
|
||||
|
||||
PlayState.instance.camCutscene.visible = true;
|
||||
PlayState.instance.camHUD.visible = true;
|
||||
|
||||
FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,
|
||||
|
|
|
@ -114,7 +114,7 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
name: 'zoom',
|
||||
title: 'Zoom Level',
|
||||
defaultValue: 1.0,
|
||||
step: 0.1,
|
||||
step: 0.05,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
units: 'x'
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package funkin.play.notes;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.NoteParamData;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -65,6 +66,22 @@ class NoteSprite extends FunkinSprite
|
|||
return this.noteData.kind = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of custom parameters for this note
|
||||
*/
|
||||
public var params(get, set):Array<NoteParamData>;
|
||||
|
||||
function get_params():Array<NoteParamData>
|
||||
{
|
||||
return this.noteData?.params ?? [];
|
||||
}
|
||||
|
||||
function set_params(value:Array<NoteParamData>):Array<NoteParamData>
|
||||
{
|
||||
if (this.noteData == null) return value;
|
||||
return this.noteData.params = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data of the note (i.e. the direction.)
|
||||
*/
|
||||
|
@ -74,7 +91,7 @@ class NoteSprite extends FunkinSprite
|
|||
{
|
||||
if (frames == null) return value;
|
||||
|
||||
animation.play(DIRECTION_COLORS[value] + 'Scroll');
|
||||
playNoteAnimation(value);
|
||||
|
||||
this.direction = value;
|
||||
return this.direction;
|
||||
|
@ -135,19 +152,37 @@ class NoteSprite extends FunkinSprite
|
|||
this.hsvShader = new HSVShader();
|
||||
|
||||
setupNoteGraphic(noteStyle);
|
||||
|
||||
// Disables the update() function for performance.
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
function setupNoteGraphic(noteStyle:NoteStyle):Void
|
||||
/**
|
||||
* Creates frames and animations
|
||||
* @param noteStyle The `NoteStyle` instance
|
||||
*/
|
||||
public function setupNoteGraphic(noteStyle:NoteStyle):Void
|
||||
{
|
||||
noteStyle.buildNoteSprite(this);
|
||||
|
||||
setGraphicSize(Strumline.STRUMLINE_SIZE);
|
||||
updateHitbox();
|
||||
|
||||
this.shader = hsvShader;
|
||||
|
||||
// `false` disables the update() function for performance.
|
||||
this.active = noteStyle.isNoteAnimated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of the param with the given name
|
||||
* @param name Name of the param
|
||||
* @return Null<Dynamic>
|
||||
*/
|
||||
public function getParam(name:String):Null<Dynamic>
|
||||
{
|
||||
for (param in params)
|
||||
{
|
||||
if (param.name == name)
|
||||
{
|
||||
return param.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#if FLX_DEBUG
|
||||
|
@ -173,6 +208,11 @@ class NoteSprite extends FunkinSprite
|
|||
}
|
||||
#end
|
||||
|
||||
function playNoteAnimation(value:Int):Void
|
||||
{
|
||||
animation.play(DIRECTION_COLORS[value] + 'Scroll');
|
||||
}
|
||||
|
||||
public function desaturate():Void
|
||||
{
|
||||
this.hsvShader.saturation = 0.2;
|
||||
|
|
|
@ -16,6 +16,7 @@ import funkin.data.song.SongData.SongNoteData;
|
|||
import funkin.ui.options.PreferencesMenu;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
|
||||
/**
|
||||
* A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player.
|
||||
|
@ -93,6 +94,10 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
final noteStyle:NoteStyle;
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
var ghostTapTimer:Float = 0.0;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The note data for the song. Should NOT be altered after the song starts,
|
||||
* so we can easily rewind.
|
||||
|
@ -178,21 +183,36 @@ class Strumline extends FlxSpriteGroup
|
|||
super.update(elapsed);
|
||||
|
||||
updateNotes();
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
updateGhostTapTimer(elapsed);
|
||||
#end
|
||||
}
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
/**
|
||||
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
|
||||
*/
|
||||
public function mayGhostTap():Bool
|
||||
{
|
||||
// TODO: Refine this. Only querying "can be hit" is too tight but "is being rendered" is too loose.
|
||||
// Also, if you just hit a note, there should be a (short) period where this is off so you can't spam.
|
||||
// Any notes in range of the strumline.
|
||||
if (getNotesMayHit().length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Any hold notes in range of the strumline.
|
||||
if (getHoldNotesHitOrMissed().length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there are any notes on screen, we can't ghost tap.
|
||||
return notes.members.filter(function(note:NoteSprite) {
|
||||
return note != null && note.alive && !note.hasBeenHit;
|
||||
}).length == 0;
|
||||
// Note has been hit recently.
|
||||
if (ghostTapTimer > 0.0) return false;
|
||||
|
||||
// **yippee**
|
||||
return true;
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
|
||||
|
@ -491,6 +511,32 @@ class Strumline extends FlxSpriteGroup
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return notes that are within, or way after, `Constants.HIT_WINDOW` ms of the strumline.
|
||||
* @return An array of `NoteSprite` objects.
|
||||
*/
|
||||
public function getNotesOnScreen():Array<NoteSprite>
|
||||
{
|
||||
return notes.members.filter(function(note:NoteSprite) {
|
||||
return note != null && note.alive && !note.hasBeenHit;
|
||||
});
|
||||
}
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
function updateGhostTapTimer(elapsed:Float):Void
|
||||
{
|
||||
// If it's still our turn, don't update the ghost tap timer.
|
||||
if (getNotesOnScreen().length > 0) return;
|
||||
|
||||
ghostTapTimer -= elapsed;
|
||||
|
||||
if (ghostTapTimer <= 0)
|
||||
{
|
||||
ghostTapTimer = 0;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* Called when the PlayState skips a large amount of time forward or backward.
|
||||
*/
|
||||
|
@ -562,6 +608,10 @@ class Strumline extends FlxSpriteGroup
|
|||
playStatic(dir);
|
||||
}
|
||||
resetScrollSpeed();
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
ghostTapTimer = 0;
|
||||
#end
|
||||
}
|
||||
|
||||
public function applyNoteData(data:Array<SongNoteData>):Void
|
||||
|
@ -601,6 +651,10 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition;
|
||||
}
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
ghostTapTimer = Constants.GHOST_TAP_DELAY;
|
||||
#end
|
||||
}
|
||||
|
||||
public function killNote(note:NoteSprite):Void
|
||||
|
@ -708,11 +762,15 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
if (noteSprite != null)
|
||||
{
|
||||
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||
noteSprite.setupNoteGraphic(noteKindStyle);
|
||||
|
||||
noteSprite.direction = note.getDirection();
|
||||
noteSprite.noteData = note;
|
||||
|
||||
noteSprite.x = this.x;
|
||||
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
|
||||
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
|
||||
noteSprite.x -= NUDGE;
|
||||
// noteSprite.x += INITIAL_OFFSET;
|
||||
noteSprite.y = -9999;
|
||||
|
@ -727,6 +785,9 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
if (holdNoteSprite != null)
|
||||
{
|
||||
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||
holdNoteSprite.setupHoldNoteGraphic(noteKindStyle);
|
||||
|
||||
holdNoteSprite.parentStrumline = this;
|
||||
holdNoteSprite.noteData = note;
|
||||
holdNoteSprite.strumTime = note.time;
|
||||
|
|
|
@ -75,6 +75,13 @@ class StrumlineNote extends FlxSprite
|
|||
|
||||
function setup(noteStyle:NoteStyle):Void
|
||||
{
|
||||
if (noteStyle == null)
|
||||
{
|
||||
// If you get an exception on this line, check the debug console.
|
||||
// You probably have a parsing error in your note style's JSON file.
|
||||
throw "FATAL ERROR: Attempted to initialize PlayState with an invalid NoteStyle.";
|
||||
}
|
||||
|
||||
noteStyle.applyStrumlineFrames(this);
|
||||
noteStyle.applyStrumlineAnimations(this, this.direction);
|
||||
|
||||
|
|
|
@ -99,7 +99,27 @@ class SustainTrail extends FlxSprite
|
|||
*/
|
||||
public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle)
|
||||
{
|
||||
super(0, 0, noteStyle.getHoldNoteAssetPath());
|
||||
super(0, 0);
|
||||
|
||||
// BASIC SETUP
|
||||
this.sustainLength = sustainLength;
|
||||
this.fullSustainLength = sustainLength;
|
||||
this.noteDirection = noteDirection;
|
||||
|
||||
setupHoldNoteGraphic(noteStyle);
|
||||
|
||||
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
||||
|
||||
this.active = true; // This NEEDS to be true for the note to be drawn!
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates hold note graphic and applies correct zooming
|
||||
* @param noteStyle The note style
|
||||
*/
|
||||
public function setupHoldNoteGraphic(noteStyle:NoteStyle):Void
|
||||
{
|
||||
loadGraphic(noteStyle.getHoldNoteAssetPath());
|
||||
|
||||
antialiasing = true;
|
||||
|
||||
|
@ -109,13 +129,14 @@ class SustainTrail extends FlxSprite
|
|||
endOffset = bottomClip = 1;
|
||||
antialiasing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
endOffset = 0.5;
|
||||
bottomClip = 0.9;
|
||||
}
|
||||
|
||||
zoom = 1.0;
|
||||
zoom *= noteStyle.fetchHoldNoteScale();
|
||||
|
||||
// BASIC SETUP
|
||||
this.sustainLength = sustainLength;
|
||||
this.fullSustainLength = sustainLength;
|
||||
this.noteDirection = noteDirection;
|
||||
|
||||
zoom *= 0.7;
|
||||
|
||||
// CALCULATE SIZE
|
||||
|
@ -131,9 +152,6 @@ class SustainTrail extends FlxSprite
|
|||
updateColorTransform();
|
||||
|
||||
updateClipping();
|
||||
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
||||
|
||||
this.active = true; // This NEEDS to be true for the note to be drawn!
|
||||
}
|
||||
|
||||
function getBaseScrollSpeed()
|
||||
|
@ -195,6 +213,11 @@ class SustainTrail extends FlxSprite
|
|||
*/
|
||||
public function updateClipping(songTime:Float = 0):Void
|
||||
{
|
||||
if (graphic == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight);
|
||||
if (clipHeight <= 0.1)
|
||||
{
|
||||
|
|
119
source/funkin/play/notes/notekind/NoteKind.hx
Normal file
119
source/funkin/play/notes/notekind/NoteKind.hx
Normal file
|
@ -0,0 +1,119 @@
|
|||
package funkin.play.notes.notekind;
|
||||
|
||||
import funkin.modding.IScriptedClass.INoteScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.math.FlxMath;
|
||||
|
||||
/**
|
||||
* Class for note scripts
|
||||
*/
|
||||
class NoteKind implements INoteScriptedClass
|
||||
{
|
||||
/**
|
||||
* The name of the note kind
|
||||
*/
|
||||
public var noteKind:String;
|
||||
|
||||
/**
|
||||
* Description used in chart editor
|
||||
*/
|
||||
public var description:String;
|
||||
|
||||
/**
|
||||
* Custom note style
|
||||
*/
|
||||
public var noteStyleId:Null<String>;
|
||||
|
||||
/**
|
||||
* Custom parameters for the chart editor
|
||||
*/
|
||||
public var params:Array<NoteKindParam>;
|
||||
|
||||
public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
|
||||
{
|
||||
this.noteKind = noteKind;
|
||||
this.description = description;
|
||||
this.noteStyleId = noteStyleId;
|
||||
this.params = params ?? [];
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return noteKind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all notes of this kind
|
||||
* @return Array<NoteSprite>
|
||||
*/
|
||||
function getNotes():Array<NoteSprite>
|
||||
{
|
||||
var allNotes:Array<NoteSprite> = PlayState.instance.playerStrumline.notes.members.concat(PlayState.instance.opponentStrumline.notes.members);
|
||||
return allNotes.filter(function(note:NoteSprite) {
|
||||
return note != null && note.noteData.kind == this.noteKind;
|
||||
});
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent):Void {}
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void {}
|
||||
|
||||
public function onDestroy(event:ScriptEvent):Void {}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent):Void {}
|
||||
|
||||
public function onNoteIncoming(event:NoteScriptEvent):Void {}
|
||||
|
||||
public function onNoteHit(event:HitNoteScriptEvent):Void {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract for setting the type of the `NoteKindParam`
|
||||
* This was supposed to be an enum but polymod kept being annoying
|
||||
*/
|
||||
abstract NoteKindParamType(String) from String to String
|
||||
{
|
||||
public static final STRING:String = 'String';
|
||||
|
||||
public static final INT:String = 'Int';
|
||||
|
||||
public static final FLOAT:String = 'Float';
|
||||
}
|
||||
|
||||
typedef NoteKindParamData =
|
||||
{
|
||||
/**
|
||||
* If `min` is null, there is no minimum
|
||||
*/
|
||||
?min:Null<Float>,
|
||||
|
||||
/**
|
||||
* If `max` is null, there is no maximum
|
||||
*/
|
||||
?max:Null<Float>,
|
||||
|
||||
/**
|
||||
* If `step` is null, it will use 1.0
|
||||
*/
|
||||
?step:Null<Float>,
|
||||
|
||||
/**
|
||||
* If `precision` is null, there will be 0 decimal places
|
||||
*/
|
||||
?precision:Null<Int>,
|
||||
|
||||
?defaultValue:Dynamic
|
||||
}
|
||||
|
||||
/**
|
||||
* Typedef for creating custom parameters in the chart editor
|
||||
*/
|
||||
typedef NoteKindParam =
|
||||
{
|
||||
name:String,
|
||||
description:String,
|
||||
type:NoteKindParamType,
|
||||
?data:NoteKindParamData
|
||||
}
|
121
source/funkin/play/notes/notekind/NoteKindManager.hx
Normal file
121
source/funkin/play/notes/notekind/NoteKindManager.hx
Normal file
|
@ -0,0 +1,121 @@
|
|||
package funkin.play.notes.notekind;
|
||||
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.notekind.ScriptedNoteKind;
|
||||
import funkin.play.notes.notekind.NoteKind.NoteKindParam;
|
||||
|
||||
class NoteKindManager
|
||||
{
|
||||
static var noteKinds:Map<String, NoteKind> = [];
|
||||
|
||||
public static function loadScripts():Void
|
||||
{
|
||||
var scriptedClassName:Array<String> = ScriptedNoteKind.listScriptClasses();
|
||||
if (scriptedClassName.length > 0)
|
||||
{
|
||||
trace('Instantiating ${scriptedClassName.length} scripted note kind(s)...');
|
||||
for (scriptedClass in scriptedClassName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var script:NoteKind = ScriptedNoteKind.init(scriptedClass, "unknown");
|
||||
trace(' Initialized scripted note kind: ${script.noteKind}');
|
||||
noteKinds.set(script.noteKind, script);
|
||||
ChartEditorDropdowns.NOTE_KINDS.set(script.noteKind, script.description);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' FAILED to instantiate scripted note kind: ${scriptedClass}');
|
||||
trace(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given event for note kind scripts
|
||||
* @param event The event
|
||||
*/
|
||||
public static function callEvent(event:ScriptEvent):Void
|
||||
{
|
||||
// if it is a note script event,
|
||||
// then only call the event for the specific note kind script
|
||||
if (Std.isOfType(event, NoteScriptEvent))
|
||||
{
|
||||
var noteEvent:NoteScriptEvent = cast(event, NoteScriptEvent);
|
||||
|
||||
var noteKind:NoteKind = noteKinds.get(noteEvent.note.kind);
|
||||
|
||||
if (noteKind != null)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(noteKind, event);
|
||||
}
|
||||
}
|
||||
else // call the event for all note kind scripts
|
||||
{
|
||||
for (noteKind in noteKinds.iterator())
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(noteKind, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the note style from the given note kind
|
||||
* @param noteKind note kind name
|
||||
* @param suffix Used for song note styles
|
||||
* @return NoteStyle
|
||||
*/
|
||||
public static function getNoteStyle(noteKind:String, ?suffix:String):Null<NoteStyle>
|
||||
{
|
||||
var noteStyleId:Null<String> = getNoteStyleId(noteKind, suffix);
|
||||
|
||||
if (noteStyleId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the note style id from the given note kind
|
||||
* @param noteKind Note kind name
|
||||
* @param suffix Used for song note styles
|
||||
* @return Null<String>
|
||||
*/
|
||||
public static function getNoteStyleId(noteKind:String, ?suffix:String):Null<String>
|
||||
{
|
||||
if (suffix == '')
|
||||
{
|
||||
suffix = null;
|
||||
}
|
||||
|
||||
var noteStyleId:Null<String> = noteKinds.get(noteKind)?.noteStyleId;
|
||||
if (noteStyleId != null && suffix != null)
|
||||
{
|
||||
noteStyleId = NoteStyleRegistry.instance.hasEntry('$noteStyleId-$suffix') ? '$noteStyleId-$suffix' : noteStyleId;
|
||||
}
|
||||
|
||||
return noteStyleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive custom params of the given note kind
|
||||
* @param noteKind Name of the note kind
|
||||
* @return Array<NoteKindParam>
|
||||
*/
|
||||
public static function getParams(noteKind:Null<String>):Array<NoteKindParam>
|
||||
{
|
||||
if (noteKind == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return noteKinds.get(noteKind)?.params ?? [];
|
||||
}
|
||||
}
|
9
source/funkin/play/notes/notekind/ScriptedNoteKind.hx
Normal file
9
source/funkin/play/notes/notekind/ScriptedNoteKind.hx
Normal file
|
@ -0,0 +1,9 @@
|
|||
package funkin.play.notes.notekind;
|
||||
|
||||
/**
|
||||
* A script that can be tied to a NoteKind.
|
||||
* Create a scripted class that extends NoteKind,
|
||||
* then call `super('noteKind')` in the constructor to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedNoteKind extends NoteKind implements polymod.hscript.HScriptedClass {}
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.play.notes.notestyle;
|
||||
|
||||
import funkin.play.Countdown;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
@ -16,6 +17,7 @@ using funkin.data.animation.AnimationData.AnimationDataUtil;
|
|||
* Holds the data for what assets to use for a note style,
|
||||
* and provides convenience methods for building sprites based on them.
|
||||
*/
|
||||
@:nullSafety
|
||||
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
/**
|
||||
|
@ -42,12 +44,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
this.id = id;
|
||||
_data = _fetchData(id);
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse note style data for id: $id';
|
||||
}
|
||||
|
||||
this.fallback = NoteStyleRegistry.instance.fetchEntry(getFallbackID());
|
||||
var fallbackID = _data.fallback;
|
||||
if (fallbackID != null) this.fallback = NoteStyleRegistry.instance.fetchEntry(fallbackID);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,7 +70,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
* Get the note style ID of the parent note style.
|
||||
* @return The string ID, or `null` if there is no parent.
|
||||
*/
|
||||
function getFallbackID():Null<String>
|
||||
public function getFallbackID():Null<String>
|
||||
{
|
||||
return _data.fallback;
|
||||
}
|
||||
|
@ -80,7 +78,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
public function buildNoteSprite(target:NoteSprite):Void
|
||||
{
|
||||
// Apply the note sprite frames.
|
||||
var atlas:FlxAtlasFrames = buildNoteFrames(false);
|
||||
var atlas:Null<FlxAtlasFrames> = buildNoteFrames(false);
|
||||
|
||||
if (atlas == null)
|
||||
{
|
||||
|
@ -89,29 +87,40 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
|
||||
target.frames = atlas;
|
||||
|
||||
target.scale.x = _data.assets.note.scale;
|
||||
target.scale.y = _data.assets.note.scale;
|
||||
target.antialiasing = !_data.assets.note.isPixel;
|
||||
target.antialiasing = !(_data.assets?.note?.isPixel ?? false);
|
||||
|
||||
// Apply the animations.
|
||||
buildNoteAnimations(target);
|
||||
|
||||
// Set the scale.
|
||||
target.setGraphicSize(Strumline.STRUMLINE_SIZE * getNoteScale());
|
||||
target.updateHitbox();
|
||||
}
|
||||
|
||||
var noteFrames:FlxAtlasFrames = null;
|
||||
var noteFrames:Null<FlxAtlasFrames> = null;
|
||||
|
||||
function buildNoteFrames(force:Bool = false):FlxAtlasFrames
|
||||
function buildNoteFrames(force:Bool = false):Null<FlxAtlasFrames>
|
||||
{
|
||||
if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath())))
|
||||
var noteAssetPath = getNoteAssetPath();
|
||||
if (noteAssetPath == null) return null;
|
||||
|
||||
if (!FunkinSprite.isTextureCached(Paths.image(noteAssetPath)))
|
||||
{
|
||||
FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}');
|
||||
FlxG.log.warn('Note texture is not cached: ${noteAssetPath}');
|
||||
}
|
||||
|
||||
// Purge the note frames if the cached atlas is invalid.
|
||||
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
|
||||
}
|
||||
|
||||
if (noteFrames != null && !force) return noteFrames;
|
||||
|
||||
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
|
||||
var noteAssetPath = getNoteAssetPath();
|
||||
if (noteAssetPath == null) return null;
|
||||
|
||||
noteFrames = Paths.getSparrowAtlas(noteAssetPath, getNoteAssetLibrary());
|
||||
|
||||
if (noteFrames == null)
|
||||
{
|
||||
|
@ -121,17 +130,18 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
return noteFrames;
|
||||
}
|
||||
|
||||
function getNoteAssetPath(raw:Bool = false):String
|
||||
function getNoteAssetPath(raw:Bool = false):Null<String>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
|
||||
if (rawPath == null) return fallback.getNoteAssetPath(true);
|
||||
if (rawPath == null && fallback != null) return fallback.getNoteAssetPath(true);
|
||||
return rawPath;
|
||||
}
|
||||
|
||||
// library:path
|
||||
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
||||
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length == 0) return null;
|
||||
if (parts.length == 1) return getNoteAssetPath(true);
|
||||
return parts[1];
|
||||
}
|
||||
|
@ -139,47 +149,63 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
function getNoteAssetLibrary():Null<String>
|
||||
{
|
||||
// library:path
|
||||
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
||||
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length == 0) return null;
|
||||
if (parts.length == 1) return null;
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
function buildNoteAnimations(target:NoteSprite):Void
|
||||
{
|
||||
var leftData:AnimationData = fetchNoteAnimationData(LEFT);
|
||||
target.animation.addByPrefix('purpleScroll', leftData.prefix, leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY);
|
||||
var downData:AnimationData = fetchNoteAnimationData(DOWN);
|
||||
target.animation.addByPrefix('blueScroll', downData.prefix, downData.frameRate, downData.looped, downData.flipX, downData.flipY);
|
||||
var upData:AnimationData = fetchNoteAnimationData(UP);
|
||||
target.animation.addByPrefix('greenScroll', upData.prefix, upData.frameRate, upData.looped, upData.flipX, upData.flipY);
|
||||
var rightData:AnimationData = fetchNoteAnimationData(RIGHT);
|
||||
target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
|
||||
var leftData:Null<AnimationData> = fetchNoteAnimationData(LEFT);
|
||||
if (leftData != null) target.animation.addByPrefix('purpleScroll', leftData.prefix ?? '', leftData.frameRate ?? 24, leftData.looped ?? false,
|
||||
leftData.flipX, leftData.flipY);
|
||||
var downData:Null<AnimationData> = fetchNoteAnimationData(DOWN);
|
||||
if (downData != null) target.animation.addByPrefix('blueScroll', downData.prefix ?? '', downData.frameRate ?? 24, downData.looped ?? false,
|
||||
downData.flipX, downData.flipY);
|
||||
var upData:Null<AnimationData> = fetchNoteAnimationData(UP);
|
||||
if (upData != null) target.animation.addByPrefix('greenScroll', upData.prefix ?? '', upData.frameRate ?? 24, upData.looped ?? false, upData.flipX,
|
||||
upData.flipY);
|
||||
var rightData:Null<AnimationData> = fetchNoteAnimationData(RIGHT);
|
||||
if (rightData != null) target.animation.addByPrefix('redScroll', rightData.prefix ?? '', rightData.frameRate ?? 24, rightData.looped ?? false,
|
||||
rightData.flipX, rightData.flipY);
|
||||
}
|
||||
|
||||
function fetchNoteAnimationData(dir:NoteDirection):AnimationData
|
||||
public function isNoteAnimated():Bool
|
||||
{
|
||||
return _data.assets?.note?.animated ?? false;
|
||||
}
|
||||
|
||||
public function getNoteScale():Float
|
||||
{
|
||||
return _data.assets?.note?.scale ?? 1.0;
|
||||
}
|
||||
|
||||
function fetchNoteAnimationData(dir:NoteDirection):Null<AnimationData>
|
||||
{
|
||||
var result:Null<AnimationData> = switch (dir)
|
||||
{
|
||||
case LEFT: _data.assets.note.data.left.toNamed();
|
||||
case DOWN: _data.assets.note.data.down.toNamed();
|
||||
case UP: _data.assets.note.data.up.toNamed();
|
||||
case RIGHT: _data.assets.note.data.right.toNamed();
|
||||
case LEFT: _data.assets?.note?.data?.left?.toNamed();
|
||||
case DOWN: _data.assets?.note?.data?.down?.toNamed();
|
||||
case UP: _data.assets?.note?.data?.up?.toNamed();
|
||||
case RIGHT: _data.assets?.note?.data?.right?.toNamed();
|
||||
};
|
||||
|
||||
return (result == null) ? fallback.fetchNoteAnimationData(dir) : result;
|
||||
return (result == null && fallback != null) ? fallback.fetchNoteAnimationData(dir) : result;
|
||||
}
|
||||
|
||||
public function getHoldNoteAssetPath(raw:Bool = false):String
|
||||
public function getHoldNoteAssetPath(raw:Bool = false):Null<String>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
// TODO: figure out why ?. didn't work here
|
||||
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
||||
return (rawPath == null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
||||
return (rawPath == null && fallback != null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
||||
}
|
||||
|
||||
// library:path
|
||||
var parts = getHoldNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
||||
var parts = getHoldNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length == 0) return null;
|
||||
if (parts.length == 1) return Paths.image(parts[0]);
|
||||
return Paths.image(parts[1], parts[0]);
|
||||
}
|
||||
|
@ -187,15 +213,15 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
public function isHoldNotePixel():Bool
|
||||
{
|
||||
var data = _data?.assets?.holdNote;
|
||||
if (data == null) return fallback.isHoldNotePixel();
|
||||
return data.isPixel;
|
||||
if (data == null && fallback != null) return fallback.isHoldNotePixel();
|
||||
return data?.isPixel ?? false;
|
||||
}
|
||||
|
||||
public function fetchHoldNoteScale():Float
|
||||
{
|
||||
var data = _data?.assets?.holdNote;
|
||||
if (data == null) return fallback.fetchHoldNoteScale();
|
||||
return data.scale;
|
||||
if (data == null && fallback != null) return fallback.fetchHoldNoteScale();
|
||||
return data?.scale ?? 1.0;
|
||||
}
|
||||
|
||||
public function applyStrumlineFrames(target:StrumlineNote):Void
|
||||
|
@ -203,7 +229,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
// TODO: Add support for multi-Sparrow.
|
||||
// Will be less annoying after this is merged: https://github.com/HaxeFlixel/flixel/pull/2772
|
||||
|
||||
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath(), getStrumlineAssetLibrary());
|
||||
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath() ?? '', getStrumlineAssetLibrary());
|
||||
|
||||
if (atlas == null)
|
||||
{
|
||||
|
@ -212,31 +238,30 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
|
||||
target.frames = atlas;
|
||||
|
||||
target.scale.x = _data.assets.noteStrumline.scale;
|
||||
target.scale.y = _data.assets.noteStrumline.scale;
|
||||
target.antialiasing = !_data.assets.noteStrumline.isPixel;
|
||||
target.scale.set(_data.assets.noteStrumline?.scale ?? 1.0);
|
||||
target.antialiasing = !(_data.assets.noteStrumline?.isPixel ?? false);
|
||||
}
|
||||
|
||||
function getStrumlineAssetPath(raw:Bool = false):String
|
||||
function getStrumlineAssetPath(raw:Bool = false):Null<String>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
|
||||
if (rawPath == null) return fallback.getStrumlineAssetPath(true);
|
||||
if (rawPath == null && fallback != null) return fallback.getStrumlineAssetPath(true);
|
||||
return rawPath;
|
||||
}
|
||||
|
||||
// library:path
|
||||
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
||||
if (parts.length == 1) return getStrumlineAssetPath(true);
|
||||
var parts = getStrumlineAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length <= 1) return getStrumlineAssetPath(true);
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
function getStrumlineAssetLibrary():Null<String>
|
||||
{
|
||||
// library:path
|
||||
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
||||
if (parts.length == 1) return null;
|
||||
var parts = getStrumlineAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length <= 1) return null;
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
|
@ -247,60 +272,592 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
|
||||
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
|
||||
{
|
||||
var result:Array<AnimationData> = switch (dir)
|
||||
var result:Array<Null<AnimationData>> = switch (dir)
|
||||
{
|
||||
case NoteDirection.LEFT: [
|
||||
_data.assets.noteStrumline.data.leftStatic.toNamed('static'),
|
||||
_data.assets.noteStrumline.data.leftPress.toNamed('press'),
|
||||
_data.assets.noteStrumline.data.leftConfirm.toNamed('confirm'),
|
||||
_data.assets.noteStrumline.data.leftConfirmHold.toNamed('confirm-hold'),
|
||||
_data.assets.noteStrumline?.data?.leftStatic?.toNamed('static'),
|
||||
_data.assets.noteStrumline?.data?.leftPress?.toNamed('press'),
|
||||
_data.assets.noteStrumline?.data?.leftConfirm?.toNamed('confirm'),
|
||||
_data.assets.noteStrumline?.data?.leftConfirmHold?.toNamed('confirm-hold'),
|
||||
];
|
||||
case NoteDirection.DOWN: [
|
||||
_data.assets.noteStrumline.data.downStatic.toNamed('static'),
|
||||
_data.assets.noteStrumline.data.downPress.toNamed('press'),
|
||||
_data.assets.noteStrumline.data.downConfirm.toNamed('confirm'),
|
||||
_data.assets.noteStrumline.data.downConfirmHold.toNamed('confirm-hold'),
|
||||
_data.assets.noteStrumline?.data?.downStatic?.toNamed('static'),
|
||||
_data.assets.noteStrumline?.data?.downPress?.toNamed('press'),
|
||||
_data.assets.noteStrumline?.data?.downConfirm?.toNamed('confirm'),
|
||||
_data.assets.noteStrumline?.data?.downConfirmHold?.toNamed('confirm-hold'),
|
||||
];
|
||||
case NoteDirection.UP: [
|
||||
_data.assets.noteStrumline.data.upStatic.toNamed('static'),
|
||||
_data.assets.noteStrumline.data.upPress.toNamed('press'),
|
||||
_data.assets.noteStrumline.data.upConfirm.toNamed('confirm'),
|
||||
_data.assets.noteStrumline.data.upConfirmHold.toNamed('confirm-hold'),
|
||||
_data.assets.noteStrumline?.data?.upStatic?.toNamed('static'),
|
||||
_data.assets.noteStrumline?.data?.upPress?.toNamed('press'),
|
||||
_data.assets.noteStrumline?.data?.upConfirm?.toNamed('confirm'),
|
||||
_data.assets.noteStrumline?.data?.upConfirmHold?.toNamed('confirm-hold'),
|
||||
];
|
||||
case NoteDirection.RIGHT: [
|
||||
_data.assets.noteStrumline.data.rightStatic.toNamed('static'),
|
||||
_data.assets.noteStrumline.data.rightPress.toNamed('press'),
|
||||
_data.assets.noteStrumline.data.rightConfirm.toNamed('confirm'),
|
||||
_data.assets.noteStrumline.data.rightConfirmHold.toNamed('confirm-hold'),
|
||||
_data.assets.noteStrumline?.data?.rightStatic?.toNamed('static'),
|
||||
_data.assets.noteStrumline?.data?.rightPress?.toNamed('press'),
|
||||
_data.assets.noteStrumline?.data?.rightConfirm?.toNamed('confirm'),
|
||||
_data.assets.noteStrumline?.data?.rightConfirmHold?.toNamed('confirm-hold'),
|
||||
];
|
||||
default: [];
|
||||
};
|
||||
|
||||
return result;
|
||||
return thx.Arrays.filterNull(result);
|
||||
}
|
||||
|
||||
public function applyStrumlineOffsets(target:StrumlineNote)
|
||||
public function applyStrumlineOffsets(target:StrumlineNote):Void
|
||||
{
|
||||
target.x += _data.assets.noteStrumline.offsets[0];
|
||||
target.y += _data.assets.noteStrumline.offsets[1];
|
||||
var offsets = _data?.assets?.noteStrumline?.offsets ?? [0.0, 0.0];
|
||||
target.x += offsets[0];
|
||||
target.y += offsets[1];
|
||||
}
|
||||
|
||||
public function getStrumlineScale():Float
|
||||
{
|
||||
return _data.assets.noteStrumline.scale;
|
||||
return _data?.assets?.noteStrumline?.scale ?? 1.0;
|
||||
}
|
||||
|
||||
public function isNoteSplashEnabled():Bool
|
||||
{
|
||||
var data = _data?.assets?.noteSplash?.data;
|
||||
if (data == null) return fallback.isNoteSplashEnabled();
|
||||
return data.enabled;
|
||||
if (data == null) return fallback?.isNoteSplashEnabled() ?? false;
|
||||
return data.enabled ?? false;
|
||||
}
|
||||
|
||||
public function isHoldNoteCoverEnabled():Bool
|
||||
{
|
||||
var data = _data?.assets?.holdNoteCover?.data;
|
||||
if (data == null) return fallback.isHoldNoteCoverEnabled();
|
||||
return data.enabled;
|
||||
if (data == null) return fallback?.isHoldNoteCoverEnabled() ?? false;
|
||||
return data.enabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a sprite for the given step of the countdown.
|
||||
* @param step
|
||||
* @return A `FunkinSprite`, or `null` if no graphic is available for this step.
|
||||
*/
|
||||
public function buildCountdownSprite(step:Countdown.CountdownStep):Null<FunkinSprite>
|
||||
{
|
||||
var result = new FunkinSprite();
|
||||
|
||||
switch (step)
|
||||
{
|
||||
case THREE:
|
||||
if (_data.assets.countdownThree == null) return fallback?.buildCountdownSprite(step);
|
||||
var assetPath = buildCountdownSpritePath(step);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.countdownThree?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.countdownThree?.scale ?? 1.0;
|
||||
case TWO:
|
||||
if (_data.assets.countdownTwo == null) return fallback?.buildCountdownSprite(step);
|
||||
var assetPath = buildCountdownSpritePath(step);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.countdownTwo?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.countdownTwo?.scale ?? 1.0;
|
||||
case ONE:
|
||||
if (_data.assets.countdownOne == null) return fallback?.buildCountdownSprite(step);
|
||||
var assetPath = buildCountdownSpritePath(step);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.countdownOne?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.countdownOne?.scale ?? 1.0;
|
||||
case GO:
|
||||
if (_data.assets.countdownGo == null) return fallback?.buildCountdownSprite(step);
|
||||
var assetPath = buildCountdownSpritePath(step);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.countdownGo?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.countdownGo?.scale ?? 1.0;
|
||||
default:
|
||||
// TODO: Do something here?
|
||||
return null;
|
||||
}
|
||||
|
||||
result.scrollFactor.set(0, 0);
|
||||
result.antialiasing = !isCountdownSpritePixel(step);
|
||||
result.updateHitbox();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildCountdownSpritePath(step:Countdown.CountdownStep):Null<String>
|
||||
{
|
||||
var basePath:Null<String> = null;
|
||||
switch (step)
|
||||
{
|
||||
case THREE:
|
||||
basePath = _data.assets.countdownThree?.assetPath;
|
||||
case TWO:
|
||||
basePath = _data.assets.countdownTwo?.assetPath;
|
||||
case ONE:
|
||||
basePath = _data.assets.countdownOne?.assetPath;
|
||||
case GO:
|
||||
basePath = _data.assets.countdownGo?.assetPath;
|
||||
default:
|
||||
basePath = null;
|
||||
}
|
||||
|
||||
if (basePath == null) return fallback?.buildCountdownSpritePath(step);
|
||||
|
||||
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length < 1) return null;
|
||||
if (parts.length == 1) return parts[0];
|
||||
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
function buildCountdownSpriteLibrary(step:Countdown.CountdownStep):Null<String>
|
||||
{
|
||||
var basePath:Null<String> = null;
|
||||
switch (step)
|
||||
{
|
||||
case THREE:
|
||||
basePath = _data.assets.countdownThree?.assetPath;
|
||||
case TWO:
|
||||
basePath = _data.assets.countdownTwo?.assetPath;
|
||||
case ONE:
|
||||
basePath = _data.assets.countdownOne?.assetPath;
|
||||
case GO:
|
||||
basePath = _data.assets.countdownGo?.assetPath;
|
||||
default:
|
||||
basePath = null;
|
||||
}
|
||||
|
||||
if (basePath == null) return fallback?.buildCountdownSpriteLibrary(step);
|
||||
|
||||
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length <= 1) return null;
|
||||
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
public function isCountdownSpritePixel(step:Countdown.CountdownStep):Bool
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case THREE:
|
||||
var result = _data.assets.countdownThree?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
case TWO:
|
||||
var result = _data.assets.countdownTwo?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
case ONE:
|
||||
var result = _data.assets.countdownOne?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
case GO:
|
||||
var result = _data.assets.countdownGo?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCountdownSpriteOffsets(step:Countdown.CountdownStep):Array<Float>
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case THREE:
|
||||
var result = _data.assets.countdownThree?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
case TWO:
|
||||
var result = _data.assets.countdownTwo?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
case ONE:
|
||||
var result = _data.assets.countdownOne?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
case GO:
|
||||
var result = _data.assets.countdownGo?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
public function getCountdownSoundPath(step:Countdown.CountdownStep, raw:Bool = false):Null<String>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
// TODO: figure out why ?. didn't work here
|
||||
var rawPath:Null<String> = switch (step)
|
||||
{
|
||||
case Countdown.CountdownStep.THREE:
|
||||
_data.assets.countdownThree?.data?.audioPath;
|
||||
case Countdown.CountdownStep.TWO:
|
||||
_data.assets.countdownTwo?.data?.audioPath;
|
||||
case Countdown.CountdownStep.ONE:
|
||||
_data.assets.countdownOne?.data?.audioPath;
|
||||
case Countdown.CountdownStep.GO:
|
||||
_data.assets.countdownGo?.data?.audioPath;
|
||||
default:
|
||||
null;
|
||||
}
|
||||
|
||||
return (rawPath == null && fallback != null) ? fallback.getCountdownSoundPath(step, true) : rawPath;
|
||||
}
|
||||
|
||||
// library:path
|
||||
var parts = getCountdownSoundPath(step, true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length == 0) return null;
|
||||
if (parts.length == 1) return Paths.image(parts[0]);
|
||||
return Paths.sound(parts[1], parts[0]);
|
||||
}
|
||||
|
||||
public function buildJudgementSprite(rating:String):Null<FunkinSprite>
|
||||
{
|
||||
var result = new FunkinSprite();
|
||||
|
||||
switch (rating)
|
||||
{
|
||||
case "sick":
|
||||
if (_data.assets.judgementSick == null) return fallback?.buildJudgementSprite(rating);
|
||||
var assetPath = buildJudgementSpritePath(rating);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.judgementSick?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.judgementSick?.scale ?? 1.0;
|
||||
case "good":
|
||||
if (_data.assets.judgementGood == null) return fallback?.buildJudgementSprite(rating);
|
||||
var assetPath = buildJudgementSpritePath(rating);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.judgementGood?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.judgementGood?.scale ?? 1.0;
|
||||
case "bad":
|
||||
if (_data.assets.judgementBad == null) return fallback?.buildJudgementSprite(rating);
|
||||
var assetPath = buildJudgementSpritePath(rating);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.judgementBad?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.judgementBad?.scale ?? 1.0;
|
||||
case "shit":
|
||||
if (_data.assets.judgementShit == null) return fallback?.buildJudgementSprite(rating);
|
||||
var assetPath = buildJudgementSpritePath(rating);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.judgementShit?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.judgementShit?.scale ?? 1.0;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
result.scrollFactor.set(0.2, 0.2);
|
||||
var isPixel = isJudgementSpritePixel(rating);
|
||||
result.antialiasing = !isPixel;
|
||||
result.pixelPerfectRender = isPixel;
|
||||
result.pixelPerfectPosition = isPixel;
|
||||
result.updateHitbox();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public function isJudgementSpritePixel(rating:String):Bool
|
||||
{
|
||||
switch (rating)
|
||||
{
|
||||
case "sick":
|
||||
var result = _data.assets.judgementSick?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
case "good":
|
||||
var result = _data.assets.judgementGood?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
case "bad":
|
||||
var result = _data.assets.judgementBad?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
case "GO":
|
||||
var result = _data.assets.judgementShit?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildJudgementSpritePath(rating:String):Null<String>
|
||||
{
|
||||
var basePath:Null<String> = null;
|
||||
switch (rating)
|
||||
{
|
||||
case "sick":
|
||||
basePath = _data.assets.judgementSick?.assetPath;
|
||||
case "good":
|
||||
basePath = _data.assets.judgementGood?.assetPath;
|
||||
case "bad":
|
||||
basePath = _data.assets.judgementBad?.assetPath;
|
||||
case "shit":
|
||||
basePath = _data.assets.judgementShit?.assetPath;
|
||||
default:
|
||||
basePath = null;
|
||||
}
|
||||
|
||||
if (basePath == null) return fallback?.buildJudgementSpritePath(rating);
|
||||
|
||||
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length < 1) return null;
|
||||
if (parts.length == 1) return parts[0];
|
||||
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
public function getJudgementSpriteOffsets(rating:String):Array<Float>
|
||||
{
|
||||
switch (rating)
|
||||
{
|
||||
case "sick":
|
||||
var result = _data.assets.judgementSick?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
case "good":
|
||||
var result = _data.assets.judgementGood?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
case "bad":
|
||||
var result = _data.assets.judgementBad?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
case "shit":
|
||||
var result = _data.assets.judgementShit?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
public function buildComboNumSprite(digit:Int):Null<FunkinSprite>
|
||||
{
|
||||
var result = new FunkinSprite();
|
||||
|
||||
switch (digit)
|
||||
{
|
||||
case 0:
|
||||
if (_data.assets.comboNumber0 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber0?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber0?.scale ?? 1.0;
|
||||
case 1:
|
||||
if (_data.assets.comboNumber1 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber1?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber1?.scale ?? 1.0;
|
||||
case 2:
|
||||
if (_data.assets.comboNumber2 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber2?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber2?.scale ?? 1.0;
|
||||
case 3:
|
||||
if (_data.assets.comboNumber3 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber3?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber3?.scale ?? 1.0;
|
||||
case 4:
|
||||
if (_data.assets.comboNumber4 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber4?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber4?.scale ?? 1.0;
|
||||
case 5:
|
||||
if (_data.assets.comboNumber5 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber5?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber5?.scale ?? 1.0;
|
||||
case 6:
|
||||
if (_data.assets.comboNumber6 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber6?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber6?.scale ?? 1.0;
|
||||
case 7:
|
||||
if (_data.assets.comboNumber7 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber7?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber7?.scale ?? 1.0;
|
||||
case 8:
|
||||
if (_data.assets.comboNumber8 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber8?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber8?.scale ?? 1.0;
|
||||
case 9:
|
||||
if (_data.assets.comboNumber9 == null) return fallback?.buildComboNumSprite(digit);
|
||||
var assetPath = buildComboNumSpritePath(digit);
|
||||
if (assetPath == null) return null;
|
||||
result.loadTexture(assetPath);
|
||||
result.scale.x = _data.assets.comboNumber9?.scale ?? 1.0;
|
||||
result.scale.y = _data.assets.comboNumber9?.scale ?? 1.0;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
var isPixel = isComboNumSpritePixel(digit);
|
||||
result.antialiasing = !isPixel;
|
||||
result.pixelPerfectRender = isPixel;
|
||||
result.pixelPerfectPosition = isPixel;
|
||||
result.updateHitbox();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public function isComboNumSpritePixel(digit:Int):Bool
|
||||
{
|
||||
switch (digit)
|
||||
{
|
||||
case 0:
|
||||
var result = _data.assets.comboNumber0?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 1:
|
||||
var result = _data.assets.comboNumber1?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 2:
|
||||
var result = _data.assets.comboNumber2?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 3:
|
||||
var result = _data.assets.comboNumber3?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 4:
|
||||
var result = _data.assets.comboNumber4?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 5:
|
||||
var result = _data.assets.comboNumber5?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 6:
|
||||
var result = _data.assets.comboNumber6?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 7:
|
||||
var result = _data.assets.comboNumber7?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 8:
|
||||
var result = _data.assets.comboNumber8?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
case 9:
|
||||
var result = _data.assets.comboNumber9?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildComboNumSpritePath(digit:Int):Null<String>
|
||||
{
|
||||
var basePath:Null<String> = null;
|
||||
switch (digit)
|
||||
{
|
||||
case 0:
|
||||
basePath = _data.assets.comboNumber0?.assetPath;
|
||||
case 1:
|
||||
basePath = _data.assets.comboNumber1?.assetPath;
|
||||
case 2:
|
||||
basePath = _data.assets.comboNumber2?.assetPath;
|
||||
case 3:
|
||||
basePath = _data.assets.comboNumber3?.assetPath;
|
||||
case 4:
|
||||
basePath = _data.assets.comboNumber4?.assetPath;
|
||||
case 5:
|
||||
basePath = _data.assets.comboNumber5?.assetPath;
|
||||
case 6:
|
||||
basePath = _data.assets.comboNumber6?.assetPath;
|
||||
case 7:
|
||||
basePath = _data.assets.comboNumber7?.assetPath;
|
||||
case 8:
|
||||
basePath = _data.assets.comboNumber8?.assetPath;
|
||||
case 9:
|
||||
basePath = _data.assets.comboNumber9?.assetPath;
|
||||
default:
|
||||
basePath = null;
|
||||
}
|
||||
|
||||
if (basePath == null) return fallback?.buildComboNumSpritePath(digit);
|
||||
|
||||
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||
if (parts.length < 1) return null;
|
||||
if (parts.length == 1) return parts[0];
|
||||
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
public function getComboNumSpriteOffsets(digit:Int):Array<Float>
|
||||
{
|
||||
switch (digit)
|
||||
{
|
||||
case 0:
|
||||
var result = _data.assets.comboNumber0?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 1:
|
||||
var result = _data.assets.comboNumber1?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 2:
|
||||
var result = _data.assets.comboNumber2?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 3:
|
||||
var result = _data.assets.comboNumber3?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 4:
|
||||
var result = _data.assets.comboNumber4?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 5:
|
||||
var result = _data.assets.comboNumber5?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 6:
|
||||
var result = _data.assets.comboNumber6?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 7:
|
||||
var result = _data.assets.comboNumber7?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 8:
|
||||
var result = _data.assets.comboNumber8?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
case 9:
|
||||
var result = _data.assets.comboNumber9?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy():Void {}
|
||||
|
@ -310,8 +867,17 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
return 'NoteStyle($id)';
|
||||
}
|
||||
|
||||
static function _fetchData(id:String):Null<NoteStyleData>
|
||||
static function _fetchData(id:String):NoteStyleData
|
||||
{
|
||||
return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
||||
var result = NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw 'Could not parse note style data for id: $id';
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,7 +277,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
// If there are no difficulties in the metadata, there's a problem.
|
||||
if (metadata.playData.difficulties.length == 0)
|
||||
{
|
||||
throw 'Song $id has no difficulties listed in metadata!';
|
||||
trace('[SONG] Warning: Song $id (variation ${metadata.variation}) has no difficulties listed in metadata!');
|
||||
continue;
|
||||
}
|
||||
|
||||
// There may be more difficulties in the chart file than in the metadata,
|
||||
|
@ -494,6 +495,24 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
return diffFiltered;
|
||||
}
|
||||
|
||||
public function listSuffixedDifficulties(variationIds:Array<String>, ?showLocked:Bool, ?showHidden:Bool):Array<String>
|
||||
{
|
||||
var result = [];
|
||||
|
||||
for (variation in variationIds)
|
||||
{
|
||||
var difficulties = listDifficulties(variation, null, showLocked, showHidden);
|
||||
for (difficulty in difficulties)
|
||||
{
|
||||
var suffixedDifficulty = (variation != Constants.DEFAULT_VARIATION
|
||||
&& variation != 'erect') ? '$difficulty-${variation}' : difficulty;
|
||||
result.push(suffixedDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public function hasDifficulty(diffId:String, ?variationId:String, ?variationIds:Array<String>):Bool
|
||||
{
|
||||
if (variationIds == null) variationIds = [];
|
||||
|
@ -706,10 +725,11 @@ class SongDifficulty
|
|||
* Cache the vocals for a given character.
|
||||
* @param id The character we are about to play.
|
||||
*/
|
||||
public inline function cacheVocals():Void
|
||||
public function cacheVocals():Void
|
||||
{
|
||||
for (voice in buildVoiceList())
|
||||
{
|
||||
trace('Caching vocal track: $voice');
|
||||
FlxG.sound.cache(voice);
|
||||
}
|
||||
}
|
||||
|
@ -721,6 +741,20 @@ class SongDifficulty
|
|||
* @param id The character we are about to play.
|
||||
*/
|
||||
public function buildVoiceList():Array<String>
|
||||
{
|
||||
var result:Array<String> = [];
|
||||
result = result.concat(buildPlayerVoiceList());
|
||||
result = result.concat(buildOpponentVoiceList());
|
||||
if (result.length == 0)
|
||||
{
|
||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||
// Try to use `Voices.ogg` if no other voices are found.
|
||||
if (Assets.exists(Paths.voices(this.song.id, ''))) result.push(Paths.voices(this.song.id, '$suffix'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public function buildPlayerVoiceList():Array<String>
|
||||
{
|
||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||
|
||||
|
@ -728,62 +762,88 @@ class SongDifficulty
|
|||
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
|
||||
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
|
||||
|
||||
var playerId:String = characters.player;
|
||||
var voicePlayer:String = Paths.voices(this.song.id, '-$playerId$suffix');
|
||||
while (voicePlayer != null && !Assets.exists(voicePlayer))
|
||||
if (characters.playerVocals == null)
|
||||
{
|
||||
// Remove the last suffix.
|
||||
// For example, bf-car becomes bf.
|
||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
}
|
||||
if (voicePlayer == null)
|
||||
{
|
||||
// Try again without $suffix.
|
||||
playerId = characters.player;
|
||||
voicePlayer = Paths.voices(this.song.id, '-${playerId}');
|
||||
while (voicePlayer != null && !Assets.exists(voicePlayer))
|
||||
var playerId:String = characters.player;
|
||||
var playerVoice:String = Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
|
||||
while (playerVoice != null && !Assets.exists(playerVoice))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
// For example, bf-car becomes bf.
|
||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
}
|
||||
if (playerVoice == null)
|
||||
{
|
||||
// Try again without $suffix.
|
||||
playerId = characters.player;
|
||||
playerVoice = Paths.voices(this.song.id, '-${playerId}');
|
||||
while (playerVoice != null && !Assets.exists(playerVoice))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var opponentId:String = characters.opponent;
|
||||
var voiceOpponent:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
while (voiceOpponent != null && !Assets.exists(voiceOpponent))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
return playerVoice != null ? [playerVoice] : [];
|
||||
}
|
||||
if (voiceOpponent == null)
|
||||
else
|
||||
{
|
||||
// Try again without $suffix.
|
||||
opponentId = characters.opponent;
|
||||
voiceOpponent = Paths.voices(this.song.id, '-${opponentId}');
|
||||
while (voiceOpponent != null && !Assets.exists(voiceOpponent))
|
||||
// The metadata explicitly defines the list of voices.
|
||||
var playerIds:Array<String> = characters?.playerVocals ?? [characters.player];
|
||||
var playerVoices:Array<String> = playerIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
|
||||
|
||||
return playerVoices;
|
||||
}
|
||||
}
|
||||
|
||||
public function buildOpponentVoiceList():Array<String>
|
||||
{
|
||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||
|
||||
// Automatically resolve voices by removing suffixes.
|
||||
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
|
||||
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
|
||||
|
||||
if (characters.opponentVocals == null)
|
||||
{
|
||||
var opponentId:String = characters.opponent;
|
||||
var opponentVoice:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
while (opponentVoice != null && !Assets.exists(opponentVoice))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
}
|
||||
if (opponentVoice == null)
|
||||
{
|
||||
// Try again without $suffix.
|
||||
opponentId = characters.opponent;
|
||||
opponentVoice = Paths.voices(this.song.id, '-${opponentId}');
|
||||
while (opponentVoice != null && !Assets.exists(opponentVoice))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result:Array<String> = [];
|
||||
if (voicePlayer != null) result.push(voicePlayer);
|
||||
if (voiceOpponent != null) result.push(voiceOpponent);
|
||||
if (voicePlayer == null && voiceOpponent == null)
|
||||
{
|
||||
// Try to use `Voices.ogg` if no other voices are found.
|
||||
if (Assets.exists(Paths.voices(this.song.id, ''))) result.push(Paths.voices(this.song.id, '$suffix'));
|
||||
return opponentVoice != null ? [opponentVoice] : [];
|
||||
}
|
||||
else
|
||||
{
|
||||
// The metadata explicitly defines the list of voices.
|
||||
var opponentIds:Array<String> = characters?.opponentVocals ?? [characters.opponent];
|
||||
var opponentVoices:Array<String> = opponentIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
|
||||
|
||||
return opponentVoices;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -795,26 +855,19 @@ class SongDifficulty
|
|||
{
|
||||
var result:VoicesGroup = new VoicesGroup();
|
||||
|
||||
var voiceList:Array<String> = buildVoiceList();
|
||||
|
||||
if (voiceList.length == 0)
|
||||
{
|
||||
trace('Could not find any voices for song ${this.song.id}');
|
||||
return result;
|
||||
}
|
||||
var playerVoiceList:Array<String> = this.buildPlayerVoiceList();
|
||||
var opponentVoiceList:Array<String> = this.buildOpponentVoiceList();
|
||||
|
||||
// Add player vocals.
|
||||
if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(voiceList[0]));
|
||||
// Add opponent vocals.
|
||||
if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(voiceList[1]));
|
||||
|
||||
// Add additional vocals.
|
||||
if (voiceList.length > 2)
|
||||
for (playerVoice in playerVoiceList)
|
||||
{
|
||||
for (i in 2...voiceList.length)
|
||||
{
|
||||
result.add(FunkinSound.load(Assets.getSound(voiceList[i])));
|
||||
}
|
||||
result.addPlayerVoice(FunkinSound.load(playerVoice));
|
||||
}
|
||||
|
||||
// Add opponent vocals.
|
||||
for (opponentVoice in opponentVoiceList)
|
||||
{
|
||||
result.addOpponentVoice(FunkinSound.load(opponentVoice));
|
||||
}
|
||||
|
||||
result.playerVoicesOffset = offsets.getVocalOffset(characters.player);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
|
@ -45,8 +46,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
public var idleSuffix(default, set):String = '';
|
||||
|
||||
/**
|
||||
* If this bopper is rendered with pixel art,
|
||||
* disable anti-aliasing and render at 6x scale.
|
||||
* If this bopper is rendered with pixel art, disable anti-aliasing.
|
||||
* @default `false`
|
||||
*/
|
||||
public var isPixel(default, set):Bool = false;
|
||||
|
||||
|
@ -79,11 +80,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
if (globalOffsets == null) globalOffsets = [0, 0];
|
||||
if (globalOffsets == value) return value;
|
||||
|
||||
var xDiff = globalOffsets[0] - value[0];
|
||||
var yDiff = globalOffsets[1] - value[1];
|
||||
|
||||
this.x += xDiff;
|
||||
this.y += yDiff;
|
||||
return globalOffsets = value;
|
||||
}
|
||||
|
||||
|
@ -97,12 +93,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
if (animOffsets == null) animOffsets = [0, 0];
|
||||
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
|
||||
|
||||
var xDiff = animOffsets[0] - value[0];
|
||||
var yDiff = animOffsets[1] - value[1];
|
||||
|
||||
this.x += xDiff;
|
||||
this.y += yDiff;
|
||||
|
||||
return animOffsets = value;
|
||||
}
|
||||
|
||||
|
@ -320,19 +310,12 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
function applyAnimationOffsets(name:String):Void
|
||||
{
|
||||
var offsets = animationOffsets.get(name);
|
||||
if (offsets != null && !(offsets[0] == 0 && offsets[1] == 0))
|
||||
{
|
||||
this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animOffsets = globalOffsets;
|
||||
}
|
||||
this.animOffsets = offsets;
|
||||
}
|
||||
|
||||
public function isAnimationFinished():Bool
|
||||
{
|
||||
return this.animation.finished;
|
||||
return this.animation?.finished ?? false;
|
||||
}
|
||||
|
||||
public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void
|
||||
|
@ -351,6 +334,15 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
return this.animation.curAnim.name;
|
||||
}
|
||||
|
||||
// override getScreenPosition (used by FlxSprite's draw method) to account for animation offsets.
|
||||
override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
|
||||
{
|
||||
var output:FlxPoint = super.getScreenPosition(result, camera);
|
||||
output.x -= (animOffsets[0] - globalOffsets[0]) * this.scale.x;
|
||||
output.y -= (animOffsets[1] - globalOffsets[1]) * this.scale.y;
|
||||
return output;
|
||||
}
|
||||
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
|
|
@ -386,7 +386,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
{
|
||||
if (character == null) return;
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Temporary marker that shows where the character's location is relative to.
|
||||
// Should display at the stage position of the character (before any offsets).
|
||||
// TODO: Make this a toggle? It's useful to turn on from time to time.
|
||||
|
@ -436,8 +436,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
// Start with the per-stage character position.
|
||||
// Subtracting the origin ensures characters are positioned relative to their feet.
|
||||
// Subtracting the global offset allows positioning on a per-character basis.
|
||||
character.x = stageCharData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
||||
character.y = stageCharData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
||||
// We previously applied the global offset here but that is now done elsewhere.
|
||||
character.x = stageCharData.position[0] - character.characterOrigin.x;
|
||||
character.y = stageCharData.position[1] - character.characterOrigin.y;
|
||||
|
||||
@:privateAccess(funkin.play.stage.Bopper)
|
||||
{
|
||||
|
@ -451,7 +452,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
character.cameraFocusPoint.x += stageCharData.cameraOffsets[0];
|
||||
character.cameraFocusPoint.y += stageCharData.cameraOffsets[1];
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Draw the debug icon at the character's feet.
|
||||
if (charType == BF || charType == DAD)
|
||||
{
|
||||
|
@ -468,7 +469,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
|
||||
ScriptEventDispatcher.callEvent(character, new ScriptEvent(ADDED, false));
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
debugIconGroup.add(debugIcon);
|
||||
debugIconGroup.add(debugIcon2);
|
||||
#end
|
||||
|
@ -769,39 +770,15 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
* A function that gets called once per step in the song.
|
||||
* @param curStep The current step number.
|
||||
*/
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
// Make sure to call super.onStepHit(event) if you want to keep the boppers dancing.
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
||||
|
||||
/**
|
||||
* A function that gets called once per beat in the song (once every four steps).
|
||||
* @param curStep The current beat number.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
// Make sure to call super.onBeatHit(event) if you want to keep the boppers dancing.
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void {}
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onUpdate(event:UpdateScriptEvent) {}
|
||||
|
||||
public override function kill()
|
||||
{
|
||||
|
@ -883,129 +860,41 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
|
||||
public function onScriptEvent(event:ScriptEvent)
|
||||
{
|
||||
// Ensure all custom events get broadcast to the elements of the stage.
|
||||
// If we do it here, we don't have to add a handler to EACH script event function.
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
|
||||
public function onPause(event:PauseScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
||||
public function onResume(event:ScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onResume(event:ScriptEvent) {}
|
||||
|
||||
public function onSongStart(event:ScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onSongStart(event:ScriptEvent) {}
|
||||
|
||||
public function onSongEnd(event:ScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onSongEnd(event:ScriptEvent) {}
|
||||
|
||||
public function onGameOver(event:ScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onCountdownStart(event:CountdownScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownStep(event:CountdownScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onCountdownEnd(event:CountdownScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onNoteIncoming(event:NoteScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:HitNoteScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
public function onSongEvent(event:SongEventScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onSongEvent(event:SongEventScriptEvent) {}
|
||||
|
||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||
|
||||
public function onSongLoaded(event:SongLoadScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
||||
|
||||
public function onSongRetry(event:ScriptEvent)
|
||||
{
|
||||
for (bopper in boppers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(bopper, event);
|
||||
}
|
||||
}
|
||||
public function onSongRetry(event:ScriptEvent) {}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,12 @@ class Save
|
|||
modOptions: [],
|
||||
},
|
||||
|
||||
unlocks:
|
||||
{
|
||||
// Default to having seen the default character.
|
||||
charactersSeen: ["bf"],
|
||||
},
|
||||
|
||||
optionsChartEditor:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
|
@ -393,6 +399,22 @@ class Save
|
|||
return data.optionsChartEditor.playbackSpeed;
|
||||
}
|
||||
|
||||
public var charactersSeen(get, never):Array<String>;
|
||||
|
||||
function get_charactersSeen():Array<String>
|
||||
{
|
||||
return data.unlocks.charactersSeen;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we've seen a character unlock, add it to the list of characters seen.
|
||||
* @param character
|
||||
*/
|
||||
public function addCharacterSeen(character:String):Void
|
||||
{
|
||||
data.unlocks.charactersSeen.push(character);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the score the user achieved for a given level on a given difficulty.
|
||||
*
|
||||
|
@ -471,10 +493,18 @@ class Save
|
|||
for (difficulty in difficultyList)
|
||||
{
|
||||
var score:Null<SaveScoreData> = getLevelScore(levelId, difficulty);
|
||||
// TODO: Do we need to check accuracy/score here?
|
||||
if (score != null)
|
||||
{
|
||||
return true;
|
||||
if (score.score > 0)
|
||||
{
|
||||
// Level has score data, which means we cleared it!
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Level has score data, but the score is 0.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -630,10 +660,18 @@ class Save
|
|||
for (difficulty in difficultyList)
|
||||
{
|
||||
var score:Null<SaveScoreData> = getSongScore(songId, difficulty);
|
||||
// TODO: Do we need to check accuracy/score here?
|
||||
if (score != null)
|
||||
{
|
||||
return true;
|
||||
if (score.score > 0)
|
||||
{
|
||||
// Level has score data, which means we cleared it!
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Level has score data, but the score is 0.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -956,6 +994,8 @@ typedef RawSaveData =
|
|||
*/
|
||||
var options:SaveDataOptions;
|
||||
|
||||
var unlocks:SaveDataUnlocks;
|
||||
|
||||
/**
|
||||
* The user's favorited songs in the Freeplay menu,
|
||||
* as a list of song IDs.
|
||||
|
@ -980,6 +1020,15 @@ typedef SaveApiNewgroundsData =
|
|||
var sessionId:Null<String>;
|
||||
}
|
||||
|
||||
typedef SaveDataUnlocks =
|
||||
{
|
||||
/**
|
||||
* Every time we see the unlock animation for a character,
|
||||
* add it to this list so that we don't show it again.
|
||||
*/
|
||||
var charactersSeen:Array<String>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An anoymous structure containing options about the user's high scores.
|
||||
*/
|
||||
|
|
|
@ -152,6 +152,32 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
|||
}
|
||||
}
|
||||
|
||||
public function getWidth():Int
|
||||
{
|
||||
var width = 0;
|
||||
for (char in this.text.split(""))
|
||||
{
|
||||
switch (char)
|
||||
{
|
||||
case " ":
|
||||
{
|
||||
width += 40;
|
||||
}
|
||||
case "\n":
|
||||
{}
|
||||
case char:
|
||||
{
|
||||
var sprite = new AtlasChar(atlas, char);
|
||||
sprite.revive();
|
||||
sprite.char = char;
|
||||
sprite.alpha = 1;
|
||||
width += Std.int(sprite.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
override function toString()
|
||||
{
|
||||
return "InputItem, " + FlxStringUtil.getDebugString([
|
||||
|
|
|
@ -78,9 +78,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
{
|
||||
// Emergency exit button.
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
// This can now be used in EVERY STATE YAY!
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
|
@ -114,12 +111,10 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
ModuleHandler.callEvent(event);
|
||||
}
|
||||
|
||||
function debug_refreshModules()
|
||||
function reloadAssets()
|
||||
{
|
||||
PolymodHandler.forceReloadAssets();
|
||||
|
||||
this.destroy();
|
||||
|
||||
// Create a new instance of the current state, so old data is cleared.
|
||||
FlxG.resetState();
|
||||
}
|
||||
|
|
|
@ -72,9 +72,6 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
// Emergency exit button.
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
// This can now be used in EVERY STATE YAY!
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
|
||||
// Display Conductor info in the watch window.
|
||||
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
||||
Conductor.watchQuick(conductorInUse);
|
||||
|
@ -82,7 +79,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
||||
function debug_refreshModules()
|
||||
function reloadAssets()
|
||||
{
|
||||
PolymodHandler.forceReloadAssets();
|
||||
|
||||
|
|
|
@ -22,14 +22,26 @@ class PixelatedIcon extends FlxSprite
|
|||
|
||||
switch (char)
|
||||
{
|
||||
case 'monster-christmas':
|
||||
charPath += 'monsterpixel';
|
||||
case 'mom-car':
|
||||
charPath += 'mommypixel';
|
||||
case 'darnell-blazin':
|
||||
charPath += 'darnellpixel';
|
||||
case 'senpai-angry':
|
||||
charPath += 'senpaipixel';
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker":
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas":
|
||||
charPath += "tankmanpixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class CharSelectPlayer extends FlxAtlasSprite
|
|||
{
|
||||
super(x, y, Paths.animateAtlas("charSelect/bfChill"));
|
||||
|
||||
onAnimationFinish.add(function(animLabel:String) {
|
||||
onAnimationComplete.add(function(animLabel:String) {
|
||||
switch (animLabel)
|
||||
{
|
||||
case "slidein":
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
package funkin.ui.charSelect;
|
||||
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.ui.PixelatedIcon;
|
||||
import flixel.system.debug.watch.Tracker.TrackerProfile;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.tweens.FlxTween;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.play.stage.Stage;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.system.debug.watch.Tracker.TrackerProfile;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxObject;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.group.FlxGroup;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.PixelatedIcon;
|
||||
import funkin.util.MathUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
import openfl.display.BlendMode;
|
||||
|
||||
class CharSelectSubState extends MusicBeatSubState
|
||||
{
|
||||
|
@ -67,8 +71,29 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
{
|
||||
super();
|
||||
|
||||
availableChars.set(4, "bf");
|
||||
availableChars.set(3, "pico");
|
||||
loadAvailableCharacters();
|
||||
}
|
||||
|
||||
function loadAvailableCharacters():Void
|
||||
{
|
||||
var playerIds:Array<String> = PlayerRegistry.instance.listEntryIds();
|
||||
|
||||
for (playerId in playerIds)
|
||||
{
|
||||
var player:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerId);
|
||||
if (player == null) continue;
|
||||
var playerData = player.getCharSelectData();
|
||||
if (playerData == null) continue;
|
||||
|
||||
var targetPosition:Int = playerData.position ?? 0;
|
||||
while (availableChars.exists(targetPosition))
|
||||
{
|
||||
targetPosition += 1;
|
||||
}
|
||||
|
||||
trace('Placing player ${playerId} at position ${targetPosition}');
|
||||
availableChars.set(targetPosition, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
override public function create():Void
|
||||
|
@ -245,7 +270,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
cursorBlue.scrollFactor.set();
|
||||
cursorDarkBlue.scrollFactor.set();
|
||||
|
||||
FlxTween.color(cursor, 0.2, 0xFFFFFF00, 0xFFFFCC00, {type: FlxTween.PINGPONG});
|
||||
FlxTween.color(cursor, 0.2, 0xFFFFFF00, 0xFFFFCC00, {type: PINGPONG});
|
||||
|
||||
// FlxG.debugger.track(cursor);
|
||||
|
||||
|
@ -269,7 +294,6 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
var grpIcons:FlxSpriteGroup;
|
||||
|
||||
var grpXSpread(default, set):Float = 107;
|
||||
var grpYSpread(default, set):Float = 127;
|
||||
|
||||
|
@ -600,7 +624,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
playerChill.visible = false;
|
||||
playerChillOut.visible = true;
|
||||
playerChillOut.anim.goToFrameLabel("slideout");
|
||||
playerChillOut.anim.callback = (_, frame:Int) -> {
|
||||
playerChillOut.onAnimationFrame.add((_, frame:Int) -> {
|
||||
if (frame == playerChillOut.anim.getFrameLabel("slideout").index + 1)
|
||||
{
|
||||
playerChill.visible = true;
|
||||
|
@ -612,7 +636,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
playerChillOut.switchChar(value);
|
||||
playerChillOut.visible = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,13 @@ class CreditsState extends MusicBeatState
|
|||
* To use a font from the `assets` folder, use `Paths.font(...)`.
|
||||
* Choose something that will render Unicode properly.
|
||||
*/
|
||||
#if windows
|
||||
static final CREDITS_FONT = 'Consolas';
|
||||
#elseif mac
|
||||
static final CREDITS_FONT = 'Menlo';
|
||||
#else
|
||||
static final CREDITS_FONT = "Courier New";
|
||||
#end
|
||||
|
||||
/**
|
||||
* The size of the font.
|
||||
|
|
|
@ -54,7 +54,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
|
||||
// Create each menu item.
|
||||
// Call onMenuChange when the first item is created to move the camera .
|
||||
#if CHART_EDITOR_SUPPORTED
|
||||
#if FEATURE_CHART_EDITOR
|
||||
onMenuChange(createItem("CHART EDITOR", openChartEditor));
|
||||
#end
|
||||
// createItem("Input Offset Testing", openInputOffsetTesting);
|
||||
|
|
|
@ -1,33 +1,26 @@
|
|||
package funkin.ui.debug.anim;
|
||||
|
||||
import flixel.addons.display.FlxBackdrop;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.ui.FlxInputText;
|
||||
import flixel.addons.ui.FlxUIDropDownMenu;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.character.SparrowCharacter;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.util.MouseUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.ItemEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
|
@ -36,9 +29,6 @@ import openfl.events.Event;
|
|||
import openfl.events.IOErrorEvent;
|
||||
import openfl.geom.Rectangle;
|
||||
import openfl.net.FileReference;
|
||||
import openfl.net.URLLoader;
|
||||
import openfl.net.URLRequest;
|
||||
import openfl.utils.ByteArray;
|
||||
|
||||
using flixel.util.FlxSpriteUtil;
|
||||
|
||||
|
@ -55,10 +45,10 @@ class DebugBoundingState extends FlxState
|
|||
TODAY'S TO-DO
|
||||
- Cleaner UI
|
||||
*/
|
||||
var bg:FlxSprite;
|
||||
var bg:FlxBackdrop;
|
||||
var fileInfo:FlxText;
|
||||
|
||||
var txtGrp:FlxGroup;
|
||||
var txtGrp:FlxTypedGroup<FlxText>;
|
||||
|
||||
var hudCam:FlxCamera;
|
||||
|
||||
|
@ -66,16 +56,23 @@ class DebugBoundingState extends FlxState
|
|||
|
||||
var spriteSheetView:FlxGroup;
|
||||
var offsetView:FlxGroup;
|
||||
var animDropDownMenu:FlxUIDropDownMenu;
|
||||
var dropDownSetup:Bool = false;
|
||||
|
||||
var onionSkinChar:FlxSprite;
|
||||
var txtOffsetShit:FlxText;
|
||||
|
||||
var uiStuff:Component;
|
||||
var offsetEditorDialog:CollapsibleDialog;
|
||||
var offsetAnimationDropdown:DropDown;
|
||||
|
||||
var haxeUIFocused(get, default):Bool = false;
|
||||
|
||||
var currentAnimationName(get, never):String;
|
||||
|
||||
function get_currentAnimationName():String
|
||||
{
|
||||
return offsetAnimationDropdown?.value?.id ?? "idle";
|
||||
}
|
||||
|
||||
function get_haxeUIFocused():Bool
|
||||
{
|
||||
// get the screen position, according to the HUD camera, temp default to FlxG.camera juuust in case?
|
||||
|
@ -87,46 +84,35 @@ class DebugBoundingState extends FlxState
|
|||
{
|
||||
Paths.setCurrentLevel('week1');
|
||||
|
||||
// lv.
|
||||
// lv.onChange = function(e:UIEvent)
|
||||
// {
|
||||
// trace(e.type);
|
||||
// // trace(e.data.curView);
|
||||
// // var item:haxe.ui.core.ItemRenderer = cast e.target;
|
||||
// trace(e.target);
|
||||
// // if (e.type == "change")
|
||||
// // {
|
||||
// // curView = cast e.data;
|
||||
// // }
|
||||
// };
|
||||
|
||||
hudCam = new FlxCamera();
|
||||
hudCam.bgColor.alpha = 0;
|
||||
|
||||
bg = FlxGridOverlay.create(10, 10);
|
||||
// bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.GREEN);
|
||||
|
||||
bg.scrollFactor.set();
|
||||
bg = new FlxBackdrop(FlxGridOverlay.createGrid(10, 10, FlxG.width, FlxG.height, true, 0xffe7e6e6, 0xffd9d5d5));
|
||||
add(bg);
|
||||
|
||||
// we are setting this as the default draw camera only temporarily, to trick haxeui
|
||||
FlxG.cameras.add(hudCam);
|
||||
|
||||
var str = Paths.xml('ui/animation-editor/offset-editor-view');
|
||||
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
||||
offsetEditorDialog = cast RuntimeComponentBuilder.fromAsset(str);
|
||||
|
||||
// uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
|
||||
var dropdown:DropDown = cast uiStuff.findComponent("swapper");
|
||||
dropdown.onChange = function(e:UIEvent) {
|
||||
// offsetEditorDialog.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
|
||||
var viewDropdown:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
|
||||
viewDropdown.onChange = function(e:UIEvent) {
|
||||
trace(e.type);
|
||||
curView = cast e.data.curView;
|
||||
trace(e.data);
|
||||
// trace(e.data);
|
||||
};
|
||||
|
||||
uiStuff.cameras = [hudCam];
|
||||
offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
|
||||
|
||||
add(uiStuff);
|
||||
offsetEditorDialog.cameras = [hudCam];
|
||||
|
||||
add(offsetEditorDialog);
|
||||
|
||||
// Anchor to the right side by default
|
||||
// offsetEditorDialog.x = FlxG.width - offsetEditorDialog.width;
|
||||
|
||||
// sets the default camera back to FlxG.camera, since we set it to hudCamera for haxeui stuf
|
||||
FlxG.cameras.setDefaultDrawTarget(FlxG.camera, true);
|
||||
|
@ -159,7 +145,7 @@ class DebugBoundingState extends FlxState
|
|||
|
||||
generateOutlines(tex.frames);
|
||||
|
||||
txtGrp = new FlxGroup();
|
||||
txtGrp = new FlxTypedGroup<FlxText>();
|
||||
txtGrp.cameras = [hudCam];
|
||||
spriteSheetView.add(txtGrp);
|
||||
|
||||
|
@ -168,64 +154,6 @@ class DebugBoundingState extends FlxState
|
|||
addInfo('Height', bf.height);
|
||||
|
||||
spriteSheetView.add(swagOutlines);
|
||||
|
||||
FlxG.stage.window.onDropFile.add(function(path:String) {
|
||||
// WACKY ASS TESTING SHIT FOR WEB FILE LOADING??
|
||||
#if web
|
||||
var swagList:FileList = cast path;
|
||||
|
||||
var objShit = js.html.URL.createObjectURL(swagList.item(0));
|
||||
trace(objShit);
|
||||
|
||||
var funnysound = new FunkinSound().loadStream('https://cdn.discordapp.com/attachments/767500676166451231/817821618251759666/Flutter.mp3', false, false,
|
||||
null, function() {
|
||||
trace('LOADED SHIT??');
|
||||
});
|
||||
|
||||
funnysound.volume = 1;
|
||||
funnysound.play();
|
||||
|
||||
var urlShit = new URLLoader(new URLRequest(objShit));
|
||||
|
||||
new FlxTimer().start(3, function(tmr:FlxTimer) {
|
||||
// music lol!
|
||||
if (urlShit.dataFormat == BINARY)
|
||||
{
|
||||
// var daSwagBytes:ByteArray = urlShit.data;
|
||||
|
||||
// FlxG.sound.playMusic();
|
||||
|
||||
// trace('is binary!!');
|
||||
}
|
||||
trace(urlShit.dataFormat);
|
||||
});
|
||||
|
||||
// remove(bf);
|
||||
// FlxG.bitmap.removeByKey(Paths.image('characters/temp'));
|
||||
// Assets.cache.clear();
|
||||
|
||||
// bf.loadGraphic(objShit);
|
||||
// add(bf);
|
||||
|
||||
// trace(swagList.item(0).name);
|
||||
// var urlShit = js.html.URL.createObjectURL(path);
|
||||
#end
|
||||
|
||||
#if sys
|
||||
trace("DROPPED FILE FROM: " + Std.string(path));
|
||||
var newPath = "./" + Paths.image('characters/temp');
|
||||
File.copy(path, newPath);
|
||||
|
||||
var swag = Paths.image('characters/temp');
|
||||
|
||||
if (bf != null) remove(bf);
|
||||
FlxG.bitmap.removeByKey(Paths.image('characters/temp'));
|
||||
Assets.cache.clear();
|
||||
|
||||
bf.loadGraphic(Paths.image('characters/temp'));
|
||||
add(bf);
|
||||
#end
|
||||
});
|
||||
}
|
||||
|
||||
function generateOutlines(frameShit:Array<FlxFrame>):Void
|
||||
|
@ -260,15 +188,9 @@ class DebugBoundingState extends FlxState
|
|||
txtOffsetShit = new FlxText(20, 20, 0, "", 20);
|
||||
txtOffsetShit.setFormat(Paths.font("vcr.ttf"), 26, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
txtOffsetShit.cameras = [hudCam];
|
||||
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
||||
offsetView.add(txtOffsetShit);
|
||||
|
||||
animDropDownMenu = new FlxUIDropDownMenu(0, 0, FlxUIDropDownMenu.makeStrIdLabelArray(['weed'], true));
|
||||
animDropDownMenu.cameras = [hudCam];
|
||||
// Move to bottom right corner
|
||||
animDropDownMenu.x = FlxG.width - animDropDownMenu.width - 20;
|
||||
animDropDownMenu.y = FlxG.height - animDropDownMenu.height - 20;
|
||||
offsetView.add(animDropDownMenu);
|
||||
|
||||
var characters:Array<String> = CharacterDataParser.listCharacterIds();
|
||||
characters = characters.filter(function(charId:String) {
|
||||
var char = CharacterDataParser.fetchCharacterData(charId);
|
||||
|
@ -276,7 +198,7 @@ class DebugBoundingState extends FlxState
|
|||
});
|
||||
characters.sort(SortUtil.alphabetically);
|
||||
|
||||
var charDropdown:DropDown = cast uiStuff.findComponent('characterDropdown');
|
||||
var charDropdown:DropDown = offsetEditorDialog.findComponent('characterDropdown', DropDown);
|
||||
for (char in characters)
|
||||
{
|
||||
charDropdown.dataSource.add({text: char});
|
||||
|
@ -289,32 +211,47 @@ class DebugBoundingState extends FlxState
|
|||
|
||||
public var mouseOffset:FlxPoint = FlxPoint.get(0, 0);
|
||||
public var oldPos:FlxPoint = FlxPoint.get(0, 0);
|
||||
public var movingCharacter:Bool = false;
|
||||
|
||||
function mouseOffsetMovement()
|
||||
{
|
||||
if (swagChar != null)
|
||||
{
|
||||
if (FlxG.mouse.justPressed)
|
||||
if (FlxG.mouse.justPressed && !haxeUIFocused)
|
||||
{
|
||||
movingCharacter = true;
|
||||
mouseOffset.set(FlxG.mouse.x - -swagChar.animOffsets[0], FlxG.mouse.y - -swagChar.animOffsets[1]);
|
||||
}
|
||||
|
||||
if (!movingCharacter) return;
|
||||
|
||||
if (FlxG.mouse.pressed)
|
||||
{
|
||||
swagChar.animOffsets = [(FlxG.mouse.x - mouseOffset.x) * -1, (FlxG.mouse.y - mouseOffset.y) * -1];
|
||||
|
||||
swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, swagChar.animOffsets);
|
||||
swagChar.animationOffsets.set(offsetAnimationDropdown.value.id, swagChar.animOffsets);
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
||||
}
|
||||
|
||||
if (FlxG.mouse.justReleased)
|
||||
{
|
||||
movingCharacter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addInfo(str:String, value:Dynamic)
|
||||
{
|
||||
var swagText:FlxText = new FlxText(10, 10 + (28 * txtGrp.length));
|
||||
var swagText:FlxText = new FlxText(10, FlxG.height - 32);
|
||||
swagText.setFormat(Paths.font("vcr.ttf"), 26, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
swagText.scrollFactor.set();
|
||||
|
||||
for (text in txtGrp.members)
|
||||
{
|
||||
text.y -= swagText.height;
|
||||
}
|
||||
txtGrp.add(swagText);
|
||||
|
||||
swagText.text = str + ": " + Std.string(value);
|
||||
|
@ -345,14 +282,14 @@ class DebugBoundingState extends FlxState
|
|||
{
|
||||
if (FlxG.keys.justPressed.ONE)
|
||||
{
|
||||
var lv:DropDown = cast uiStuff.findComponent("swapper");
|
||||
var lv:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
|
||||
lv.selectedIndex = 0;
|
||||
curView = SPRITESHEET;
|
||||
}
|
||||
|
||||
if (FlxG.keys.justReleased.TWO)
|
||||
{
|
||||
var lv:DropDown = cast uiStuff.findComponent("swapper");
|
||||
var lv:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
|
||||
lv.selectedIndex = 1;
|
||||
curView = ANIMATIONS;
|
||||
if (swagChar != null)
|
||||
|
@ -368,12 +305,14 @@ class DebugBoundingState extends FlxState
|
|||
spriteSheetView.visible = true;
|
||||
offsetView.visible = false;
|
||||
offsetView.active = false;
|
||||
offsetAnimationDropdown.visible = false;
|
||||
case ANIMATIONS:
|
||||
spriteSheetView.visible = false;
|
||||
offsetView.visible = true;
|
||||
offsetView.active = true;
|
||||
offsetAnimationDropdown.visible = true;
|
||||
offsetControls();
|
||||
if (!haxeUIFocused) mouseOffsetMovement();
|
||||
mouseOffsetMovement();
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
|
||||
|
@ -395,24 +334,36 @@ class DebugBoundingState extends FlxState
|
|||
{
|
||||
if (FlxG.keys.justPressed.RBRACKET || FlxG.keys.justPressed.E)
|
||||
{
|
||||
if (Std.parseInt(animDropDownMenu.selectedId) + 1 <= animDropDownMenu.length)
|
||||
animDropDownMenu.selectedId = Std.string(Std.parseInt(animDropDownMenu.selectedId)
|
||||
+ 1);
|
||||
if (offsetAnimationDropdown.selectedIndex + 1 <= offsetAnimationDropdown.dataSource.size)
|
||||
{
|
||||
offsetAnimationDropdown.selectedIndex += 1;
|
||||
}
|
||||
else
|
||||
animDropDownMenu.selectedId = Std.string(0);
|
||||
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||
{
|
||||
offsetAnimationDropdown.selectedIndex = 0;
|
||||
}
|
||||
trace(offsetAnimationDropdown.selectedIndex);
|
||||
trace(offsetAnimationDropdown.dataSource.size);
|
||||
trace(offsetAnimationDropdown.value);
|
||||
trace(currentAnimationName);
|
||||
playCharacterAnimation(currentAnimationName, true);
|
||||
}
|
||||
if (FlxG.keys.justPressed.LBRACKET || FlxG.keys.justPressed.Q)
|
||||
{
|
||||
if (Std.parseInt(animDropDownMenu.selectedId) - 1 >= 0) animDropDownMenu.selectedId = Std.string(Std.parseInt(animDropDownMenu.selectedId) - 1);
|
||||
if (offsetAnimationDropdown.selectedIndex - 1 >= 0)
|
||||
{
|
||||
offsetAnimationDropdown.selectedIndex -= 1;
|
||||
}
|
||||
else
|
||||
animDropDownMenu.selectedId = Std.string(animDropDownMenu.length - 1);
|
||||
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||
{
|
||||
offsetAnimationDropdown.selectedIndex = offsetAnimationDropdown.dataSource.size - 1;
|
||||
}
|
||||
playCharacterAnimation(currentAnimationName, true);
|
||||
}
|
||||
|
||||
// Keyboards controls for general WASD "movement"
|
||||
// modifies the animDropDownMenu so that it's properly updated and shit
|
||||
// and then it's just played and updated from the animDropDownMenu callback, which is set in the loadAnimShit() function probabbly
|
||||
// modifies the animDrooffsetAnimationDropdownpDownMenu so that it's properly updated and shit
|
||||
// and then it's just played and updated from the offsetAnimationDropdown callback, which is set in the loadAnimShit() function probabbly
|
||||
if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S || FlxG.keys.justPressed.D || FlxG.keys.justPressed.A)
|
||||
{
|
||||
var suffix:String = '';
|
||||
|
@ -425,18 +376,19 @@ class DebugBoundingState extends FlxState
|
|||
if (FlxG.keys.justPressed.A) targetLabel = 'singLEFT$suffix';
|
||||
if (FlxG.keys.justPressed.D) targetLabel = 'singRIGHT$suffix';
|
||||
|
||||
if (targetLabel != animDropDownMenu.selectedLabel)
|
||||
if (targetLabel != currentAnimationName)
|
||||
{
|
||||
offsetAnimationDropdown.value = {id: targetLabel, text: targetLabel};
|
||||
|
||||
// Play the new animation if the IDs are the different.
|
||||
// Override the onion skin.
|
||||
animDropDownMenu.selectedLabel = targetLabel;
|
||||
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||
playCharacterAnimation(currentAnimationName, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replay the current animation if the IDs are the same.
|
||||
// Don't override the onion skin.
|
||||
playCharacterAnimation(animDropDownMenu.selectedId, false);
|
||||
playCharacterAnimation(currentAnimationName, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,16 +400,20 @@ class DebugBoundingState extends FlxState
|
|||
// Plays the idle animation
|
||||
if (FlxG.keys.justPressed.SPACE)
|
||||
{
|
||||
animDropDownMenu.selectedLabel = 'idle';
|
||||
playCharacterAnimation(animDropDownMenu.selectedId, true);
|
||||
offsetAnimationDropdown.value = {id: 'idle', text: 'idle'};
|
||||
|
||||
playCharacterAnimation(currentAnimationName, true);
|
||||
}
|
||||
|
||||
// Playback the animation
|
||||
if (FlxG.keys.justPressed.ENTER) playCharacterAnimation(animDropDownMenu.selectedId, false);
|
||||
if (FlxG.keys.justPressed.ENTER)
|
||||
{
|
||||
playCharacterAnimation(currentAnimationName, false);
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN)
|
||||
{
|
||||
var animName = animDropDownMenu.selectedLabel;
|
||||
var animName = currentAnimationName;
|
||||
var coolValues:Array<Float> = swagChar.animationOffsets.get(animName).copy();
|
||||
|
||||
var multiplier:Int = 5;
|
||||
|
@ -471,10 +427,11 @@ class DebugBoundingState extends FlxState
|
|||
else if (FlxG.keys.justPressed.UP) coolValues[1] += 1 * multiplier;
|
||||
else if (FlxG.keys.justPressed.DOWN) coolValues[1] -= 1 * multiplier;
|
||||
|
||||
swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, coolValues);
|
||||
swagChar.animationOffsets.set(currentAnimationName, coolValues);
|
||||
swagChar.playAnimation(animName);
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + coolValues;
|
||||
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
||||
|
||||
trace(animName);
|
||||
}
|
||||
|
@ -529,7 +486,7 @@ class DebugBoundingState extends FlxState
|
|||
swagChar = CharacterDataParser.fetchCharacter(char);
|
||||
swagChar.x = 100;
|
||||
swagChar.y = 100;
|
||||
// swagChar.debugMode = true;
|
||||
swagChar.debug = true;
|
||||
offsetView.add(swagChar);
|
||||
|
||||
if (swagChar == null || swagChar.frames == null)
|
||||
|
@ -554,11 +511,25 @@ class DebugBoundingState extends FlxState
|
|||
trace(swagChar.animationOffsets[i]);
|
||||
}
|
||||
|
||||
animDropDownMenu.setData(FlxUIDropDownMenu.makeStrIdLabelArray(characterAnimNames, true));
|
||||
animDropDownMenu.callback = function(str:String) {
|
||||
playCharacterAnimation(str, true);
|
||||
};
|
||||
offsetAnimationDropdown.dataSource.clear();
|
||||
|
||||
for (charAnim in characterAnimNames)
|
||||
{
|
||||
trace('Adding ${charAnim} to HaxeUI dropdown');
|
||||
offsetAnimationDropdown.dataSource.add({id: charAnim, text: charAnim});
|
||||
}
|
||||
|
||||
offsetAnimationDropdown.selectedIndex = 0;
|
||||
|
||||
trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
|
||||
|
||||
offsetAnimationDropdown.onChange = function(event:UIEvent) {
|
||||
trace('Selected animation ${event?.data?.id}');
|
||||
playCharacterAnimation(event.data.id, true);
|
||||
}
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
||||
dropDownSetup = true;
|
||||
}
|
||||
|
||||
|
@ -575,11 +546,13 @@ class DebugBoundingState extends FlxState
|
|||
onionSkinChar.alpha = 0.6;
|
||||
}
|
||||
|
||||
var animName = characterAnimNames[Std.parseInt(str)];
|
||||
// var animName = characterAnimNames[Std.parseInt(str)];
|
||||
var animName = str;
|
||||
swagChar.playAnimation(animName, true); // trace();
|
||||
trace(swagChar.animationOffsets.get(animName));
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||
txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
|
||||
}
|
||||
|
||||
var _file:FileReference;
|
||||
|
|
|
@ -35,6 +35,7 @@ import funkin.data.song.SongData.SongEventData;
|
|||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongOffsets;
|
||||
import funkin.data.song.SongData.NoteParamData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.stage.StageData;
|
||||
|
@ -45,6 +46,7 @@ import funkin.input.TurboActionHandler;
|
|||
import funkin.input.TurboButtonHandler;
|
||||
import funkin.input.TurboKeyHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
|
@ -282,6 +284,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
public static final WELCOME_MUSIC_FADE_IN_DURATION:Float = 10.0;
|
||||
|
||||
/**
|
||||
* A map of the keys for every live input style.
|
||||
*/
|
||||
public static final LIVE_INPUT_KEYS:Map<ChartEditorLiveInputStyle, Array<FlxKey>> = [
|
||||
NumberKeys => [
|
||||
FIVE, SIX, SEVEN, EIGHT,
|
||||
ONE, TWO, THREE, FOUR
|
||||
],
|
||||
WASDKeys => [
|
||||
LEFT, DOWN, UP, RIGHT,
|
||||
A, S, W, D
|
||||
],
|
||||
None => []
|
||||
];
|
||||
|
||||
/**
|
||||
* INSTANCE DATA
|
||||
*/
|
||||
|
@ -538,6 +555,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
var noteKindToPlace:Null<String> = null;
|
||||
|
||||
/**
|
||||
* The note params to use for notes being placed in the chart. Defaults to `[]`.
|
||||
*/
|
||||
var noteParamsToPlace:Array<NoteParamData> = [];
|
||||
|
||||
/**
|
||||
* The event type to use for events being placed in the chart. Defaults to `''`.
|
||||
*/
|
||||
|
@ -1401,7 +1423,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
function get_currentSongNoteStyle():String
|
||||
{
|
||||
if (currentSongMetadata.playData.noteStyle == null)
|
||||
if (currentSongMetadata.playData.noteStyle == null
|
||||
|| currentSongMetadata.playData.noteStyle == ''
|
||||
|| currentSongMetadata.playData.noteStyle == 'item')
|
||||
{
|
||||
// Initialize to the default value if not set.
|
||||
currentSongMetadata.playData.noteStyle = Constants.DEFAULT_NOTE_STYLE;
|
||||
|
@ -2436,7 +2460,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
gridGhostNote = new ChartEditorNoteSprite(this);
|
||||
gridGhostNote.alpha = 0.6;
|
||||
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "");
|
||||
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
|
||||
gridGhostNote.visible = false;
|
||||
add(gridGhostNote);
|
||||
gridGhostNote.zIndex = 11;
|
||||
|
@ -3303,7 +3327,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
handleTestKeybinds();
|
||||
handleHelpKeybinds();
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
handleQuickWatch();
|
||||
#end
|
||||
|
||||
|
@ -3584,6 +3608,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
// The note sprite handles animation playback and positioning.
|
||||
noteSprite.noteData = noteData;
|
||||
noteSprite.noteStyle = NoteKindManager.getNoteStyleId(noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
noteSprite.overrideStepTime = null;
|
||||
noteSprite.overrideData = null;
|
||||
|
||||
|
@ -3607,6 +3632,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
holdNoteSprite.setHeightDirectly(noteLengthPixels);
|
||||
|
||||
holdNoteSprite.noteStyle = NoteKindManager.getNoteStyleId(noteSprite.noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
|
||||
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
|
||||
|
||||
trace(holdNoteSprite.x + ', ' + holdNoteSprite.y + ', ' + holdNoteSprite.width + ', ' + holdNoteSprite.height);
|
||||
|
@ -3669,9 +3696,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
holdNoteSprite.noteData = noteData;
|
||||
holdNoteSprite.noteDirection = noteData.getDirection();
|
||||
|
||||
holdNoteSprite.setHeightDirectly(noteLengthPixels);
|
||||
|
||||
holdNoteSprite.noteStyle = NoteKindManager.getNoteStyleId(noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
|
||||
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
|
||||
|
||||
displayedHoldNoteData.push(noteData);
|
||||
|
@ -4569,7 +4597,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
gridGhostHoldNote.noteData = currentPlaceNoteData;
|
||||
gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
|
||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
|
||||
|
||||
gridGhostHoldNote.noteStyle = NoteKindManager.getNoteStyleId(currentPlaceNoteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
|
||||
}
|
||||
else
|
||||
|
@ -4726,7 +4754,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
else
|
||||
{
|
||||
// Create a note and place it in the chart.
|
||||
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace);
|
||||
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace,
|
||||
ChartEditorState.cloneNoteParams(noteParamsToPlace));
|
||||
|
||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
||||
|
||||
|
@ -4885,12 +4914,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()";
|
||||
|
||||
var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace);
|
||||
var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace,
|
||||
ChartEditorState.cloneNoteParams(noteParamsToPlace));
|
||||
|
||||
if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind)
|
||||
if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind || noteParamsToPlace != noteData.params)
|
||||
{
|
||||
noteData.kind = noteKindToPlace;
|
||||
noteData.params = noteParamsToPlace;
|
||||
noteData.data = cursorColumn;
|
||||
gridGhostNote.noteStyle = NoteKindManager.getNoteStyleId(noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
gridGhostNote.playNoteAnimation();
|
||||
}
|
||||
noteData.time = cursorSnappedMs;
|
||||
|
@ -5129,46 +5161,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
function handlePlayhead():Void
|
||||
{
|
||||
// Place notes at the playhead with the keyboard.
|
||||
switch (currentLiveInputStyle)
|
||||
for (note => key in LIVE_INPUT_KEYS[currentLiveInputStyle])
|
||||
{
|
||||
case ChartEditorLiveInputStyle.WASDKeys:
|
||||
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justReleased.A) finishPlaceNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justReleased.S) finishPlaceNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justReleased.W) finishPlaceNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
|
||||
if (FlxG.keys.justReleased.D) finishPlaceNoteAtPlayhead(7);
|
||||
|
||||
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justReleased.LEFT) finishPlaceNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justReleased.DOWN) finishPlaceNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justReleased.UP) finishPlaceNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
|
||||
if (FlxG.keys.justReleased.RIGHT) finishPlaceNoteAtPlayhead(3);
|
||||
case ChartEditorLiveInputStyle.NumberKeys:
|
||||
// Flipped because Dad is on the left but represents data 0-3.
|
||||
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justReleased.ONE) finishPlaceNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justReleased.TWO) finishPlaceNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justReleased.THREE) finishPlaceNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
|
||||
if (FlxG.keys.justReleased.FOUR) finishPlaceNoteAtPlayhead(7);
|
||||
|
||||
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justReleased.FIVE) finishPlaceNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justPressed.SIX) placeNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justReleased.SEVEN) finishPlaceNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
|
||||
if (FlxG.keys.justReleased.EIGHT) finishPlaceNoteAtPlayhead(3);
|
||||
case ChartEditorLiveInputStyle.None:
|
||||
// Do nothing.
|
||||
if (FlxG.keys.checkStatus(key, JUST_PRESSED)) placeNoteAtPlayhead(note)
|
||||
else if (FlxG.keys.checkStatus(key, JUST_RELEASED)) finishPlaceNoteAtPlayhead(note);
|
||||
}
|
||||
|
||||
// Place events at playhead.
|
||||
|
@ -5196,7 +5192,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
if (notesAtPos.length == 0 && !removeNoteInstead)
|
||||
{
|
||||
trace('Placing note. ${column}');
|
||||
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace);
|
||||
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace, ChartEditorState.cloneNoteParams(noteParamsToPlace));
|
||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
||||
currentLiveInputPlaceNoteData[column] = newNoteData;
|
||||
}
|
||||
|
@ -5282,6 +5278,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
ghostHold.visible = true;
|
||||
ghostHold.alpha = 0.6;
|
||||
ghostHold.setHeightDirectly(0);
|
||||
ghostHold.noteStyle = NoteKindManager.getNoteStyleId(ghostHold.noteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
ghostHold.updateHoldNotePosition(renderedHoldNotes);
|
||||
}
|
||||
|
||||
|
@ -5648,6 +5645,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0);
|
||||
|
||||
FlxG.watch.addQuick('noteKindToPlace', noteKindToPlace);
|
||||
FlxG.watch.addQuick('noteParamsToPlace', noteParamsToPlace);
|
||||
FlxG.watch.addQuick('eventKindToPlace', eventKindToPlace);
|
||||
|
||||
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
|
||||
|
@ -5701,13 +5699,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
// TODO: Rework asset system so we can remove this jank.
|
||||
switch (currentSongStage)
|
||||
{
|
||||
case 'mainStage':
|
||||
case 'mainStage' | 'mainStageErect':
|
||||
PlayStatePlaylist.campaignId = 'week1';
|
||||
case 'spookyMansion':
|
||||
case 'spookyMansion' | 'spookyMansionErect':
|
||||
PlayStatePlaylist.campaignId = 'week2';
|
||||
case 'phillyTrain':
|
||||
case 'phillyTrain' | 'phillyTrainErect':
|
||||
PlayStatePlaylist.campaignId = 'week3';
|
||||
case 'limoRide':
|
||||
case 'limoRide' | 'limoRideErect':
|
||||
PlayStatePlaylist.campaignId = 'week4';
|
||||
case 'mallXmas' | 'mallEvil':
|
||||
PlayStatePlaylist.campaignId = 'week5';
|
||||
|
@ -6511,6 +6509,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
public static function cloneNoteParams(paramsToClone:Array<NoteParamData>):Array<NoteParamData>
|
||||
{
|
||||
var params:Array<NoteParamData> = [];
|
||||
for (param in paramsToClone)
|
||||
{
|
||||
params.push(param.clone());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@ package funkin.ui.debug.charting.components;
|
|||
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
|
@ -15,6 +16,7 @@ import flixel.math.FlxMath;
|
|||
* A sprite that can be used to display the trail of a hold note in a chart.
|
||||
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||
*/
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:nullSafety
|
||||
class ChartEditorHoldNoteSprite extends SustainTrail
|
||||
{
|
||||
|
@ -23,6 +25,22 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
*/
|
||||
public var parentState:ChartEditorState;
|
||||
|
||||
@:isVar
|
||||
public var noteStyle(get, set):Null<String>;
|
||||
|
||||
function get_noteStyle():Null<String>
|
||||
{
|
||||
return this.noteStyle ?? this.parentState.currentSongNoteStyle;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
function set_noteStyle(value:Null<String>):Null<String>
|
||||
{
|
||||
this.noteStyle = value;
|
||||
this.updateHoldNoteGraphic();
|
||||
return value;
|
||||
}
|
||||
|
||||
public function new(parent:ChartEditorState)
|
||||
{
|
||||
var noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||
|
@ -30,14 +48,52 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
super(0, 100, noteStyle);
|
||||
|
||||
this.parentState = parent;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
function updateHoldNoteGraphic():Void
|
||||
{
|
||||
var bruhStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyle);
|
||||
if (bruhStyle == null) bruhStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||
setupHoldNoteGraphic(bruhStyle);
|
||||
}
|
||||
|
||||
override function setupHoldNoteGraphic(noteStyle:NoteStyle):Void
|
||||
{
|
||||
var graphicPath = noteStyle.getHoldNoteAssetPath();
|
||||
if (graphicPath == null) return;
|
||||
loadGraphic(graphicPath);
|
||||
|
||||
antialiasing = true;
|
||||
|
||||
this.isPixel = noteStyle.isHoldNotePixel();
|
||||
if (isPixel)
|
||||
{
|
||||
endOffset = bottomClip = 1;
|
||||
antialiasing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
endOffset = 0.5;
|
||||
bottomClip = 0.9;
|
||||
}
|
||||
|
||||
zoom = 1.0;
|
||||
zoom *= noteStyle.fetchHoldNoteScale();
|
||||
zoom *= 0.7;
|
||||
zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE;
|
||||
|
||||
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
graphicHeight = sustainLength * 0.45; // sustainHeight
|
||||
|
||||
flipY = false;
|
||||
|
||||
alpha = 1.0;
|
||||
|
||||
updateColorTransform();
|
||||
|
||||
updateClipping();
|
||||
|
||||
setup();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,11 @@ import flixel.graphics.frames.FlxAtlasFrames;
|
|||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.graphics.frames.FlxTileFrames;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.data.animation.AnimationData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a note in a chart.
|
||||
|
@ -36,7 +40,8 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
/**
|
||||
* The name of the note style currently in use.
|
||||
*/
|
||||
public var noteStyle(get, never):String;
|
||||
@:isVar
|
||||
public var noteStyle(get, set):Null<String>;
|
||||
|
||||
public var overrideStepTime(default, set):Null<Float> = null;
|
||||
|
||||
|
@ -66,72 +71,80 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
|
||||
this.parentState = parent;
|
||||
|
||||
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
|
||||
|
||||
if (noteFrameCollection == null)
|
||||
{
|
||||
initFrameCollection();
|
||||
buildEmptyFrameCollection();
|
||||
|
||||
for (entry in entries)
|
||||
{
|
||||
addNoteStyleFrames(fetchNoteStyle(entry));
|
||||
}
|
||||
}
|
||||
|
||||
if (noteFrameCollection == null) throw 'ERROR: Could not initialize note sprite animations.';
|
||||
|
||||
this.frames = noteFrameCollection;
|
||||
|
||||
// Initialize all the animations, not just the one we're going to use immediately,
|
||||
// so that later we can reuse the sprite without having to initialize more animations during scrolling.
|
||||
this.animation.addByPrefix('tapLeftFunkin', 'purple instance');
|
||||
this.animation.addByPrefix('tapDownFunkin', 'blue instance');
|
||||
this.animation.addByPrefix('tapUpFunkin', 'green instance');
|
||||
this.animation.addByPrefix('tapRightFunkin', 'red instance');
|
||||
|
||||
this.animation.addByPrefix('holdLeftFunkin', 'LeftHoldPiece');
|
||||
this.animation.addByPrefix('holdDownFunkin', 'DownHoldPiece');
|
||||
this.animation.addByPrefix('holdUpFunkin', 'UpHoldPiece');
|
||||
this.animation.addByPrefix('holdRightFunkin', 'RightHoldPiece');
|
||||
|
||||
this.animation.addByPrefix('holdEndLeftFunkin', 'LeftHoldEnd');
|
||||
this.animation.addByPrefix('holdEndDownFunkin', 'DownHoldEnd');
|
||||
this.animation.addByPrefix('holdEndUpFunkin', 'UpHoldEnd');
|
||||
this.animation.addByPrefix('holdEndRightFunkin', 'RightHoldEnd');
|
||||
|
||||
this.animation.addByPrefix('tapLeftPixel', 'pixel4');
|
||||
this.animation.addByPrefix('tapDownPixel', 'pixel5');
|
||||
this.animation.addByPrefix('tapUpPixel', 'pixel6');
|
||||
this.animation.addByPrefix('tapRightPixel', 'pixel7');
|
||||
for (entry in entries)
|
||||
{
|
||||
addNoteStyleAnimations(fetchNoteStyle(entry));
|
||||
}
|
||||
}
|
||||
|
||||
static var noteFrameCollection:Null<FlxFramesCollection> = null;
|
||||
|
||||
/**
|
||||
* We load all the note frames once, then reuse them.
|
||||
*/
|
||||
static function initFrameCollection():Void
|
||||
function fetchNoteStyle(noteStyleId:String):NoteStyle
|
||||
{
|
||||
buildEmptyFrameCollection();
|
||||
if (noteFrameCollection == null) return;
|
||||
var result = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||
if (result != null) return result;
|
||||
return NoteStyleRegistry.instance.fetchDefault();
|
||||
}
|
||||
|
||||
// TODO: Automatically iterate over the list of note skins.
|
||||
@:access(funkin.play.notes.notestyle.NoteStyle)
|
||||
@:nullSafety(Off)
|
||||
static function addNoteStyleFrames(noteStyle:NoteStyle):Void
|
||||
{
|
||||
var prefix:String = noteStyle.id.toTitleCase();
|
||||
|
||||
// Normal notes
|
||||
var frameCollectionNormal:FlxAtlasFrames = Paths.getSparrowAtlas('NOTE_assets');
|
||||
|
||||
for (frame in frameCollectionNormal.frames)
|
||||
var frameCollection:FlxAtlasFrames = Paths.getSparrowAtlas(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary());
|
||||
if (frameCollection == null)
|
||||
{
|
||||
noteFrameCollection.pushFrame(frame);
|
||||
trace('Could not retrieve frame collection for ${noteStyle}: ${Paths.image(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary())}');
|
||||
FlxG.log.error('Could not retrieve frame collection for ${noteStyle}: ${Paths.image(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary())}');
|
||||
return;
|
||||
}
|
||||
|
||||
// Pixel notes
|
||||
var graphicPixel = FlxG.bitmap.add(Paths.image('weeb/pixelUI/arrows-pixels', 'week6'), false, null);
|
||||
if (graphicPixel == null) trace('ERROR: Could not load graphic: ' + Paths.image('weeb/pixelUI/arrows-pixels', 'week6'));
|
||||
var frameCollectionPixel = FlxTileFrames.fromGraphic(graphicPixel, new FlxPoint(17, 17));
|
||||
for (i in 0...frameCollectionPixel.frames.length)
|
||||
for (frame in frameCollection.frames)
|
||||
{
|
||||
var frame:Null<FlxFrame> = frameCollectionPixel.frames[i];
|
||||
if (frame == null) continue;
|
||||
|
||||
frame.name = 'pixel' + i;
|
||||
noteFrameCollection.pushFrame(frame);
|
||||
// cloning the frame because else
|
||||
// we will fuck up the frame data used in game
|
||||
var clonedFrame:FlxFrame = frame.copyTo();
|
||||
clonedFrame.name = '$prefix${clonedFrame.name}';
|
||||
noteFrameCollection.pushFrame(clonedFrame);
|
||||
}
|
||||
}
|
||||
|
||||
@:access(funkin.play.notes.notestyle.NoteStyle)
|
||||
@:nullSafety(Off)
|
||||
function addNoteStyleAnimations(noteStyle:NoteStyle):Void
|
||||
{
|
||||
var prefix:String = noteStyle.id.toTitleCase();
|
||||
var suffix:String = noteStyle.id.toTitleCase();
|
||||
|
||||
var leftData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.LEFT);
|
||||
this.animation.addByPrefix('tapLeft$suffix', '$prefix${leftData.prefix}', leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY);
|
||||
|
||||
var downData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.DOWN);
|
||||
this.animation.addByPrefix('tapDown$suffix', '$prefix${downData.prefix}', downData.frameRate, downData.looped, downData.flipX, downData.flipY);
|
||||
|
||||
var upData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.UP);
|
||||
this.animation.addByPrefix('tapUp$suffix', '$prefix${upData.prefix}', upData.frameRate, upData.looped, upData.flipX, upData.flipY);
|
||||
|
||||
var rightData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.RIGHT);
|
||||
this.animation.addByPrefix('tapRight$suffix', '$prefix${rightData.prefix}', rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
static function buildEmptyFrameCollection():Void
|
||||
{
|
||||
|
@ -185,12 +198,24 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
}
|
||||
}
|
||||
|
||||
function get_noteStyle():String
|
||||
function get_noteStyle():Null<String>
|
||||
{
|
||||
// Fall back to Funkin' if it's not a valid note style.
|
||||
return if (NOTE_STYLES.contains(this.parentState.currentSongNoteStyle)) this.parentState.currentSongNoteStyle else 'funkin';
|
||||
if (this.noteStyle == null)
|
||||
{
|
||||
var result = this.parentState.currentSongNoteStyle;
|
||||
return result;
|
||||
}
|
||||
return this.noteStyle;
|
||||
}
|
||||
|
||||
function set_noteStyle(value:Null<String>):Null<String>
|
||||
{
|
||||
this.noteStyle = value;
|
||||
this.playNoteAnimation();
|
||||
return value;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
public function playNoteAnimation():Void
|
||||
{
|
||||
if (this.noteData == null) return;
|
||||
|
@ -200,6 +225,7 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
|
||||
// Play the appropriate animation for the type, direction, and skin.
|
||||
var dirName:String = overrideData != null ? SongNoteData.buildDirectionName(overrideData) : this.noteData.getDirectionName();
|
||||
var noteStyleSuffix:String = this.noteStyle?.toTitleCase() ?? Constants.DEFAULT_NOTE_STYLE.toTitleCase();
|
||||
var animationName:String = '${baseAnimationName}${dirName}${this.noteStyle.toTitleCase()}';
|
||||
|
||||
this.animation.play(animationName);
|
||||
|
@ -209,12 +235,12 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
switch (baseAnimationName)
|
||||
{
|
||||
case 'tap':
|
||||
this.setGraphicSize(0, ChartEditorState.GRID_SIZE);
|
||||
this.setGraphicSize(ChartEditorState.GRID_SIZE, 0);
|
||||
this.updateHitbox();
|
||||
}
|
||||
this.updateHitbox();
|
||||
|
||||
// TODO: Make this an attribute of the note skin.
|
||||
this.antialiasing = (this.parentState.currentSongNoteStyle != 'Pixel');
|
||||
var bruhStyle:NoteStyle = fetchNoteStyle(this.noteStyle);
|
||||
this.antialiasing = !bruhStyle._data?.assets?.note?.isPixel ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -190,8 +190,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
|||
var numberStepper:NumberStepper = new NumberStepper();
|
||||
numberStepper.id = field.name;
|
||||
numberStepper.step = field.step ?? 1.0;
|
||||
numberStepper.min = field.min ?? 0.0;
|
||||
numberStepper.max = field.max ?? 10.0;
|
||||
if (field.min != null) numberStepper.min = field.min;
|
||||
if (field.min != null) numberStepper.max = field.max;
|
||||
if (field.defaultValue != null) numberStepper.value = field.defaultValue;
|
||||
input = numberStepper;
|
||||
case FLOAT:
|
||||
|
|
|
@ -2,8 +2,16 @@ package funkin.ui.debug.charting.toolboxes;
|
|||
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.containers.Grid;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.notes.notekind.NoteKind.NoteKindParam;
|
||||
import funkin.play.notes.notekind.NoteKind.NoteKindParamType;
|
||||
import funkin.data.song.SongData.NoteParamData;
|
||||
|
||||
/**
|
||||
* The toolbox which allows modifying information like Note Kind.
|
||||
|
@ -12,8 +20,22 @@ import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
|||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/note-data.xml"))
|
||||
class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
|
||||
{
|
||||
// 100 is the height used in note-data.xml
|
||||
static final DIALOG_HEIGHT:Int = 100;
|
||||
|
||||
// toolboxNotesGrid.height + 45
|
||||
// this is what i found out by printing this.height and grid.height
|
||||
// and then seeing that this.height is 100 and grid.height is 55
|
||||
static final HEIGHT_OFFSET:Int = 45;
|
||||
|
||||
// minimizing creates a gray bar the bottom, which would obscure the components,
|
||||
// which is why we use an extra offset of 20
|
||||
static final MINIMIZE_FIX:Int = 20;
|
||||
|
||||
var toolboxNotesGrid:Grid;
|
||||
var toolboxNotesNoteKind:DropDown;
|
||||
var toolboxNotesCustomKind:TextField;
|
||||
var toolboxNotesParams:Array<ToolboxNoteKindParam> = [];
|
||||
|
||||
var _initializing:Bool = true;
|
||||
|
||||
|
@ -54,12 +76,35 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
|
|||
toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace;
|
||||
}
|
||||
|
||||
createNoteKindParams(noteKind);
|
||||
|
||||
if (!_initializing && chartEditorState.currentNoteSelection.length > 0)
|
||||
{
|
||||
// Edit the note data of any selected notes.
|
||||
for (note in chartEditorState.currentNoteSelection)
|
||||
{
|
||||
// Edit the note data of any selected notes.
|
||||
note.kind = chartEditorState.noteKindToPlace;
|
||||
note.params = ChartEditorState.cloneNoteParams(chartEditorState.noteParamsToPlace);
|
||||
|
||||
// update note sprites
|
||||
for (noteSprite in chartEditorState.renderedNotes.members)
|
||||
{
|
||||
if (noteSprite.noteData == note)
|
||||
{
|
||||
noteSprite.noteStyle = NoteKindManager.getNoteStyleId(note.kind) ?? chartEditorState.currentSongNoteStyle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// update hold note sprites
|
||||
for (holdNoteSprite in chartEditorState.renderedHoldNotes.members)
|
||||
{
|
||||
if (holdNoteSprite.noteData == note)
|
||||
{
|
||||
holdNoteSprite.noteStyle = NoteKindManager.getNoteStyleId(note.kind) ?? chartEditorState.currentSongNoteStyle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
chartEditorState.saveDataDirty = true;
|
||||
chartEditorState.noteDisplayDirty = true;
|
||||
|
@ -94,6 +139,8 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
|
|||
|
||||
toolboxNotesNoteKind.value = ChartEditorDropdowns.lookupNoteKind(chartEditorState.noteKindToPlace);
|
||||
toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace;
|
||||
|
||||
createNoteKindParams(chartEditorState.noteKindToPlace);
|
||||
}
|
||||
|
||||
function showCustom():Void
|
||||
|
@ -108,8 +155,149 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
|
|||
toolboxNotesCustomKind.hidden = true;
|
||||
}
|
||||
|
||||
function createNoteKindParams(noteKind:Null<String>):Void
|
||||
{
|
||||
clearNoteKindParams();
|
||||
|
||||
var setParamsToPlace:Bool = false;
|
||||
if (!_initializing)
|
||||
{
|
||||
for (note in chartEditorState.currentNoteSelection)
|
||||
{
|
||||
if (note.kind == chartEditorState.noteKindToPlace)
|
||||
{
|
||||
chartEditorState.noteParamsToPlace = ChartEditorState.cloneNoteParams(note.params);
|
||||
setParamsToPlace = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var noteKindParams:Array<NoteKindParam> = NoteKindManager.getParams(noteKind);
|
||||
|
||||
for (i in 0...noteKindParams.length)
|
||||
{
|
||||
var param:NoteKindParam = noteKindParams[i];
|
||||
|
||||
var paramLabel:Label = new Label();
|
||||
paramLabel.value = param.description;
|
||||
paramLabel.verticalAlign = "center";
|
||||
paramLabel.horizontalAlign = "right";
|
||||
|
||||
var paramComponent:Component = null;
|
||||
|
||||
switch (param.type)
|
||||
{
|
||||
case NoteKindParamType.INT | NoteKindParamType.FLOAT:
|
||||
var paramStepper:NumberStepper = new NumberStepper();
|
||||
paramStepper.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? 0.0;
|
||||
paramStepper.percentWidth = 100;
|
||||
paramStepper.step = param.data?.step ?? 1.0;
|
||||
|
||||
// this check should be unnecessary but for some reason
|
||||
// even when these are null it will set it to 0
|
||||
if (param.data?.min != null)
|
||||
{
|
||||
paramStepper.min = param.data.min;
|
||||
}
|
||||
if (param.data?.max != null)
|
||||
{
|
||||
paramStepper.max = param.data.max;
|
||||
}
|
||||
if (param.data?.precision != null)
|
||||
{
|
||||
paramStepper.precision = param.data.precision;
|
||||
}
|
||||
paramComponent = paramStepper;
|
||||
|
||||
case NoteKindParamType.STRING:
|
||||
var paramTextField:TextField = new TextField();
|
||||
paramTextField.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? '';
|
||||
paramTextField.percentWidth = 100;
|
||||
paramComponent = paramTextField;
|
||||
}
|
||||
|
||||
if (paramComponent == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
paramComponent.onChange = function(event:UIEvent) {
|
||||
chartEditorState.noteParamsToPlace[i].value = paramComponent.value;
|
||||
|
||||
for (note in chartEditorState.currentNoteSelection)
|
||||
{
|
||||
if (note.params.length != noteKindParams.length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (note.params[i].name == param.name)
|
||||
{
|
||||
note.params[i].value = paramComponent.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addNoteKindParam(paramLabel, paramComponent);
|
||||
}
|
||||
|
||||
if (!setParamsToPlace)
|
||||
{
|
||||
var noteParamData:Array<NoteParamData> = [];
|
||||
for (i in 0...noteKindParams.length)
|
||||
{
|
||||
noteParamData.push(new NoteParamData(noteKindParams[i].name, toolboxNotesParams[i].component.value));
|
||||
}
|
||||
chartEditorState.noteParamsToPlace = noteParamData;
|
||||
}
|
||||
}
|
||||
|
||||
function addNoteKindParam(label:Label, component:Component):Void
|
||||
{
|
||||
toolboxNotesParams.push({label: label, component: component});
|
||||
toolboxNotesGrid.addComponent(label);
|
||||
toolboxNotesGrid.addComponent(component);
|
||||
|
||||
this.height = Math.max(DIALOG_HEIGHT, DIALOG_HEIGHT - 30 + toolboxNotesParams.length * 30);
|
||||
}
|
||||
|
||||
function clearNoteKindParams():Void
|
||||
{
|
||||
for (param in toolboxNotesParams)
|
||||
{
|
||||
toolboxNotesGrid.removeComponent(param.component);
|
||||
toolboxNotesGrid.removeComponent(param.label);
|
||||
}
|
||||
toolboxNotesParams = [];
|
||||
this.height = DIALOG_HEIGHT;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// current dialog is minimized, dont change the height
|
||||
if (this.minimized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var heightToSet:Int = Std.int(Math.max(DIALOG_HEIGHT, (toolboxNotesGrid?.height ?? 50.0) + HEIGHT_OFFSET)) + MINIMIZE_FIX;
|
||||
if (this.height != heightToSet)
|
||||
{
|
||||
this.height = heightToSet;
|
||||
}
|
||||
}
|
||||
|
||||
public static function build(chartEditorState:ChartEditorState):ChartEditorNoteDataToolbox
|
||||
{
|
||||
return new ChartEditorNoteDataToolbox(chartEditorState);
|
||||
}
|
||||
}
|
||||
|
||||
typedef ToolboxNoteKindParam =
|
||||
{
|
||||
var label:Label;
|
||||
var component:Component;
|
||||
}
|
||||
|
|
|
@ -135,6 +135,14 @@ class ChartEditorDropdowns
|
|||
var noteStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||
if (noteStyle == null) continue;
|
||||
|
||||
// check if the note style has all necessary assets (strums, notes, holdNotes)
|
||||
if (noteStyle._data?.assets?.noteStrumline == null
|
||||
|| noteStyle._data?.assets?.note == null
|
||||
|| noteStyle._data?.assets?.holdNote == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = {id: noteStyleId, text: noteStyle.getName()};
|
||||
if (startingStyleId == noteStyleId) returnValue = value;
|
||||
|
||||
|
@ -146,7 +154,7 @@ class ChartEditorDropdowns
|
|||
return returnValue;
|
||||
}
|
||||
|
||||
static final NOTE_KINDS:Map<String, String> = [
|
||||
public static final NOTE_KINDS:Map<String, String> = [
|
||||
// Base
|
||||
"" => "Default",
|
||||
"~CUSTOM~" => "Custom",
|
||||
|
@ -187,11 +195,11 @@ class ChartEditorDropdowns
|
|||
{
|
||||
dropDown.dataSource.clear();
|
||||
|
||||
var returnValue:DropDownEntry = lookupNoteKind('~CUSTOM');
|
||||
var returnValue:DropDownEntry = lookupNoteKind('');
|
||||
|
||||
for (noteKindId in NOTE_KINDS.keys())
|
||||
{
|
||||
var noteKind:String = NOTE_KINDS.get(noteKindId) ?? 'Default';
|
||||
var noteKind:String = NOTE_KINDS.get(noteKindId) ?? 'Unknown';
|
||||
|
||||
var value:DropDownEntry = {id: noteKindId, text: noteKind};
|
||||
if (startingKindId == noteKindId) returnValue = value;
|
||||
|
@ -208,7 +216,7 @@ class ChartEditorDropdowns
|
|||
{
|
||||
if (noteKindId == null) return lookupNoteKind('');
|
||||
if (!NOTE_KINDS.exists(noteKindId)) return {id: '~CUSTOM~', text: 'Custom'};
|
||||
return {id: noteKindId ?? '', text: NOTE_KINDS.get(noteKindId) ?? 'Default'};
|
||||
return {id: noteKindId ?? '', text: NOTE_KINDS.get(noteKindId) ?? 'Unknown'};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,6 +37,7 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
}
|
||||
|
||||
var newAlbumArt:FlxAtlasSprite;
|
||||
var albumTitle:FunkinSprite;
|
||||
|
||||
var difficultyStars:DifficultyStars;
|
||||
var _exitMovers:Null<FreeplayState.ExitMoverData>;
|
||||
|
@ -59,24 +60,27 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
{
|
||||
super();
|
||||
|
||||
newAlbumArt = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum"));
|
||||
newAlbumArt = new FlxAtlasSprite(640, 350, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum"));
|
||||
newAlbumArt.visible = false;
|
||||
newAlbumArt.onAnimationFinish.add(onAlbumFinish);
|
||||
newAlbumArt.onAnimationComplete.add(onAlbumFinish);
|
||||
|
||||
add(newAlbumArt);
|
||||
|
||||
difficultyStars = new DifficultyStars(140, 39);
|
||||
difficultyStars.visible = false;
|
||||
add(difficultyStars);
|
||||
|
||||
buildAlbumTitle("freeplay/albumRoll/volume1-text");
|
||||
albumTitle.visible = false;
|
||||
}
|
||||
|
||||
function onAlbumFinish(animName:String):Void
|
||||
{
|
||||
// Play the idle animation for the current album.
|
||||
newAlbumArt.playAnimation(animNames.get('$albumId-idle'), false, false, true);
|
||||
|
||||
// End on the last frame and don't continue until playAnimation is called again.
|
||||
// newAlbumArt.anim.pause();
|
||||
if (animName != "idle")
|
||||
{
|
||||
// newAlbumArt.playAnimation('idle', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,6 +108,12 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
return;
|
||||
};
|
||||
|
||||
// Update the album art.
|
||||
var albumGraphic = Paths.image(albumData.getAlbumArtAssetKey());
|
||||
newAlbumArt.replaceFrameGraphic(0, albumGraphic);
|
||||
|
||||
buildAlbumTitle(albumData.getAlbumTitleAssetKey());
|
||||
|
||||
applyExitMovers();
|
||||
|
||||
refresh();
|
||||
|
@ -146,19 +156,57 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
*/
|
||||
public function playIntro():Void
|
||||
{
|
||||
albumTitle.visible = false;
|
||||
newAlbumArt.visible = true;
|
||||
newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
|
||||
newAlbumArt.playAnimation('intro', true);
|
||||
|
||||
difficultyStars.visible = false;
|
||||
new FlxTimer().start(0.75, function(_) {
|
||||
// showTitle();
|
||||
showTitle();
|
||||
showStars();
|
||||
albumTitle.animation.play('switch');
|
||||
});
|
||||
}
|
||||
|
||||
public function skipIntro():Void
|
||||
{
|
||||
newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
|
||||
// Weird workaround
|
||||
newAlbumArt.playAnimation('switch', true);
|
||||
albumTitle.animation.play('switch');
|
||||
}
|
||||
|
||||
public function showTitle():Void
|
||||
{
|
||||
albumTitle.visible = true;
|
||||
}
|
||||
|
||||
public function buildAlbumTitle(assetKey:String):Void
|
||||
{
|
||||
if (albumTitle != null)
|
||||
{
|
||||
remove(albumTitle);
|
||||
albumTitle = null;
|
||||
}
|
||||
|
||||
albumTitle = FunkinSprite.createSparrow(925, 500, assetKey);
|
||||
albumTitle.visible = albumTitle.frames != null && newAlbumArt.visible;
|
||||
albumTitle.animation.addByPrefix('idle', 'idle0', 24, true);
|
||||
albumTitle.animation.addByPrefix('switch', 'switch0', 24, false);
|
||||
add(albumTitle);
|
||||
|
||||
albumTitle.animation.finishCallback = (function(name) {
|
||||
if (name == 'switch') albumTitle.animation.play('idle');
|
||||
});
|
||||
albumTitle.animation.play('idle');
|
||||
|
||||
albumTitle.zIndex = 1000;
|
||||
|
||||
if (_exitMovers != null) _exitMovers.set([albumTitle],
|
||||
{
|
||||
x: FlxG.width,
|
||||
speed: 0.4,
|
||||
wait: 0
|
||||
});
|
||||
}
|
||||
|
||||
public function setDifficultyStars(?difficulty:Int):Void
|
||||
|
|
|
@ -15,7 +15,7 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
{
|
||||
// Represents the sprite's current status.
|
||||
// Without state machines I would have driven myself crazy years ago.
|
||||
public var currentState:DJBoyfriendState = Intro;
|
||||
public var currentState:FreeplayDJState = Intro;
|
||||
|
||||
// A callback activated when the intro animation finishes.
|
||||
public var onIntroDone:FlxSignal = new FlxSignal();
|
||||
|
@ -43,7 +43,7 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
|
||||
super(x, y, playableCharData.getAtlasPath());
|
||||
|
||||
anim.callback = function(name, number) {
|
||||
onAnimationFrame.add(function(name, number) {
|
||||
if (name == playableCharData.getAnimationPrefix('cartoon'))
|
||||
{
|
||||
if (number == playableCharData.getCartoonSoundClickFrame())
|
||||
|
@ -55,12 +55,12 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
runTvLogic();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
FlxG.debugger.track(this);
|
||||
FlxG.console.registerObject("dj", this);
|
||||
|
||||
anim.onComplete = onFinishAnim;
|
||||
onAnimationComplete.add(onFinishAnim);
|
||||
|
||||
FlxG.console.registerFunction("freeplayCartoon", function() {
|
||||
currentState = Cartoon;
|
||||
|
@ -96,10 +96,10 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
var animPrefix = playableCharData.getAnimationPrefix('idle');
|
||||
if (getCurrentAnimation() != animPrefix)
|
||||
{
|
||||
playFlashAnimation(animPrefix, true);
|
||||
playFlashAnimation(animPrefix, true, false, true);
|
||||
}
|
||||
|
||||
if (getCurrentAnimation() == animPrefix && this.isLoopFinished())
|
||||
if (getCurrentAnimation() == animPrefix && this.isLoopComplete())
|
||||
{
|
||||
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
|
||||
{
|
||||
|
@ -111,18 +111,69 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
}
|
||||
}
|
||||
timeIdling += elapsed;
|
||||
case NewUnlock:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('newUnlock');
|
||||
if (!hasAnimation(animPrefix))
|
||||
{
|
||||
currentState = Idle;
|
||||
}
|
||||
if (getCurrentAnimation() != animPrefix)
|
||||
{
|
||||
playFlashAnimation(animPrefix, true, false, true);
|
||||
}
|
||||
case Confirm:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('confirm');
|
||||
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false);
|
||||
timeIdling = 0;
|
||||
case FistPumpIntro:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('fistPump');
|
||||
if (getCurrentAnimation() != animPrefix) playFlashAnimation('Boyfriend DJ fist pump', false);
|
||||
if (getCurrentAnimation() == animPrefix && anim.curFrame >= 4)
|
||||
var animPrefixA = playableCharData.getAnimationPrefix('fistPump');
|
||||
var animPrefixB = playableCharData.getAnimationPrefix('loss');
|
||||
|
||||
if (getCurrentAnimation() == animPrefixA)
|
||||
{
|
||||
anim.play("Boyfriend DJ fist pump", true, false, 0);
|
||||
var endFrame = playableCharData.getFistPumpIntroEndFrame();
|
||||
if (endFrame > -1 && anim.curFrame >= endFrame)
|
||||
{
|
||||
playFlashAnimation(animPrefixA, true, false, false, playableCharData.getFistPumpIntroStartFrame());
|
||||
}
|
||||
}
|
||||
else if (getCurrentAnimation() == animPrefixB)
|
||||
{
|
||||
var endFrame = playableCharData.getFistPumpIntroBadEndFrame();
|
||||
if (endFrame > -1 && anim.curFrame >= endFrame)
|
||||
{
|
||||
playFlashAnimation(animPrefixB, true, false, false, playableCharData.getFistPumpIntroBadStartFrame());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.log.warn("Unrecognized animation in FistPumpIntro: " + getCurrentAnimation());
|
||||
}
|
||||
|
||||
case FistPump:
|
||||
var animPrefixA = playableCharData.getAnimationPrefix('fistPump');
|
||||
var animPrefixB = playableCharData.getAnimationPrefix('loss');
|
||||
|
||||
if (getCurrentAnimation() == animPrefixA)
|
||||
{
|
||||
var endFrame = playableCharData.getFistPumpLoopEndFrame();
|
||||
if (endFrame > -1 && anim.curFrame >= endFrame)
|
||||
{
|
||||
playFlashAnimation(animPrefixA, true, false, false, playableCharData.getFistPumpLoopStartFrame());
|
||||
}
|
||||
}
|
||||
else if (getCurrentAnimation() == animPrefixB)
|
||||
{
|
||||
var endFrame = playableCharData.getFistPumpLoopBadEndFrame();
|
||||
if (endFrame > -1 && anim.curFrame >= endFrame)
|
||||
{
|
||||
playFlashAnimation(animPrefixB, true, false, false, playableCharData.getFistPumpLoopBadStartFrame());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.log.warn("Unrecognized animation in FistPump: " + getCurrentAnimation());
|
||||
}
|
||||
|
||||
case IdleEasterEgg:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('idleEasterEgg');
|
||||
|
@ -135,9 +186,12 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
timeIdling = 0;
|
||||
case Cartoon:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('cartoon');
|
||||
if (animPrefix == null) {
|
||||
if (animPrefix == null)
|
||||
{
|
||||
currentState = IdleEasterEgg;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true);
|
||||
timeIdling = 0;
|
||||
}
|
||||
|
@ -145,6 +199,7 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
// I shit myself.
|
||||
}
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
if (FlxG.keys.justPressed.LEFT)
|
||||
|
@ -167,20 +222,28 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
this.offsetY += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0);
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.SPACE)
|
||||
if (FlxG.keys.justPressed.C)
|
||||
{
|
||||
currentState = (currentState == Idle ? Cartoon : Idle);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function onFinishAnim():Void
|
||||
function onFinishAnim(name:String):Void
|
||||
{
|
||||
var name = anim.curSymbol.name;
|
||||
// var name = anim.curSymbol.name;
|
||||
|
||||
if (name == playableCharData.getAnimationPrefix('intro'))
|
||||
{
|
||||
currentState = Idle;
|
||||
if (PlayerRegistry.instance.hasNewCharacter())
|
||||
{
|
||||
currentState = NewUnlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = Idle;
|
||||
}
|
||||
onIntroDone.dispatch();
|
||||
}
|
||||
else if (name == playableCharData.getAnimationPrefix('idle'))
|
||||
|
@ -220,9 +283,17 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
// runTvLogic();
|
||||
}
|
||||
trace('Replay idle: ${frame}');
|
||||
anim.play(playableCharData.getAnimationPrefix('cartoon'), true, false, frame);
|
||||
playFlashAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, frame);
|
||||
// trace('Finished confirm');
|
||||
}
|
||||
else if (name == playableCharData.getAnimationPrefix('newUnlock'))
|
||||
{
|
||||
// Animation should loop.
|
||||
}
|
||||
else if (name == playableCharData.getAnimationPrefix('charSelect'))
|
||||
{
|
||||
onCharSelectComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Finished ${name}');
|
||||
|
@ -235,6 +306,15 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
seenIdleEasterEgg = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic function, it's actually a variable you can reassign!
|
||||
* `dj.onCharSelectComplete = function() {};`
|
||||
*/
|
||||
public dynamic function onCharSelectComplete():Void
|
||||
{
|
||||
trace('onCharSelectComplete()');
|
||||
}
|
||||
|
||||
var offsetX:Float = 0.0;
|
||||
var offsetY:Float = 0.0;
|
||||
|
||||
|
@ -266,7 +346,7 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
function loadCartoon()
|
||||
{
|
||||
cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() {
|
||||
anim.play("Boyfriend DJ watchin tv OG", true, false, 60);
|
||||
playFlashAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, 60);
|
||||
});
|
||||
|
||||
// Fade out music to 40% volume over 1 second.
|
||||
|
@ -296,21 +376,48 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
currentState = Confirm;
|
||||
}
|
||||
|
||||
public function fistPump():Void
|
||||
public function toCharSelect():Void
|
||||
{
|
||||
if (hasAnimation('charSelect'))
|
||||
{
|
||||
currentState = CharSelect;
|
||||
var animPrefix = playableCharData.getAnimationPrefix('charSelect');
|
||||
playFlashAnimation(animPrefix, true, false, false, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = Confirm;
|
||||
// Call this immediately; otherwise, we get locked out of Character Select.
|
||||
onCharSelectComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public function fistPumpIntro():Void
|
||||
{
|
||||
currentState = FistPumpIntro;
|
||||
var animPrefix = playableCharData.getAnimationPrefix('fistPump');
|
||||
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpIntroStartFrame());
|
||||
}
|
||||
|
||||
public function pumpFist():Void
|
||||
public function fistPump():Void
|
||||
{
|
||||
currentState = FistPump;
|
||||
anim.play("Boyfriend DJ fist pump", true, false, 4);
|
||||
var animPrefix = playableCharData.getAnimationPrefix('fistPump');
|
||||
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpLoopStartFrame());
|
||||
}
|
||||
|
||||
public function pumpFistBad():Void
|
||||
public function fistPumpLossIntro():Void
|
||||
{
|
||||
currentState = FistPumpIntro;
|
||||
var animPrefix = playableCharData.getAnimationPrefix('loss');
|
||||
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpIntroBadStartFrame());
|
||||
}
|
||||
|
||||
public function fistPumpLoss():Void
|
||||
{
|
||||
currentState = FistPump;
|
||||
anim.play("Boyfriend DJ loss reaction 1", true, false, 4);
|
||||
var animPrefix = playableCharData.getAnimationPrefix('loss');
|
||||
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpLoopBadStartFrame());
|
||||
}
|
||||
|
||||
override public function getCurrentAnimation():String
|
||||
|
@ -319,9 +426,9 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
return this.anim.curSymbol.name;
|
||||
}
|
||||
|
||||
public function playFlashAnimation(id:String, ?Force:Bool = false, ?Reverse:Bool = false, ?Frame:Int = 0):Void
|
||||
public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
|
||||
{
|
||||
anim.play(id, Force, Reverse, Frame);
|
||||
playAnimation(id, Force, Reverse, Loop, Frame);
|
||||
applyAnimOffset();
|
||||
}
|
||||
|
||||
|
@ -361,13 +468,53 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
}
|
||||
}
|
||||
|
||||
enum DJBoyfriendState
|
||||
enum FreeplayDJState
|
||||
{
|
||||
/**
|
||||
* Character enters the frame and transitions to Idle.
|
||||
*/
|
||||
Intro;
|
||||
|
||||
/**
|
||||
* Character loops in idle.
|
||||
*/
|
||||
Idle;
|
||||
Confirm;
|
||||
FistPumpIntro;
|
||||
FistPump;
|
||||
|
||||
/**
|
||||
* Plays an easter egg animation after a period in Idle, then reverts to Idle.
|
||||
*/
|
||||
IdleEasterEgg;
|
||||
|
||||
/**
|
||||
* Plays an elaborate easter egg animation. Does not revert until another animation is triggered.
|
||||
*/
|
||||
Cartoon;
|
||||
|
||||
/**
|
||||
* Player has selected a song.
|
||||
*/
|
||||
Confirm;
|
||||
|
||||
/**
|
||||
* Character preps to play the fist pump animation; plays after the Results screen.
|
||||
* The actual frame label that gets played may vary based on the player's success.
|
||||
*/
|
||||
FistPumpIntro;
|
||||
|
||||
/**
|
||||
* Character plays the fist pump animation.
|
||||
* The actual frame label that gets played may vary based on the player's success.
|
||||
*/
|
||||
FistPump;
|
||||
|
||||
/**
|
||||
* Plays an animation to indicate that the player has a new unlock in Character Select.
|
||||
* Overrides all idle animations as well as the fist pump. Only Confirm and CharSelect will override this.
|
||||
*/
|
||||
NewUnlock;
|
||||
|
||||
/**
|
||||
* Plays an animation to transition to the Character Select screen.
|
||||
*/
|
||||
CharSelect;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.ui.freeplay;
|
||||
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.ui.FlxInputText;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup;
|
||||
|
@ -307,14 +306,14 @@ class FreeplayState extends MusicBeatSubState
|
|||
stickerSubState.degenStickers();
|
||||
}
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence('In the Menus', null);
|
||||
#end
|
||||
|
||||
var isDebug:Bool = false;
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
isDebug = true;
|
||||
#end
|
||||
|
||||
|
@ -354,7 +353,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
// Only display songs which actually have available difficulties for the current character.
|
||||
var displayedVariations = song.getVariationsByCharacter(currentCharacter);
|
||||
trace('Displayed Variations (${songId}): $displayedVariations');
|
||||
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
|
||||
var availableDifficultiesForSong:Array<String> = song.listSuffixedDifficulties(displayedVariations, false, false);
|
||||
trace('Available Difficulties: $availableDifficultiesForSong');
|
||||
if (availableDifficultiesForSong.length == 0) continue;
|
||||
|
||||
|
@ -645,8 +644,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
speed: 0.3
|
||||
});
|
||||
|
||||
var diffSelLeft:DifficultySelector = new DifficultySelector(20, grpDifficulties.y - 10, false, controls);
|
||||
var diffSelRight:DifficultySelector = new DifficultySelector(325, grpDifficulties.y - 10, true, controls);
|
||||
var diffSelLeft:DifficultySelector = new DifficultySelector(this, 20, grpDifficulties.y - 10, false, controls);
|
||||
var diffSelRight:DifficultySelector = new DifficultySelector(this, 325, grpDifficulties.y - 10, true, controls);
|
||||
diffSelLeft.visible = false;
|
||||
diffSelRight.visible = false;
|
||||
add(diffSelLeft);
|
||||
|
@ -884,7 +883,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
return str.songName.toLowerCase().startsWith(songFilter.filterData ?? '');
|
||||
});
|
||||
case ALL:
|
||||
// no filter!
|
||||
// no filter!
|
||||
case FAVORITE:
|
||||
songsToFilter = songsToFilter.filter(str -> {
|
||||
if (str == null) return true; // Random
|
||||
|
@ -914,7 +913,15 @@ class FreeplayState extends MusicBeatSubState
|
|||
changeSelection();
|
||||
changeDiff();
|
||||
|
||||
if (dj != null) dj.fistPump();
|
||||
if (fromResultsParams?.newRank == SHIT)
|
||||
{
|
||||
if (dj != null) dj.fistPumpLossIntro();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dj != null) dj.fistPumpIntro();
|
||||
}
|
||||
|
||||
// rankCamera.fade(FlxColor.BLACK, 0.5, true);
|
||||
rankCamera.fade(0xFF000000, 0.5, true, null, true);
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
|
||||
|
@ -1096,11 +1103,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (fromResultsParams?.newRank == SHIT)
|
||||
{
|
||||
if (dj != null) dj.pumpFistBad();
|
||||
if (dj != null) dj.fistPumpLoss();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dj != null) dj.pumpFist();
|
||||
if (dj != null) dj.fistPump();
|
||||
}
|
||||
|
||||
rankCamera.zoom = 0.8;
|
||||
|
@ -1132,7 +1139,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
// NOW we can interact with the menu
|
||||
busy = false;
|
||||
grpCapsules.members[curSelected].sparkle.alpha = 0.7;
|
||||
capsule.sparkle.alpha = 0.7;
|
||||
playCurSongPreview(capsule);
|
||||
}, null);
|
||||
|
||||
|
@ -1203,7 +1210,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
/**
|
||||
* If true, disable interaction with the interface.
|
||||
*/
|
||||
var busy:Bool = false;
|
||||
public var busy:Bool = false;
|
||||
|
||||
var originalPos:FlxPoint = new FlxPoint();
|
||||
|
||||
|
@ -1211,7 +1218,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (FlxG.keys.justPressed.T)
|
||||
{
|
||||
rankAnimStart(fromResultsParams ??
|
||||
|
@ -1246,7 +1253,32 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (controls.FREEPLAY_CHAR_SELECT && !busy)
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
// Check if we have ACCESS to character select!
|
||||
trace('Is Pico unlocked? ${PlayerRegistry.instance.fetchEntry('pico')?.isUnlocked()}');
|
||||
trace('Number of characters: ${PlayerRegistry.instance.countUnlockedCharacters()}');
|
||||
|
||||
if (PlayerRegistry.instance.countUnlockedCharacters() > 1)
|
||||
{
|
||||
if (dj != null)
|
||||
{
|
||||
busy = true;
|
||||
// Transition to character select after animation
|
||||
dj.onCharSelectComplete = function() {
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
}
|
||||
dj.toCharSelect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transition to character select immediately
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Not enough characters unlocked to open character select!');
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.FREEPLAY_FAVORITE && !busy)
|
||||
|
@ -1339,6 +1371,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
handleInputs(elapsed);
|
||||
|
||||
if (dj != null) FlxG.watch.addQuick('dj-anim', dj.getCurrentAnimation());
|
||||
}
|
||||
|
||||
function handleInputs(elapsed:Float):Void
|
||||
|
@ -1496,7 +1530,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
generateSongList(currentFilter, true);
|
||||
}
|
||||
|
||||
if (controls.BACK)
|
||||
if (controls.BACK && !busy)
|
||||
{
|
||||
busy = true;
|
||||
FlxTween.globalManager.clear();
|
||||
|
@ -1539,7 +1573,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var moveDataX = funnyMoveShit.x ?? spr.x;
|
||||
var moveDataY = funnyMoveShit.y ?? spr.y;
|
||||
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
||||
var moveDataWait = funnyMoveShit.wait ?? 0;
|
||||
var moveDataWait = funnyMoveShit.wait ?? 0.0;
|
||||
|
||||
FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn});
|
||||
|
||||
|
@ -1686,6 +1720,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
songCapsule.init(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the song preview in case we changed variations (normal->erect etc)
|
||||
playCurSongPreview();
|
||||
}
|
||||
|
||||
// Set the album graphic and play the animation if relevant.
|
||||
|
@ -1782,7 +1819,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var targetInstId:String = baseInstrumentalId;
|
||||
|
||||
// TODO: Make this a UI element.
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
targetInstId = altInstrumentalIds[0];
|
||||
|
@ -1804,7 +1841,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
confirmGlow.visible = true;
|
||||
confirmGlow2.visible = true;
|
||||
|
||||
backingTextYeah.anim.play("BF back card confirm raw", false, false, 0);
|
||||
backingTextYeah.playAnimation("BF back card confirm raw", false, false, false, 0);
|
||||
confirmGlow2.alpha = 0;
|
||||
confirmGlow.alpha = 0;
|
||||
|
||||
|
@ -1843,7 +1880,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
practiceMode: false,
|
||||
minimalMode: false,
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
botPlayMode: FlxG.keys.pressed.SHIFT,
|
||||
#else
|
||||
botPlayMode: false,
|
||||
|
@ -1924,8 +1961,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
public function playCurSongPreview(daSongCapsule:SongMenuItem):Void
|
||||
public function playCurSongPreview(?daSongCapsule:SongMenuItem):Void
|
||||
{
|
||||
if (daSongCapsule == null) daSongCapsule = grpCapsules.members[curSelected];
|
||||
|
||||
if (curSelected == 0)
|
||||
{
|
||||
FunkinSound.playMusic('freeplayRandom',
|
||||
|
@ -1950,7 +1989,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var instSuffix:String = baseInstrumentalId;
|
||||
|
||||
// TODO: Make this a UI element.
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
instSuffix = altInstrumentalIds[0];
|
||||
|
@ -2008,10 +2047,13 @@ class DifficultySelector extends FlxSprite
|
|||
var controls:Controls;
|
||||
var whiteShader:PureColor;
|
||||
|
||||
public function new(x:Float, y:Float, flipped:Bool, controls:Controls)
|
||||
var parent:FreeplayState;
|
||||
|
||||
public function new(parent:FreeplayState, x:Float, y:Float, flipped:Bool, controls:Controls)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.parent = parent;
|
||||
this.controls = controls;
|
||||
|
||||
frames = Paths.getSparrowAtlas('freeplay/freeplaySelector');
|
||||
|
@ -2027,8 +2069,8 @@ class DifficultySelector extends FlxSprite
|
|||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
if (flipX && controls.UI_RIGHT_P) moveShitDown();
|
||||
if (!flipX && controls.UI_LEFT_P) moveShitDown();
|
||||
if (flipX && controls.UI_RIGHT_P && !parent.busy) moveShitDown();
|
||||
if (!flipX && controls.UI_LEFT_P && !parent.busy) moveShitDown();
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
@ -2158,7 +2200,13 @@ class FreeplaySongData
|
|||
function updateValues(variations:Array<String>):Void
|
||||
{
|
||||
this.songDifficulties = song.listDifficulties(null, variations, false, false);
|
||||
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
if (!this.songDifficulties.contains(currentDifficulty))
|
||||
{
|
||||
currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
// This method gets called again by the setter-method
|
||||
// or the difficulty didn't change, so there's no need to continue.
|
||||
return;
|
||||
}
|
||||
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations);
|
||||
if (songDifficulty == null) return;
|
||||
|
@ -2219,15 +2267,26 @@ class DifficultySprite extends FlxSprite
|
|||
|
||||
difficultyId = diffId;
|
||||
|
||||
if (Assets.exists(Paths.file('images/freeplay/freeplay${diffId}.xml')))
|
||||
var assetDiffId:String = diffId;
|
||||
while (!Assets.exists(Paths.image('freeplay/freeplay${assetDiffId}')))
|
||||
{
|
||||
this.frames = Paths.getSparrowAtlas('freeplay/freeplay${diffId}');
|
||||
// Remove the last suffix of the difficulty id until we find an asset or there are no more suffixes.
|
||||
var assetDiffIdParts:Array<String> = assetDiffId.split('-');
|
||||
assetDiffIdParts.pop();
|
||||
if (assetDiffIdParts.length == 0) break;
|
||||
assetDiffId = assetDiffIdParts.join('-');
|
||||
}
|
||||
|
||||
// Check for an XML to use an animation instead of an image.
|
||||
if (Assets.exists(Paths.file('images/freeplay/freeplay${assetDiffId}.xml')))
|
||||
{
|
||||
this.frames = Paths.getSparrowAtlas('freeplay/freeplay${assetDiffId}');
|
||||
this.animation.addByPrefix('idle', 'idle0', 24, true);
|
||||
if (Preferences.flashingLights) this.animation.play('idle');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.loadGraphic(Paths.image('freeplay/freeplay' + diffId));
|
||||
this.loadGraphic(Paths.image('freeplay/freeplay' + assetDiffId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,11 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
return _data.freeplayDJ.getFreeplayDJText(index);
|
||||
}
|
||||
|
||||
public function getCharSelectData():PlayerCharSelectData
|
||||
{
|
||||
return _data.charSelect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rank Which rank to get info for
|
||||
* @return An array of animations. For example, BF Great has two animations, one for BF and one for GF
|
||||
|
|
|
@ -27,7 +27,7 @@ import funkin.ui.title.TitleState;
|
|||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.WindowUtil;
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
#if newgrounds
|
||||
|
@ -54,7 +54,7 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
override function create():Void
|
||||
{
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence("In the Menus", null);
|
||||
#end
|
||||
|
@ -98,14 +98,7 @@ class MainMenuState extends MusicBeatState
|
|||
add(menuItems);
|
||||
menuItems.onChange.add(onMenuItemChange);
|
||||
menuItems.onAcceptPress.add(function(_) {
|
||||
if (_.name == 'freeplay')
|
||||
{
|
||||
magenta.visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxFlicker.flicker(magenta, 1.1, 0.15, false, true);
|
||||
}
|
||||
FlxFlicker.flicker(magenta, 1.1, 0.15, false, true);
|
||||
});
|
||||
|
||||
menuItems.enabled = true; // can move on intro
|
||||
|
@ -117,13 +110,7 @@ class MainMenuState extends MusicBeatState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
openSubState(new FreeplayState(
|
||||
{
|
||||
#if debug
|
||||
// If SHIFT is held, toggle the selected character, else use the remembered character
|
||||
character: (FlxG.keys.pressed.SHIFT) ? (FreeplayState.rememberedCharacterId == Constants.DEFAULT_CHARACTER ? 'pico' : 'bf') : null,
|
||||
#end
|
||||
}));
|
||||
openSubState(new FreeplayState());
|
||||
});
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
|
@ -347,7 +334,7 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Open the debug menu, defaults to ` / ~
|
||||
if (controls.DEBUG_MENU)
|
||||
{
|
||||
|
@ -358,6 +345,7 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
// Give the user a score of 1 point on Weekend 1 story mode.
|
||||
// This makes the level count as cleared and displays the songs in Freeplay.
|
||||
funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
|
||||
|
@ -378,6 +366,29 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
// Give the user a score of 0 points on Weekend 1 story mode.
|
||||
// This makes the level count as uncleared and no longer displays the songs in Freeplay.
|
||||
funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
|
||||
{
|
||||
score: 1,
|
||||
tallies:
|
||||
{
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R)
|
||||
{
|
||||
// Give the user a hypothetical overridden score,
|
||||
|
|
10
source/funkin/ui/options/MenuItemEnums.hx
Normal file
10
source/funkin/ui/options/MenuItemEnums.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.ui.options;
|
||||
|
||||
// Add enums for use with `EnumPreferenceItem` here!
|
||||
/* Example:
|
||||
class MyOptionEnum
|
||||
{
|
||||
public static inline var YuhUh = "true"; // "true" is the value's ID
|
||||
public static inline var NuhUh = "false";
|
||||
}
|
||||
*/
|
|
@ -8,6 +8,11 @@ import funkin.ui.AtlasText.AtlasFont;
|
|||
import funkin.ui.options.OptionsState.Page;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.ui.TextMenuList.TextMenuItem;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.ui.options.MenuItemEnums;
|
||||
import funkin.ui.options.items.CheckboxPreferenceItem;
|
||||
import funkin.ui.options.items.NumberPreferenceItem;
|
||||
import funkin.ui.options.items.EnumPreferenceItem;
|
||||
|
||||
class PreferencesMenu extends Page
|
||||
{
|
||||
|
@ -69,11 +74,51 @@ class PreferencesMenu extends Page
|
|||
}, Preferences.autoPause);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// Indent the selected item.
|
||||
items.forEach(function(daItem:TextMenuItem) {
|
||||
var thyOffset:Int = 0;
|
||||
|
||||
// Initializing thy text width (if thou text present)
|
||||
var thyTextWidth:Int = 0;
|
||||
if (Std.isOfType(daItem, EnumPreferenceItem)) thyTextWidth = cast(daItem, EnumPreferenceItem).lefthandText.getWidth();
|
||||
else if (Std.isOfType(daItem, NumberPreferenceItem)) thyTextWidth = cast(daItem, NumberPreferenceItem).lefthandText.getWidth();
|
||||
|
||||
if (thyTextWidth != 0)
|
||||
{
|
||||
// Magic number because of the weird offset thats being added by default
|
||||
thyOffset += thyTextWidth - 75;
|
||||
}
|
||||
|
||||
if (items.selectedItem == daItem)
|
||||
{
|
||||
thyOffset += 150;
|
||||
}
|
||||
else
|
||||
{
|
||||
thyOffset += 120;
|
||||
}
|
||||
|
||||
daItem.x = thyOffset;
|
||||
});
|
||||
}
|
||||
|
||||
// - Preference item creation methods -
|
||||
// Should be moved into a separate PreferenceItems class but you can't access PreferencesMenu.items and PreferencesMenu.preferenceItems from outside.
|
||||
|
||||
/**
|
||||
* Creates a pref item that works with booleans
|
||||
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||
*/
|
||||
function createPrefItemCheckbox(prefName:String, prefDesc:String, onChange:Bool->Void, defaultValue:Bool):Void
|
||||
{
|
||||
var checkbox:CheckboxPreferenceItem = new CheckboxPreferenceItem(0, 120 * (items.length - 1 + 1), defaultValue);
|
||||
|
||||
items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() {
|
||||
items.createItem(0, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() {
|
||||
var value = !checkbox.currentValue;
|
||||
onChange(value);
|
||||
checkbox.currentValue = value;
|
||||
|
@ -82,62 +127,54 @@ class PreferencesMenu extends Page
|
|||
preferenceItems.add(checkbox);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
/**
|
||||
* Creates a pref item that works with general numbers
|
||||
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||
* @param valueFormatter Will get called every time the game needs to display the float value; use this to change how the displayed value looks
|
||||
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||
* @param min Minimum value (example: 0)
|
||||
* @param max Maximum value (example: 10)
|
||||
* @param step The value to increment/decrement by (default = 0.1)
|
||||
* @param precision Rounds decimals up to a `precision` amount of digits (ex: 4 -> 0.1234, 2 -> 0.12)
|
||||
*/
|
||||
function createPrefItemNumber(prefName:String, prefDesc:String, onChange:Float->Void, ?valueFormatter:Float->String, defaultValue:Int, min:Int, max:Int,
|
||||
step:Float = 0.1, precision:Int):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
var item = new NumberPreferenceItem(0, (120 * items.length) + 30, prefName, defaultValue, min, max, step, precision, onChange, valueFormatter);
|
||||
items.addItem(prefName, item);
|
||||
preferenceItems.add(item.lefthandText);
|
||||
}
|
||||
|
||||
// Indent the selected item.
|
||||
// TODO: Only do this on menu change?
|
||||
items.forEach(function(daItem:TextMenuItem) {
|
||||
if (items.selectedItem == daItem) daItem.x = 150;
|
||||
else
|
||||
daItem.x = 120;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxPreferenceItem extends FlxSprite
|
||||
{
|
||||
public var currentValue(default, set):Bool;
|
||||
|
||||
public function new(x:Float, y:Float, defaultValue:Bool = false)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
frames = Paths.getSparrowAtlas('checkboxThingie');
|
||||
animation.addByPrefix('static', 'Check Box unselected', 24, false);
|
||||
animation.addByPrefix('checked', 'Check Box selecting animation', 24, false);
|
||||
|
||||
setGraphicSize(Std.int(width * 0.7));
|
||||
updateHitbox();
|
||||
|
||||
this.currentValue = defaultValue;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
switch (animation.curAnim.name)
|
||||
{
|
||||
case 'static':
|
||||
offset.set();
|
||||
case 'checked':
|
||||
offset.set(17, 70);
|
||||
}
|
||||
}
|
||||
|
||||
function set_currentValue(value:Bool):Bool
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
animation.play('checked', true);
|
||||
}
|
||||
else
|
||||
{
|
||||
animation.play('static');
|
||||
}
|
||||
|
||||
return currentValue = value;
|
||||
/**
|
||||
* Creates a pref item that works with number percentages
|
||||
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||
* @param min Minimum value (default = 0)
|
||||
* @param max Maximum value (default = 100)
|
||||
*/
|
||||
function createPrefItemPercentage(prefName:String, prefDesc:String, onChange:Int->Void, defaultValue:Int, min:Int = 0, max:Int = 100):Void
|
||||
{
|
||||
var newCallback = function(value:Float) {
|
||||
onChange(Std.int(value));
|
||||
};
|
||||
var formatter = function(value:Float) {
|
||||
return '${value}%';
|
||||
};
|
||||
var item = new NumberPreferenceItem(0, (120 * items.length) + 30, prefName, defaultValue, min, max, 10, 0, newCallback, formatter);
|
||||
items.addItem(prefName, item);
|
||||
preferenceItems.add(item.lefthandText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pref item that works with enums
|
||||
* @param values Maps enum values to display strings _(ex: `NoteHitSoundType.PingPong => "Ping pong"`)_
|
||||
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||
*/
|
||||
function createPrefItemEnum(prefName:String, prefDesc:String, values:Map<String, String>, onChange:String->Void, defaultValue:String):Void
|
||||
{
|
||||
var item = new EnumPreferenceItem(0, (120 * items.length) + 30, prefName, values, defaultValue, onChange);
|
||||
items.addItem(prefName, item);
|
||||
preferenceItems.add(item.lefthandText);
|
||||
}
|
||||
}
|
||||
|
|
49
source/funkin/ui/options/items/CheckboxPreferenceItem.hx
Normal file
49
source/funkin/ui/options/items/CheckboxPreferenceItem.hx
Normal file
|
@ -0,0 +1,49 @@
|
|||
package funkin.ui.options.items;
|
||||
|
||||
import flixel.FlxSprite.FlxSprite;
|
||||
|
||||
class CheckboxPreferenceItem extends FlxSprite
|
||||
{
|
||||
public var currentValue(default, set):Bool;
|
||||
|
||||
public function new(x:Float, y:Float, defaultValue:Bool = false)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
frames = Paths.getSparrowAtlas('checkboxThingie');
|
||||
animation.addByPrefix('static', 'Check Box unselected', 24, false);
|
||||
animation.addByPrefix('checked', 'Check Box selecting animation', 24, false);
|
||||
|
||||
setGraphicSize(Std.int(width * 0.7));
|
||||
updateHitbox();
|
||||
|
||||
this.currentValue = defaultValue;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
switch (animation.curAnim.name)
|
||||
{
|
||||
case 'static':
|
||||
offset.set();
|
||||
case 'checked':
|
||||
offset.set(17, 70);
|
||||
}
|
||||
}
|
||||
|
||||
function set_currentValue(value:Bool):Bool
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
animation.play('checked', true);
|
||||
}
|
||||
else
|
||||
{
|
||||
animation.play('static');
|
||||
}
|
||||
|
||||
return currentValue = value;
|
||||
}
|
||||
}
|
84
source/funkin/ui/options/items/EnumPreferenceItem.hx
Normal file
84
source/funkin/ui/options/items/EnumPreferenceItem.hx
Normal file
|
@ -0,0 +1,84 @@
|
|||
package funkin.ui.options.items;
|
||||
|
||||
import funkin.ui.TextMenuList;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.input.Controls;
|
||||
import funkin.ui.options.MenuItemEnums;
|
||||
import haxe.EnumTools;
|
||||
|
||||
/**
|
||||
* Preference item that allows the player to pick a value from an enum (list of values)
|
||||
*/
|
||||
class EnumPreferenceItem extends TextMenuItem
|
||||
{
|
||||
function controls():Controls
|
||||
{
|
||||
return PlayerSettings.player1.controls;
|
||||
}
|
||||
|
||||
public var lefthandText:AtlasText;
|
||||
|
||||
public var currentValue:String;
|
||||
public var onChangeCallback:Null<String->Void>;
|
||||
public var map:Map<String, String>;
|
||||
public var keys:Array<String> = [];
|
||||
|
||||
var index = 0;
|
||||
|
||||
public function new(x:Float, y:Float, name:String, map:Map<String, String>, defaultValue:String, ?callback:String->Void)
|
||||
{
|
||||
super(x, y, name, function() {
|
||||
callback(this.currentValue);
|
||||
});
|
||||
|
||||
updateHitbox();
|
||||
|
||||
this.map = map;
|
||||
this.currentValue = defaultValue;
|
||||
this.onChangeCallback = callback;
|
||||
|
||||
var i:Int = 0;
|
||||
for (key in map.keys())
|
||||
{
|
||||
this.keys.push(key);
|
||||
if (this.currentValue == key) index = i;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
lefthandText = new AtlasText(15, y, formatted(defaultValue), AtlasFont.DEFAULT);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// var fancyTextFancyColor:Color;
|
||||
if (selected)
|
||||
{
|
||||
var shouldDecrease:Bool = controls().UI_LEFT_P;
|
||||
var shouldIncrease:Bool = controls().UI_RIGHT_P;
|
||||
|
||||
if (shouldDecrease) index -= 1;
|
||||
if (shouldIncrease) index += 1;
|
||||
|
||||
if (index > keys.length - 1) index = 0;
|
||||
if (index < 0) index = keys.length - 1;
|
||||
|
||||
currentValue = keys[index];
|
||||
if (onChangeCallback != null && (shouldIncrease || shouldDecrease))
|
||||
{
|
||||
onChangeCallback(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
lefthandText.text = formatted(currentValue);
|
||||
}
|
||||
|
||||
function formatted(value:String):String
|
||||
{
|
||||
// FIXME: Can't add arrows around the text because the font doesn't support < >
|
||||
// var leftArrow:String = selected ? '<' : '';
|
||||
// var rightArrow:String = selected ? '>' : '';
|
||||
return '${map.get(value) ?? value}';
|
||||
}
|
||||
}
|
136
source/funkin/ui/options/items/NumberPreferenceItem.hx
Normal file
136
source/funkin/ui/options/items/NumberPreferenceItem.hx
Normal file
|
@ -0,0 +1,136 @@
|
|||
package funkin.ui.options.items;
|
||||
|
||||
import funkin.ui.TextMenuList;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.input.Controls;
|
||||
|
||||
/**
|
||||
* Preference item that allows the player to pick a value between min and max
|
||||
*/
|
||||
class NumberPreferenceItem extends TextMenuItem
|
||||
{
|
||||
function controls():Controls
|
||||
{
|
||||
return PlayerSettings.player1.controls;
|
||||
}
|
||||
|
||||
// Widgets
|
||||
public var lefthandText:AtlasText;
|
||||
|
||||
// Constants
|
||||
static final HOLD_DELAY:Float = 0.3; // seconds
|
||||
static final CHANGE_RATE:Float = 0.08; // seconds
|
||||
|
||||
// Constructor-initialized variables
|
||||
public var currentValue:Float;
|
||||
public var min:Float;
|
||||
public var max:Float;
|
||||
public var step:Float;
|
||||
public var precision:Int;
|
||||
public var onChangeCallback:Null<Float->Void>;
|
||||
public var valueFormatter:Null<Float->String>;
|
||||
|
||||
// Variables
|
||||
var holdDelayTimer:Float = HOLD_DELAY; // seconds
|
||||
var changeRateTimer:Float = 0.0; // seconds
|
||||
|
||||
/**
|
||||
* @param min Minimum value (example: 0)
|
||||
* @param max Maximum value (example: 100)
|
||||
* @param step The value to increment/decrement by (example: 10)
|
||||
* @param callback Will get called every time the user changes the setting; use this to apply/save the setting.
|
||||
* @param valueFormatter Will get called every time the game needs to display the float value; use this to change how the displayed string looks
|
||||
*/
|
||||
public function new(x:Float, y:Float, name:String, defaultValue:Float, min:Float, max:Float, step:Float, precision:Int, ?callback:Float->Void,
|
||||
?valueFormatter:Float->String):Void
|
||||
{
|
||||
super(x, y, name, function() {
|
||||
callback(this.currentValue);
|
||||
});
|
||||
lefthandText = new AtlasText(15, y, formatted(defaultValue), AtlasFont.DEFAULT);
|
||||
|
||||
updateHitbox();
|
||||
|
||||
this.currentValue = defaultValue;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
this.precision = precision;
|
||||
this.onChangeCallback = callback;
|
||||
this.valueFormatter = valueFormatter;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// var fancyTextFancyColor:Color;
|
||||
if (selected)
|
||||
{
|
||||
holdDelayTimer -= elapsed;
|
||||
if (holdDelayTimer <= 0.0)
|
||||
{
|
||||
changeRateTimer -= elapsed;
|
||||
}
|
||||
|
||||
var jpLeft:Bool = controls().UI_LEFT_P;
|
||||
var jpRight:Bool = controls().UI_RIGHT_P;
|
||||
|
||||
if (jpLeft || jpRight)
|
||||
{
|
||||
holdDelayTimer = HOLD_DELAY;
|
||||
changeRateTimer = 0.0;
|
||||
}
|
||||
|
||||
var shouldDecrease:Bool = jpLeft;
|
||||
var shouldIncrease:Bool = jpRight;
|
||||
|
||||
if (controls().UI_LEFT && holdDelayTimer <= 0.0 && changeRateTimer <= 0.0)
|
||||
{
|
||||
shouldDecrease = true;
|
||||
changeRateTimer = CHANGE_RATE;
|
||||
}
|
||||
else if (controls().UI_RIGHT && holdDelayTimer <= 0.0 && changeRateTimer <= 0.0)
|
||||
{
|
||||
shouldIncrease = true;
|
||||
changeRateTimer = CHANGE_RATE;
|
||||
}
|
||||
|
||||
// Actually increasing/decreasing the value
|
||||
if (shouldDecrease)
|
||||
{
|
||||
var isBelowMin:Bool = currentValue - step < min;
|
||||
currentValue = (currentValue - step).clamp(min, max);
|
||||
if (onChangeCallback != null && !isBelowMin) onChangeCallback(currentValue);
|
||||
}
|
||||
else if (shouldIncrease)
|
||||
{
|
||||
var isAboveMax:Bool = currentValue + step > max;
|
||||
currentValue = (currentValue + step).clamp(min, max);
|
||||
if (onChangeCallback != null && !isAboveMax) onChangeCallback(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
lefthandText.text = formatted(currentValue);
|
||||
}
|
||||
|
||||
/** Turns the float into a string */
|
||||
function formatted(value:Float):String
|
||||
{
|
||||
var float:Float = toFixed(value);
|
||||
if (valueFormatter != null)
|
||||
{
|
||||
return valueFormatter(float);
|
||||
}
|
||||
else
|
||||
{
|
||||
return '${float}';
|
||||
}
|
||||
}
|
||||
|
||||
function toFixed(value:Float):Float
|
||||
{
|
||||
var multiplier:Float = Math.pow(10, precision);
|
||||
return Math.floor(value * multiplier) / multiplier;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ class LevelProp extends Bopper
|
|||
this.propData = value;
|
||||
|
||||
this.visible = this.propData != null;
|
||||
danceEvery = this.propData?.danceEvery ?? 0;
|
||||
danceEvery = this.propData?.danceEvery ?? 1.0;
|
||||
|
||||
applyData();
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class LevelProp extends Bopper
|
|||
|
||||
public function playConfirm():Void
|
||||
{
|
||||
playAnimation('confirm', true, true);
|
||||
if (hasAnimation('confirm')) playAnimation('confirm', true, true);
|
||||
}
|
||||
|
||||
function applyData():Void
|
||||
|
|
|
@ -216,7 +216,7 @@ class StoryMenuState extends MusicBeatState
|
|||
changeLevel();
|
||||
refresh();
|
||||
|
||||
#if discord_rpc
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence('In the Menus', null);
|
||||
#end
|
||||
|
|
|
@ -174,7 +174,7 @@ class LoadingState extends MusicBeatSubState
|
|||
FlxG.watch.addQuick('percentage?', callbacks.numRemaining / callbacks.length);
|
||||
}
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (FlxG.keys.justPressed.SPACE) trace('fired: ' + callbacks.getFired() + ' unfired:' + callbacks.getUnfired());
|
||||
#end
|
||||
}
|
||||
|
@ -291,29 +291,51 @@ class LoadingState extends MusicBeatSubState
|
|||
FunkinSprite.preparePurgeCache();
|
||||
FunkinSprite.cacheTexture(Paths.image('healthBar'));
|
||||
FunkinSprite.cacheTexture(Paths.image('menuDesat'));
|
||||
FunkinSprite.cacheTexture(Paths.image('combo'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num0'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num1'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num2'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num3'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num4'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num5'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num6'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num7'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num8'));
|
||||
FunkinSprite.cacheTexture(Paths.image('num9'));
|
||||
// Lord have mercy on me and this caching -anysad
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/combo'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num0'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num1'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num2'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num3'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num4'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num5'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num6'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num7'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num8'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/num9'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/combo'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num0'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num1'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num2'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num3'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num4'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num5'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num6'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num7'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num8'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num9'));
|
||||
|
||||
FunkinSprite.cacheTexture(Paths.image('notes', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('noteSplashes', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('noteStrumline', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('NOTE_hold_assets'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ready', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('set', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('go', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('sick', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('good', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('bad', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('shit', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this
|
||||
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/ready', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/set', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/countdown/funkin/go', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/ready', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/set', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/countdown/pixel/go', 'shared'));
|
||||
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/sick'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/good'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/bad'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/funkin/shit'));
|
||||
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/sick'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/good'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/bad'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/shit'));
|
||||
|
||||
// List all image assets in the level's library.
|
||||
// This is crude and I want to remove it when we have a proper asset caching system.
|
||||
|
|
|
@ -41,9 +41,9 @@ class Constants
|
|||
* A suffix to add to the game version.
|
||||
* Add a suffix to prototype builds and remove it for releases.
|
||||
*/
|
||||
public static final VERSION_SUFFIX:String = #if (DEBUG || FORCE_DEBUG_VERSION) ' PROTOTYPE' #else '' #end;
|
||||
public static final VERSION_SUFFIX:String = #if FEATURE_DEBUG_FUNCTIONS ' PROTOTYPE' #else '' #end;
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
static function get_VERSION():String
|
||||
{
|
||||
return 'v${Application.current.meta.get('version')} (${GIT_BRANCH} : ${GIT_HASH}${GIT_HAS_LOCAL_CHANGES ? ' : MODIFIED' : ''})' + VERSION_SUFFIX;
|
||||
|
@ -258,6 +258,11 @@ class Constants
|
|||
*/
|
||||
public static final DEFAULT_NOTE_STYLE:String = 'funkin';
|
||||
|
||||
/**
|
||||
* The default pixel note style for songs.
|
||||
*/
|
||||
public static final DEFAULT_PIXEL_NOTE_STYLE:String = 'pixel';
|
||||
|
||||
/**
|
||||
* The default album for songs in Freeplay.
|
||||
*/
|
||||
|
@ -379,11 +384,7 @@ class Constants
|
|||
* 1 = The preloader waits for 1 second before moving to the next step.
|
||||
* The progress bare is automatically rescaled to match.
|
||||
*/
|
||||
#if debug
|
||||
public static final PRELOADER_MIN_STAGE_TIME:Float = 0.0;
|
||||
#else
|
||||
public static final PRELOADER_MIN_STAGE_TIME:Float = 0.1;
|
||||
#end
|
||||
|
||||
/**
|
||||
* HEALTH VALUES
|
||||
|
@ -523,12 +524,16 @@ class Constants
|
|||
* OTHER
|
||||
*/
|
||||
// ==============================
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
// Hey there, Eric here.
|
||||
// This feature is currently still in development. You can test it out by creating a special debug build!
|
||||
// lime build windows -DFEATURE_GHOST_TAPPING
|
||||
|
||||
/**
|
||||
* If true, the player will not receive the ghost miss penalty if there are no notes within the hit window.
|
||||
* This is the thing people have been begging for forever lolol.
|
||||
* Duration, in seconds, after the player's section ends before the player can spam without penalty.
|
||||
*/
|
||||
public static final GHOST_TAPPING:Bool = false;
|
||||
public static final GHOST_TAP_DELAY:Float = 3 / 8;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The maximum number of previous file paths for the Chart Editor to remember.
|
||||
|
|
|
@ -265,9 +265,10 @@ class CrashHandler
|
|||
|
||||
static function renderMethod():String
|
||||
{
|
||||
try
|
||||
var outputStr:String = 'UNKNOWN';
|
||||
outputStr = try
|
||||
{
|
||||
return switch (FlxG.renderMethod)
|
||||
switch (FlxG.renderMethod)
|
||||
{
|
||||
case FlxRenderMethod.DRAW_TILES: 'DRAW_TILES';
|
||||
case FlxRenderMethod.BLITTING: 'BLITTING';
|
||||
|
@ -276,7 +277,9 @@ class CrashHandler
|
|||
}
|
||||
catch (e)
|
||||
{
|
||||
return 'ERROR ON QUERY RENDER METHOD: ${e}';
|
||||
'ERROR ON QUERY RENDER METHOD: ${e}';
|
||||
}
|
||||
|
||||
return outputStr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package funkin.util.plugins;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxBasic;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
|
||||
/**
|
||||
* A plugin which adds functionality to press `F5` to reload all game assets, then reload the current state.
|
||||
|
@ -28,10 +31,15 @@ class ReloadAssetsDebugPlugin extends FlxBasic
|
|||
if (FlxG.keys.justPressed.F5)
|
||||
#end
|
||||
{
|
||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||
var state:Dynamic = FlxG.state;
|
||||
if (state is MusicBeatState || state is MusicBeatSubState) state.reloadAssets();
|
||||
else
|
||||
{
|
||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||
|
||||
// Create a new instance of the current state, so old data is cleared.
|
||||
FlxG.resetState();
|
||||
// Create a new instance of the current state, so old data is cleared.
|
||||
FlxG.resetState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue