Merge branch 'rewrite/master' into funkin-mobile/main
This commit is contained in:
@@ -6,7 +6,6 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
@@ -49,7 +48,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.3
|
||||
placeholder: ex. 0.6.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.3
|
||||
placeholder: ex. 0.6.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
|
||||
placeholder: ex. 0.5.3
|
||||
placeholder: ex. 0.6.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.3
|
||||
placeholder: ex. 0.6.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ runs:
|
||||
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Haxe
|
||||
uses: funkincrew/ci-haxe@v3.2.6
|
||||
uses: funkincrew/ci-haxe@v3.2.7
|
||||
with:
|
||||
haxe-version: ${{ inputs.haxe }}
|
||||
|
||||
|
||||
@@ -118,10 +118,11 @@ jobs:
|
||||
|
||||
build-game-on-host:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: windows
|
||||
runs-on: windows-latest
|
||||
runs-on: windows
|
||||
- target: macos
|
||||
runs-on: macos
|
||||
runs-on:
|
||||
|
||||
@@ -51,6 +51,7 @@ The Pit Stop 2 update!
|
||||
- Added new stickers which appear when exiting Pico songs!
|
||||
- The new sticker system isn't fully available to mods yet, but we're working on it!
|
||||
- New option in the Preferences menu: Strumline Backgrounds!
|
||||
- Options in the Preferences menu now display an on-screen description when selected.
|
||||
- New song event type in the Chart Editor: Set Health Icon!
|
||||
- This event is now used in Stress (Pico Mix).
|
||||
- The HOME and END keys now jump to the top and bottom of the Freeplay song list, respectively. ([bb974c2](https://github.com/FunkinCrew/Funkin/commit/bb974c264270d10ff503784063e5d77bb352b3f7)) - by @AbnormalPoof in [#4103](https://github.com/FunkinCrew/Funkin/pull/4103)
|
||||
|
||||
+3
-3
@@ -14,8 +14,8 @@ Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which include
|
||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
||||
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
|
||||
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
||||
- Reconfigure `MethodLength`
|
||||
- Reconfigure `CyclomaticComplexity`
|
||||
- Re-configure `MethodLength`
|
||||
- Re-configure `CyclomaticComplexity`
|
||||
- Re-enable `MagicNumber`
|
||||
- Re-configure `NestedControlFlow`
|
||||
- Re-configure `NestedIfDepth`
|
||||
@@ -28,5 +28,5 @@ Prettier automatically handles formatting of JSON files, and can be configured i
|
||||
|
||||
### Prettier Notes
|
||||
* Prettier will automatically attempt to place expressions on a single line if they fit, but will keep them multi-line if they are manually made multi-line.
|
||||
* This means that long singleline objects are automatically expanded, and short multiline objects aren't automatically collapsed.
|
||||
* This means that long single-line objects are automatically expanded, and short multi-line objects aren't automatically collapsed.
|
||||
* You may want to use regex replacement to manually remove the first newline in short multi-line objects to convince Prettier to collapse them.
|
||||
|
||||
+1
-1
Submodule assets updated: 1a865cec81...223c5040d3
+1
-1
@@ -102,7 +102,7 @@ RUN <<EOF
|
||||
# | jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
# | jq -r '.browser_download_url')
|
||||
neko_url="https://geo.thei.rs/funkin/neko-2.4.0-linux64.tar.gz"
|
||||
neko_url="https://geo.thei.rs/funkin/neko-2.4.1-linux64.tar.gz"
|
||||
curl -fL "$neko_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
|
||||
+1
-1
@@ -347,7 +347,7 @@
|
||||
{
|
||||
"props": {
|
||||
"allowSingleLineStatement": true,
|
||||
"tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"]
|
||||
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
|
||||
},
|
||||
"type": "NeedBraces"
|
||||
},
|
||||
|
||||
+176
-64
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
Welcome to the Contributing Guide!
|
||||
You can contribute to the Funkin' repository by opening issues or pull requests.
|
||||
You can contribute to the Funkin' repository by opening issues or pull requests.
|
||||
|
||||
This guide will cover best practices for each type of contribution.
|
||||
|
||||
@@ -11,7 +11,7 @@ This guide will cover best practices for each type of contribution.
|
||||
[Part 1: Etiquette](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-1-etiquette)
|
||||
|
||||
<details>
|
||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
|
||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
|
||||
|
||||
* [Requirements](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#requirements)
|
||||
|
||||
@@ -24,8 +24,8 @@ This guide will cover best practices for each type of contribution.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
|
||||
|
||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
|
||||
|
||||
* [Choosing a base branch](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#choosing-a-base-branch)
|
||||
|
||||
@@ -39,6 +39,8 @@ This guide will cover best practices for each type of contribution.
|
||||
|
||||
* [funkin.assets PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#funkinassets-prs)
|
||||
|
||||
* [Charting PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#charting-prs)
|
||||
|
||||
</details>
|
||||
|
||||
[Closing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#closing)
|
||||
@@ -54,11 +56,11 @@ This guide will cover best practices for each type of contribution.
|
||||
|
||||
# Part 2: Issues
|
||||
Issues serve many purposes, from reporting bugs to suggesting new features.
|
||||
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
|
||||
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
|
||||
|
||||
## Requirements
|
||||
Make sure you're playing:
|
||||
- the latest version of the game (currently v0.5.3)
|
||||
- the latest version of the game (currently v0.6.3)
|
||||
- without any mods
|
||||
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
||||
|
||||
@@ -84,7 +86,7 @@ Here's a list of commonly suggested features and the reasons why they won't be a
|
||||
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
|
||||
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
|
||||
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
|
||||
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||
|
||||
|
||||
## Issue Types
|
||||
@@ -122,6 +124,9 @@ Also only report one issue or enhancement at a time! If you have multiple bug re
|
||||
|
||||
Once you're sure your issue is unique and specific, feel free to submit it.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **DO NOT CLOSE YOUR ISSUE FOR ANY REASON!** Your issue will be taken care of by a moderator!
|
||||
|
||||
**Thank you for opening issues!**
|
||||
|
||||
# Part 3: Pull Requests
|
||||
@@ -133,6 +138,7 @@ When creating a branch in your fork, base your branch on either the `main` or `d
|
||||
|
||||
> [!CAUTION]
|
||||
> Avoid using your fork's default branch (`main` in this case) for your PR. This is considered an [anti-pattern](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) by GitHub themselves!
|
||||
> Instead, make a separate branch for your additions (ex. `docs/fix-typo` or `minor-bugfix`).
|
||||
|
||||
Choose the `main` branch if you modify:
|
||||
- Documentation (`.md` files)
|
||||
@@ -165,85 +171,173 @@ This process reapplies your changes on top of the updated branch and cleanly res
|
||||
> This guide does not cover compiling. If you have trouble compiling the game, refer to the [Compilation Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md).
|
||||
|
||||
Code-based PRs make changes such as **fixing bugs** or **implementing new features** in the game.
|
||||
|
||||
This involves modifying one or several of the repository’s `.hx` files, found within the `source/` folder.
|
||||
|
||||
### Codestyle
|
||||
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md).
|
||||
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md). This keeps your code consistent with the rest of the codebase!
|
||||
|
||||
### Code comments
|
||||
Code comments help others understand your changes, so the way you write them is important!
|
||||
Here are some guidelines for writing comments in your code:
|
||||
- Leave comments only when you believe a piece of code warrants explanation.
|
||||
- Leave comments only when you believe a piece of code warrants explanation. If a piece of code is self-explanatory, it does not need a comment.
|
||||
- Ensure that your comments provide meaningful insight into the function or purpose of the code.
|
||||
- Write your comments in a clear and concise manner.
|
||||
- Only sign your comments with your name when your changes are complex and may require further explanation.
|
||||
|
||||
### Example Comments
|
||||
### Example code comments
|
||||
#### DO NOT:
|
||||
Below are examples of what you SHOULD NOT do when writing code comments.
|
||||
```haxe
|
||||
/**
|
||||
* jumps around the song
|
||||
* works with bpm changes but skipped notes still hurt
|
||||
* @param sections how many sections to jump, negative = backwards
|
||||
*/
|
||||
function changeSection(sections:Int):Void
|
||||
/**
|
||||
* jumps around the song
|
||||
* works with bpm changes but skipped notes still hurt
|
||||
* @param sections how many sections to jump, negative = backwards
|
||||
*/
|
||||
function changeSection(sections:Int):Void
|
||||
{
|
||||
// Pause the music, as you probably guessed
|
||||
// FlxG.sound.music.pause();
|
||||
|
||||
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
|
||||
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||
|
||||
// Don't go back in time to before the song started, that would probably break a lot of things and cause a bunch of problems!
|
||||
targetTimeMs = Math.max(0, targetTimeMs);
|
||||
|
||||
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
|
||||
{
|
||||
// Pause the music, as you probably guessed
|
||||
// FlxG.sound.music.pause();
|
||||
|
||||
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
|
||||
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||
|
||||
// Don't go back in time to before the song started, that would probably break a lot of things and cause a whole bunch of problems!
|
||||
targetTimeMs = Math.max(0, targetTimeMs);
|
||||
|
||||
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
|
||||
{
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
}
|
||||
|
||||
// Handle skipped notes and events and all that jazz
|
||||
handleSkippedNotes();
|
||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||
// regenNoteData(FlxG.sound.music.time);
|
||||
|
||||
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||
|
||||
// I hate this function - [GitHub username]
|
||||
resyncVocals();
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
}
|
||||
|
||||
// Handle skipped notes and events and all that jazz
|
||||
handleSkippedNotes();
|
||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||
// regenNoteData(FlxG.sound.music.time);
|
||||
|
||||
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||
|
||||
// I hate this function - [GitHub username]
|
||||
resyncVocals();
|
||||
}
|
||||
```
|
||||
|
||||
```haxe
|
||||
// End the song when the music is complete.
|
||||
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, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||
FlxG.sound.music.pitch = playbackRate;
|
||||
|
||||
// Prevent the volume from being wrong.
|
||||
FlxG.sound.music.volume = 1.0;
|
||||
// IF fadetween is not null we cancel it
|
||||
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
|
||||
|
||||
// Play the vocals
|
||||
trace('Playing vocals...');
|
||||
// Add the vocals
|
||||
add(vocals);
|
||||
// Play the vocals for real this time lol
|
||||
vocals.play();
|
||||
// Set the vocals volume
|
||||
vocals.volume = 1.0;
|
||||
// Set the vocals pitch
|
||||
vocals.pitch = playbackRate;
|
||||
// Set the vocals time to the music time
|
||||
vocals.time = FlxG.sound.music.time;
|
||||
// trace('${FlxG.sound.music.time}');
|
||||
// trace('${vocals.time}');
|
||||
// functionThatWasntHereBeforeThisPRorSomethingIdKLOL();
|
||||
/* testsprite = new FlxSprite(0, 0);
|
||||
testsprite.loadGraphic(Paths.image('test'));
|
||||
testsprite.screenCenter();
|
||||
add(testsprite); */
|
||||
|
||||
// Me too [GitHub username] I hate this function it gave me pain and suffering - Girlfriend
|
||||
resyncVocals();
|
||||
```
|
||||
|
||||
```haxe
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// PAGEUP: who knows what this does
|
||||
// SHIFT+PAGEUP: There will be dire consequences.
|
||||
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||
// PAGEUP: who knows what this does
|
||||
// SHIFT+PAGEUP: There will be dire consequences.
|
||||
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||
#end
|
||||
```
|
||||
|
||||
#### DO:
|
||||
Below are examples on what you SHOULD do when writing code comments.
|
||||
```haxe
|
||||
/**
|
||||
* 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
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||
|
||||
// Don't go back in time to before the song started.
|
||||
targetTimeMs = Math.max(0, targetTimeMs);
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||
|
||||
// Don't go back in time to before the song started.
|
||||
targetTimeMs = Math.max(0, targetTimeMs);
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
}
|
||||
|
||||
handleSkippedNotes();
|
||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||
|
||||
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||
|
||||
resyncVocals();
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
}
|
||||
|
||||
handleSkippedNotes();
|
||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||
|
||||
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||
|
||||
resyncVocals();
|
||||
}
|
||||
```
|
||||
|
||||
```haxe
|
||||
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, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||
FlxG.sound.music.pitch = playbackRate;
|
||||
|
||||
// Prevent the volume from being wrong.
|
||||
FlxG.sound.music.volume = 1.0;
|
||||
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
|
||||
|
||||
trace('Playing vocals...');
|
||||
add(vocals);
|
||||
vocals.play();
|
||||
vocals.volume = 1.0;
|
||||
vocals.pitch = playbackRate;
|
||||
vocals.time = FlxG.sound.music.time;
|
||||
resyncVocals();
|
||||
```
|
||||
|
||||
```haxe
|
||||
#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);
|
||||
// PAGEDOWN: Skip backward two section. Doesn't replace notes.
|
||||
// SHIFT+PAGEDOWN: Skip backward twenty sections.
|
||||
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||
#end
|
||||
```
|
||||
|
||||
## Documentation PRs
|
||||
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
|
||||
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
|
||||
|
||||
This involves modifying one or several of the repository’s `.md` files, found throughout the repository.
|
||||
|
||||
@@ -297,6 +391,24 @@ If you simultaneously modify files from both repositories, then open two separat
|
||||
|
||||
Be sure to choose `main` as the base branch for `funkin.assets` PRs, as no `develop` branch exists for that repository.
|
||||
|
||||
### Charting PRs
|
||||
Charting PRs make changes such as **adding/removing notes** or **adjusting the placement of song events**.
|
||||
|
||||
This involves modifying one or several of the `funkin.assets` repository's `.json` chart files, found in the `preload/data/songs/` directory.
|
||||
|
||||
These PRs should only be opened in the `funkin.assets` repository.
|
||||
|
||||
> [!CAUTION]
|
||||
> **No Major Recharts!** Any PR that makes major chart modifications will be rejected.
|
||||
> Keep your PRs to small tweaks and fixes.
|
||||
|
||||
Here are some guidelines for opening a Charting PR:
|
||||
- **Explain the issue.** Which song, variation, difficulty, and section/timestamp is the problem in? Help us understand with screenshots and videos.
|
||||
- **Show your changes.** How does the chart look with your changes? Provide screenshots and videos here as well.
|
||||
- **Minimize the diff.** If your changes are very small (e.g. a few notes), do not re-export the chart using the Chart Editor. Instead, manually edit the `.json` chart files to help GitHub display your changes cleanly.
|
||||
|
||||
If your PR is accepted, you will be credited as a GitHub contributor (but not as a charter in the Pause Menu).
|
||||
|
||||
# Closing
|
||||
Thank you for reading the Contributing Guide.
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
Most of this functionality is only available on debug builds of the game!
|
||||
|
||||
## Any State
|
||||
- `INSERT`: ***MEMORY GARBAGE COLLECTION***: Immediately perform memory garbage collection.
|
||||
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
||||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||
@@ -11,6 +12,9 @@ Most of this functionality is only available on debug builds of the game!
|
||||
|
||||
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||
|
||||
## Loading State
|
||||
- `SPACE`: Trace callbacks.
|
||||
|
||||
## **Play State**
|
||||
- `H`: ***HIDE UI***: Makes the user interface invisible. Works in Pause Menu, great for screenshots.
|
||||
- `1`: ***END SONG***: Immediately ends the song and moves to Results Screen on Freeplay, or next song on Story Mode.
|
||||
@@ -19,20 +23,29 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `9`: NEATO!
|
||||
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
||||
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
||||
- `CHART Hotkey`: ***OPEN CHART EDITOR***: Open the Chart Editor with the current song loaded.
|
||||
- `STAGE Hotkey`: ***OPEN STAGE EDITOR***: Open the old Stage Editor overlay.
|
||||
|
||||
## **Freeplay State**
|
||||
- `F` (Freeplay Menu) - Move to Favorites
|
||||
- `Q` (Freeplay Menu) - Back one category
|
||||
- `E` (Freeplay Menu) - Forward one category
|
||||
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
|
||||
- `T`: ***TEST RANK ANIMATION***: Play the Gold Perfect rank animation on the currently selected song.
|
||||
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
|
||||
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
||||
- `CTRL-LEFT`/`RIGHT`/`UP`/`DOWN`: ***OFFSET BF DJ IN CARTOON STATE***: While BF DJ's TV animation is playing, offset the DJ by 1 pixel in the opposite direction. `SHIFT` to move 10 pixels, `ALT` to move 0.1 pixels.
|
||||
- `CTRL-C`: ***TRIGGER DJ CARTOON***: Play the Freeplay DJ's cartoon animation. Press again to return to its regular animation.
|
||||
|
||||
## **Title State**
|
||||
- `Y` - WOAH
|
||||
- `ESCAPE`: ***QUIT***: Quit the game.
|
||||
- `D`: COOL
|
||||
- `UI LEFT`/`UI RIGHT Hotkey`: 2COOL
|
||||
- `Y`: WOAH
|
||||
|
||||
## **Main Menu**
|
||||
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||
- `SHIFT-ACCEPT`: ***OPEN FREEPLAY AS PICO***: When selecting Freeplay, open the Freeplay menu with the playable character set to Pico.
|
||||
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-M`: ***NO MORE ACCESS***: Re-locks all songs in Freeplay except those unlocked by default. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-R`: ***GREAT SCORE?***: Give the user a hypothetical overridden score, and see if we can maintain that golden P rank. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-P`: ***CHARACTER UNLOCK SCREEN***: Forces the Character Select screen to play Pico's unlocking animation. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-N`: ***CHARACTER NOT SEEN***: Marks all characters as not seen and enables BF's new character unlocked animation in Freeplay. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ This short document is designed to give a run-down on how code should be formatt
|
||||
|
||||
## Notes on IDEs and Extensions
|
||||
|
||||
The Visual Studio Code IDE is highly recommended, as this repo contains various configuration that works with VSCode extensions to automatically style certain things for you.
|
||||
The Visual Studio Code IDE is highly recommended, as this repo contains various configurations that work with VSCode extensions to automatically style certain things for you.
|
||||
|
||||
VSCode is also the only IDE that has any good tools for Haxe so yeah.
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "713d3de0e566d6cd54cde13711ab3e4f60f59f4b",
|
||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||
"ref": "f8842cea9883d6112a2c854cf93fa72856171428",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
@@ -143,7 +143,7 @@
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e41e3d428ccdd13a1197b20c353f7c40acb45e96",
|
||||
"ref": "be81ad7e4e1a92c3482bcc009648a4ac892cfa35",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ class Project extends HXProject {
|
||||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||
* You only have to change it here, the rest of the game will query this value.
|
||||
*/
|
||||
static final VERSION:String = "0.6.2";
|
||||
static final VERSION:String = "0.6.3";
|
||||
|
||||
/**
|
||||
* The game's name. Used as the default window title.
|
||||
|
||||
@@ -6,6 +6,7 @@ import openfl.utils.Future;
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Assets
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -125,6 +125,9 @@ class InitState extends FlxState
|
||||
// Set the game to a lower frame rate while it is in the background.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
|
||||
FlxG.fixedTimestep = false;
|
||||
|
||||
setupFlixelDebug();
|
||||
|
||||
//
|
||||
@@ -281,8 +284,7 @@ class InitState extends FlxState
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico",
|
||||
difficultyId: "nightmare",
|
||||
variationId: "erect",
|
||||
difficultyId: "hard",
|
||||
isNewHighscore: true,
|
||||
scoreData:
|
||||
{
|
||||
|
||||
@@ -149,6 +149,9 @@ class NewgroundsClient
|
||||
Save.instance.ngSessionId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if the user is logged in to Newgrounds.
|
||||
*/
|
||||
public function isLoggedIn():Bool
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.NG;
|
||||
#end
|
||||
import funkin.api.newgrounds.NewgroundsClient;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
/**
|
||||
* Handles referrals to links retrieved from the Newgrounds API.
|
||||
*/
|
||||
class Referral
|
||||
{
|
||||
/**
|
||||
* Opens the referral page with the given name.
|
||||
* @param referralName The name of the referral.
|
||||
* @param fallbackUrl The URL to open if the referral fails or is not available.
|
||||
*/
|
||||
static function doReferral(referralName:String, fallbackUrl:String):Void
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
NG.core?.calls.loader.loadReferral(false)
|
||||
.addComponentParameter("referral_name", referralName)
|
||||
.addResponseHandler(response -> {
|
||||
trace(response);
|
||||
if (response.success)
|
||||
{
|
||||
WindowUtil.openURL(response.result.data.url);
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowUtil.openURL(fallbackUrl);
|
||||
}
|
||||
})
|
||||
.send();
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowUtil.openURL(fallbackUrl);
|
||||
}
|
||||
#else
|
||||
WindowUtil.openURL(fallbackUrl);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the merch referral page.
|
||||
*/
|
||||
public static function doMerchReferral():Void
|
||||
{
|
||||
doReferral("merch_link", Constants.URL_MERCH_FALLBACK);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +334,11 @@ typedef PlayerResultsAnimationData =
|
||||
*/
|
||||
var renderType:String;
|
||||
|
||||
var assetPath:String;
|
||||
@:optional
|
||||
var assetPath:Null<String>;
|
||||
|
||||
@:optional
|
||||
var scriptClass:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
|
||||
@@ -10,6 +10,7 @@ import flixel.math.FlxPoint;
|
||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||
* does offsets for characters. example, fixes Spirits trail
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkTrail extends FlxTrail
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import flixel.util.FlxTimer;
|
||||
import flixel.FlxCamera;
|
||||
import openfl.filters.ColorMatrixFilter;
|
||||
|
||||
@:nullSafety
|
||||
class RetroCameraFade
|
||||
{
|
||||
// im lazy, but we only use this for week 6
|
||||
|
||||
@@ -251,6 +251,9 @@ class PolymodHandler
|
||||
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||
|
||||
// `funkin.util.FileUtil` has unrestricted access to the file system.
|
||||
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
|
||||
|
||||
// Add blacklisting for prohibited classes and packages.
|
||||
|
||||
// `Sys`
|
||||
|
||||
@@ -686,9 +686,6 @@ class PlayState extends MusicBeatSubState
|
||||
funkin.util.plugins.TouchPointerPlugin.enabled = false;
|
||||
#end
|
||||
|
||||
// Reduce physics accuracy (who cares!!!) to improve animation quality.
|
||||
FlxG.fixedTimestep = false;
|
||||
|
||||
// This state receives update() even when a substate is active.
|
||||
this.persistentUpdate = true;
|
||||
// This state receives draw calls even when a substate is active.
|
||||
@@ -941,6 +938,9 @@ class PlayState extends MusicBeatSubState
|
||||
// Delete all notes and reset the arrays.
|
||||
regenNoteData();
|
||||
|
||||
// so the song doesn't start too early :D
|
||||
Conductor.instance.update(-5000, false);
|
||||
|
||||
// Reset camera zooming
|
||||
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
|
||||
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
|
||||
@@ -949,11 +949,26 @@ class PlayState extends MusicBeatSubState
|
||||
health = Constants.HEALTH_STARTING;
|
||||
songScore = 0;
|
||||
Highscore.tallies.combo = 0;
|
||||
Countdown.performCountdown();
|
||||
// timer for vwoosh
|
||||
var vwooshTimer = new FlxTimer();
|
||||
vwooshTimer.start(0.5, function(t:FlxTimer) {
|
||||
Conductor.instance.update(startTimestamp - Conductor.instance.combinedOffset, false);
|
||||
if (playerStrumline.notes.length == 0) playerStrumline.updateNotes();
|
||||
if (opponentStrumline.notes.length == 0) opponentStrumline.updateNotes();
|
||||
playerStrumline.vwooshInNotes();
|
||||
opponentStrumline.vwooshInNotes();
|
||||
Countdown.performCountdown();
|
||||
});
|
||||
|
||||
// Reset the health icons.
|
||||
currentStage.getBoyfriend().initHealthIcon(false);
|
||||
currentStage.getDad().initHealthIcon(true);
|
||||
if (currentStage.getBoyfriend() != null)
|
||||
{
|
||||
currentStage.getBoyfriend().initHealthIcon(false);
|
||||
}
|
||||
if (currentStage.getDad() != null)
|
||||
{
|
||||
currentStage.getDad().initHealthIcon(true);
|
||||
}
|
||||
|
||||
needsReset = false;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
|
||||
var dumbNumb = Std.parseInt(Std.string(val));
|
||||
var prevNum:ScoreNum;
|
||||
|
||||
dumbNumb = Std.int(Math.min(dumbNumb, Math.pow(10, group.members.length) - 1));
|
||||
|
||||
while (dumbNumb > 0)
|
||||
{
|
||||
scoreStart += 1;
|
||||
|
||||
@@ -33,6 +33,7 @@ import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.modding.base.ScriptedFlxAtlasSprite;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
#end
|
||||
@@ -199,14 +200,30 @@ class ResultState extends MusicBeatSubState
|
||||
if (Preferences.naughtyness && animData.filter != "naughty" || !Preferences.naughtyness && animData.filter != "safe") continue;
|
||||
}
|
||||
|
||||
var animPath:String = Paths.stripLibrary(animData.assetPath);
|
||||
var animLibrary:String = Paths.getLibrary(animData.assetPath);
|
||||
var animPath:String = "";
|
||||
var animLibrary:String = "";
|
||||
|
||||
if (animData.assetPath != null)
|
||||
{
|
||||
animPath = Paths.stripLibrary(animData.assetPath);
|
||||
animLibrary = Paths.getLibrary(animData.assetPath);
|
||||
}
|
||||
var offsets = animData.offsets ?? [0, 0];
|
||||
switch (animData.renderType)
|
||||
{
|
||||
case 'animateatlas':
|
||||
var animation:FlxAtlasSprite = new FlxAtlasSprite(offsets[0] + (FullScreenScaleMode.gameCutoutSize.x / 2), offsets[1],
|
||||
Paths.animateAtlas(animPath, animLibrary));
|
||||
@:nullSafety(Off)
|
||||
var animation:FlxAtlasSprite = null;
|
||||
|
||||
var xPos = offsets[0] + (FullScreenScaleMode.gameCutoutSize.x / 2);
|
||||
var yPos = offsets[1];
|
||||
|
||||
if (animData.scriptClass != null) animation = ScriptedFlxAtlasSprite.init(animData.scriptClass, xPos, yPos);
|
||||
else
|
||||
animation = new FlxAtlasSprite(xPos, yPos, Paths.animateAtlas(animPath, animLibrary));
|
||||
|
||||
if (animation == null) continue;
|
||||
|
||||
animation.zIndex = animData.zIndex ?? 500;
|
||||
|
||||
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
|
||||
@@ -235,7 +252,6 @@ class ResultState extends MusicBeatSubState
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
if (animation != null)
|
||||
{
|
||||
trace("AHAHAH");
|
||||
animation.anim.curFrame = animData.loopFrame ?? 0;
|
||||
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||
}
|
||||
@@ -513,7 +529,7 @@ class ResultState extends MusicBeatSubState
|
||||
// Only play the tick sound if the number increased.
|
||||
if (clearPercentLerp != clearPercentCounter.curNumber)
|
||||
{
|
||||
trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
|
||||
// trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
|
||||
clearPercentLerp = clearPercentCounter.curNumber;
|
||||
FunkinSound.playOnce(Paths.sound('scrollMenu'));
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import funkin.util.MathUtil;
|
||||
/**
|
||||
* Numerical counters used next to each judgement in the Results screen.
|
||||
*/
|
||||
@:nullSafety
|
||||
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
public var curNumber:Float = 0;
|
||||
@@ -17,13 +18,13 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||
|
||||
public var align:FlxTextAlign = FlxTextAlign.LEFT;
|
||||
|
||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF, align:FlxTextAlign = FlxTextAlign.LEFT)
|
||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int, align:FlxTextAlign = FlxTextAlign.LEFT)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.align = align;
|
||||
|
||||
this.flavour = flavour;
|
||||
this.flavour = flavour ?? 0xFFFFFFFF;
|
||||
|
||||
this.neededNumber = neededNumber;
|
||||
|
||||
@@ -79,6 +80,7 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class TallyNumber extends FlxSprite
|
||||
{
|
||||
public function new(x:Float, y:Float, digit:Int)
|
||||
|
||||
@@ -104,6 +104,11 @@ class NoteSprite extends FunkinSprite
|
||||
return noteData.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Y Offset of the note.
|
||||
*/
|
||||
public var yOffset:Float = 0.0;
|
||||
|
||||
/**
|
||||
* Set this flag to true when hitting the note to avoid scoring it multiple times.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.play.notes;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flixel.FlxG;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
@@ -27,35 +28,46 @@ import flixel.FlxSprite;
|
||||
*/
|
||||
class Strumline extends FlxSpriteGroup
|
||||
{
|
||||
/**
|
||||
* The directions of the notes on the strumline, in order.
|
||||
*/
|
||||
public static final DIRECTIONS:Array<NoteDirection> = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT];
|
||||
|
||||
/**
|
||||
* A magic number for the size of the strumline, in pixels.
|
||||
*/
|
||||
public static final STRUMLINE_SIZE:Int = 104;
|
||||
|
||||
/**
|
||||
* The spacing between notes on the strumline, in pixels.
|
||||
*/
|
||||
public static final NOTE_SPACING:Int = STRUMLINE_SIZE + 8;
|
||||
|
||||
// Positional fixes for new strumline graphics.
|
||||
static final INITIAL_OFFSET = -0.275 * STRUMLINE_SIZE;
|
||||
static final INITIAL_OFFSET:Float = -0.275 * STRUMLINE_SIZE;
|
||||
static final NUDGE:Float = 2.0;
|
||||
|
||||
static final KEY_COUNT:Int = 4;
|
||||
static final NOTE_SPLASH_CAP:Int = 6;
|
||||
|
||||
static var RENDER_DISTANCE_MS(get, never):Float;
|
||||
var renderDistanceMs(get, never):Float;
|
||||
|
||||
/**
|
||||
* The custom render distance for the strumline.
|
||||
* This should be in miliseconds only! Not pixels.
|
||||
*/
|
||||
public static var CUSTOM_RENDER_DISTANCE_MS:Float = 0.0;
|
||||
public var customRenderDistanceMs:Float = 0.0;
|
||||
|
||||
/**
|
||||
* Whether to use the custom render distance.
|
||||
* If false, the render distance will be calculated based on the screen height.
|
||||
*/
|
||||
public static var USE_CUSTOM_RENDER_DISTANCE:Bool = false;
|
||||
public var useCustomRenderDistance:Bool = false;
|
||||
|
||||
static function get_RENDER_DISTANCE_MS():Float
|
||||
function get_renderDistanceMs():Float
|
||||
{
|
||||
if (USE_CUSTOM_RENDER_DISTANCE) return CUSTOM_RENDER_DISTANCE_MS;
|
||||
return FlxG.height / Constants.PIXELS_PER_MS;
|
||||
if (useCustomRenderDistance) return customRenderDistanceMs;
|
||||
return FlxG.height / Constants.PIXELS_PER_MS / scrollSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,6 +92,9 @@ class Strumline extends FlxSpriteGroup
|
||||
// Used in-game to control the scroll speed within a song
|
||||
public var scrollSpeed:Float = 1.0;
|
||||
|
||||
/**
|
||||
* Reset the scroll speed to the current chart's scroll speed.
|
||||
*/
|
||||
public function resetScrollSpeed():Void
|
||||
{
|
||||
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
|
||||
@@ -110,8 +125,16 @@ class Strumline extends FlxSpriteGroup
|
||||
*/
|
||||
public var notes:FlxTypedSpriteGroup<NoteSprite>;
|
||||
|
||||
/**
|
||||
* The hold notes currently being rendered on the strumline.
|
||||
* This group iterates over this every frame to update hold note positions.
|
||||
* The PlayState also iterates over this to calculate user inputs.
|
||||
*/
|
||||
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
|
||||
|
||||
/**
|
||||
* A signal that is dispatched when a note is spawned and heading towards the strumline.
|
||||
*/
|
||||
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
|
||||
|
||||
var background:FunkinSprite;
|
||||
@@ -250,6 +273,9 @@ class Strumline extends FlxSpriteGroup
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the strumline, sorting its children by z-index.
|
||||
*/
|
||||
public function refresh():Void
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
@@ -275,7 +301,7 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
#if FEATURE_GHOST_TAPPING
|
||||
/**
|
||||
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
|
||||
* @return `true` if no notes are in range of the strumline and the player can spam without penalty.
|
||||
*/
|
||||
public function mayGhostTap():Bool
|
||||
{
|
||||
@@ -320,31 +346,41 @@ class Strumline extends FlxSpriteGroup
|
||||
});
|
||||
}
|
||||
|
||||
public function getNoteSprite(noteData:SongNoteData):NoteSprite
|
||||
/**
|
||||
* Get a note sprite corresponding to the given note data.
|
||||
* @param target The note data for the note sprite.
|
||||
* @return The note sprite.
|
||||
*/
|
||||
public function getNoteSprite(target:SongNoteData):NoteSprite
|
||||
{
|
||||
if (noteData == null) return null;
|
||||
if (target == null) return null;
|
||||
|
||||
for (note in notes.members)
|
||||
{
|
||||
if (note == null) continue;
|
||||
if (note.alive) continue;
|
||||
|
||||
if (note.noteData == noteData) return note;
|
||||
if (note.noteData == target) return note;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getHoldNoteSprite(noteData:SongNoteData):SustainTrail
|
||||
/**
|
||||
* Get a hold note sprite corresponding to the given note data.
|
||||
* @param target The note data for the hold note.
|
||||
* @return The hold note sprite.
|
||||
*/
|
||||
public function getHoldNoteSprite(target:SongNoteData):SustainTrail
|
||||
{
|
||||
if (noteData == null || ((noteData.length ?? 0.0) <= 0.0)) return null;
|
||||
if (target == null || ((target.length ?? 0.0) <= 0.0)) return null;
|
||||
|
||||
for (holdNote in holdNotes.members)
|
||||
{
|
||||
if (holdNote == null) continue;
|
||||
if (holdNote.alive) continue;
|
||||
|
||||
if (holdNote.noteData == noteData) return holdNote;
|
||||
if (holdNote.noteData == target) return holdNote;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -355,6 +391,8 @@ class Strumline extends FlxSpriteGroup
|
||||
*/
|
||||
public function vwooshNotes():Void
|
||||
{
|
||||
var vwooshTime:Float = 0.5;
|
||||
|
||||
for (note in notes.members)
|
||||
{
|
||||
if (note == null) continue;
|
||||
@@ -365,7 +403,7 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
var targetY:Float = FlxG.height + note.y;
|
||||
if (isDownscroll) targetY = 0 - note.height;
|
||||
FlxTween.tween(note, {y: targetY}, 0.5,
|
||||
FlxTween.tween(note, {y: targetY}, vwooshTime,
|
||||
{
|
||||
ease: FlxEase.expoIn,
|
||||
onComplete: function(twn) {
|
||||
@@ -386,7 +424,7 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
var targetY:Float = FlxG.height + holdNote.y;
|
||||
if (isDownscroll) targetY = 0 - holdNote.height;
|
||||
FlxTween.tween(holdNote, {y: targetY}, 0.5,
|
||||
FlxTween.tween(holdNote, {y: targetY}, vwooshTime,
|
||||
{
|
||||
ease: FlxEase.expoIn,
|
||||
onComplete: function(twn) {
|
||||
@@ -444,19 +482,64 @@ class Strumline extends FlxSpriteGroup
|
||||
* NOTE: Assumes Conductor and PlayState are both initialized.
|
||||
* @param strumTime
|
||||
* @return Float
|
||||
* Reverse of vwooshNotes, we bring the notes IN (by their offsets)
|
||||
*/
|
||||
public function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float
|
||||
public function vwooshInNotes():Void
|
||||
{
|
||||
// Make the note move faster visually as it moves offscreen.
|
||||
// var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
|
||||
// ^^^ commented this out... do NOT make it move faster as it moves offscreen!
|
||||
var vwoosh:Float = 1.0;
|
||||
var vwooshTime:Float = 0.5;
|
||||
|
||||
return
|
||||
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * vwoosh * (isDownscroll ? 1 : -1);
|
||||
for (note in notes.members)
|
||||
{
|
||||
if (note == null) continue;
|
||||
if (!note.alive) continue;
|
||||
|
||||
note.yOffset = 200;
|
||||
if (Preferences.downscroll)
|
||||
{
|
||||
note.yOffset = -200;
|
||||
}
|
||||
FlxTween.tween(note, {yOffset: 0}, vwooshTime,
|
||||
{
|
||||
ease: FlxEase.expoOut,
|
||||
onComplete: function(twn) {
|
||||
note.yOffset = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (holdNote in holdNotes.members)
|
||||
{
|
||||
if (holdNote == null) continue;
|
||||
if (!holdNote.alive) continue;
|
||||
|
||||
holdNote.yOffset = 200;
|
||||
if (Preferences.downscroll)
|
||||
{
|
||||
holdNote.yOffset = -200;
|
||||
}
|
||||
FlxTween.tween(holdNote, {yOffset: 0}, vwooshTime,
|
||||
{
|
||||
ease: FlxEase.expoOut,
|
||||
onComplete: function(twn) {
|
||||
holdNote.yOffset = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateNotes():Void
|
||||
/**
|
||||
* For a note's strumTime, calculate its Y position relative to the strumline.
|
||||
* NOTE: Assumes Conductor and PlayState are both initialized.
|
||||
* @param strumTime The strumtime of the note.
|
||||
* @return The Y position of the note.
|
||||
*/
|
||||
public function calculateNoteYPos(strumTime:Float):Float
|
||||
{
|
||||
return
|
||||
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1);
|
||||
}
|
||||
|
||||
public function updateNotes():Void
|
||||
{
|
||||
if (noteData.length == 0) return;
|
||||
|
||||
@@ -467,7 +550,7 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
||||
var hitWindowStart:Float = conductorInUse.songPosition - Constants.HIT_WINDOW_MS;
|
||||
var renderWindowStart:Float = conductorInUse.songPosition + RENDER_DISTANCE_MS;
|
||||
var renderWindowStart:Float = conductorInUse.songPosition + renderDistanceMs;
|
||||
|
||||
for (noteIndex in nextNoteIndex...noteData.length)
|
||||
{
|
||||
@@ -482,7 +565,7 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
if (note.time > renderWindowStart) break; // Note is too far ahead to render
|
||||
|
||||
var noteSprite = buildNoteSprite(note);
|
||||
var noteSprite:NoteSprite = buildNoteSprite(note);
|
||||
|
||||
if (note.length > 0)
|
||||
{
|
||||
@@ -499,12 +582,11 @@ class Strumline extends FlxSpriteGroup
|
||||
{
|
||||
if (note == null || !note.alive) continue;
|
||||
|
||||
var vwoosh:Bool = note.holdNoteSprite == null;
|
||||
// Set the note's position.
|
||||
if (!customPositionData) note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime, vwoosh);
|
||||
if (!customPositionData) note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime) + note.yOffset;
|
||||
|
||||
// If the note is miss
|
||||
var isOffscreen = isDownscroll ? note.y > FlxG.height : note.y < -note.height;
|
||||
var isOffscreen:Bool = isDownscroll ? note.y > FlxG.height : note.y < -note.height;
|
||||
if (note.handledMiss && isOffscreen)
|
||||
{
|
||||
killNote(note);
|
||||
@@ -528,7 +610,8 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
}
|
||||
|
||||
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
|
||||
final magicNumberIGuess:Float = 8;
|
||||
var renderWindowEnd:Float = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + (renderDistanceMs / magicNumberIGuess);
|
||||
|
||||
if (holdNote.missedNote && conductorInUse.songPosition >= renderWindowEnd)
|
||||
{
|
||||
@@ -578,17 +661,20 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Constants.PIXELS_PER_MS;
|
||||
|
||||
var vwoosh:Bool = false;
|
||||
|
||||
if (!customPositionData)
|
||||
{
|
||||
if (isDownscroll)
|
||||
{
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
|
||||
holdNote.y = this.y
|
||||
- INITIAL_OFFSET
|
||||
+ calculateNoteYPos(holdNote.strumTime)
|
||||
- holdNote.height
|
||||
+ STRUMLINE_SIZE / 2
|
||||
+ holdNote.yOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2;
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + yOffset + STRUMLINE_SIZE / 2 + holdNote.yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,17 +717,21 @@ class Strumline extends FlxSpriteGroup
|
||||
{
|
||||
// Hold note is new, render it normally.
|
||||
holdNote.visible = true;
|
||||
var vwoosh:Bool = false;
|
||||
|
||||
if (!customPositionData)
|
||||
{
|
||||
if (isDownscroll)
|
||||
{
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
|
||||
holdNote.y = this.y
|
||||
- INITIAL_OFFSET
|
||||
+ calculateNoteYPos(holdNote.strumTime)
|
||||
- holdNote.height
|
||||
+ STRUMLINE_SIZE / 2
|
||||
+ holdNote.yOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + STRUMLINE_SIZE / 2;
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + STRUMLINE_SIZE / 2 + holdNote.yOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,6 +786,9 @@ class Strumline extends FlxSpriteGroup
|
||||
nextNoteIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on each beat of the song.
|
||||
*/
|
||||
public function onBeatHit():Void
|
||||
{
|
||||
if (notes.members.length > 1) notes.members.insertionSort(compareNoteSprites.bind(FlxSort.ASCENDING));
|
||||
@@ -703,16 +796,29 @@ class Strumline extends FlxSpriteGroup
|
||||
if (holdNotes.members.length > 1) holdNotes.members.insertionSort(compareHoldNoteSprites.bind(FlxSort.ASCENDING));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a key is pressed.
|
||||
* @param dir The direction of the key that was pressed.
|
||||
*/
|
||||
public function pressKey(dir:NoteDirection):Void
|
||||
{
|
||||
heldKeys[dir] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a key is released.
|
||||
* @param dir The direction of the key that was released.
|
||||
*/
|
||||
public function releaseKey(dir:NoteDirection):Void
|
||||
{
|
||||
heldKeys[dir] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a key is held down.
|
||||
* @param dir The direction of the key to check.
|
||||
* @return `true` if the key is held down, `false` otherwise.
|
||||
*/
|
||||
public function isKeyHeld(dir:NoteDirection):Bool
|
||||
{
|
||||
return heldKeys[dir];
|
||||
@@ -762,6 +868,11 @@ class Strumline extends FlxSpriteGroup
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply note data from a chart to this strumline.
|
||||
* Note data should be valid and apply only to this strumline.
|
||||
* @param data The note data to apply.
|
||||
*/
|
||||
public function applyNoteData(data:Array<SongNoteData>):Void
|
||||
{
|
||||
this.notes.clear();
|
||||
@@ -774,6 +885,7 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* Hit a note.
|
||||
* @param note The note to hit.
|
||||
* @param removeNote True to remove the note immediately, false to make it transparent and let it move offscreen.
|
||||
*/
|
||||
@@ -805,6 +917,10 @@ class Strumline extends FlxSpriteGroup
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill a note heading towards the strumline.
|
||||
* @param note The note to kill. Gets recycled and reused for performance.
|
||||
*/
|
||||
public function killNote(note:NoteSprite):Void
|
||||
{
|
||||
if (note == null) return;
|
||||
@@ -819,16 +935,30 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a strumline note sprite by its index.
|
||||
* @param index The index of the note to get.
|
||||
* @return The note.
|
||||
*/
|
||||
public function getByIndex(index:Int):StrumlineNote
|
||||
{
|
||||
return this.strumlineNotes.members[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a strumline note sprite by its direction.
|
||||
* @param direction The direction of the note to get.
|
||||
* @return The note.
|
||||
*/
|
||||
public function getByDirection(direction:NoteDirection):StrumlineNote
|
||||
{
|
||||
return getByIndex(DIRECTIONS.indexOf(direction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a static animation for a given direction.
|
||||
* @param direction The direction of the note to play the static animation for.
|
||||
*/
|
||||
public function playStatic(direction:NoteDirection):Void
|
||||
{
|
||||
getByDirection(direction).playStatic();
|
||||
@@ -837,26 +967,47 @@ class Strumline extends FlxSpriteGroup
|
||||
if (isPlayer) noteVibrations.noteStatuses[direction] = NoteStatus.isReleased;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a press animation for a given direction.
|
||||
* @param direction The direction of the note to play the press animation for.
|
||||
*/
|
||||
public function playPress(direction:NoteDirection):Void
|
||||
{
|
||||
getByDirection(direction).playPress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a confirm animation for a given direction.
|
||||
* @param direction The direction of the note to play the confirm animation for.
|
||||
*/
|
||||
public function playConfirm(direction:NoteDirection):Void
|
||||
{
|
||||
getByDirection(direction).playConfirm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a confirm animation for a hold note.
|
||||
* @param direction The direction of the note to play the confirm animation for.
|
||||
*/
|
||||
public function holdConfirm(direction:NoteDirection):Void
|
||||
{
|
||||
getByDirection(direction).holdConfirm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given direction is playing the confirm animation.
|
||||
* @param direction The direction of the note to check.
|
||||
* @return `true` if the note is playing the confirm animation, `false` otherwise.
|
||||
*/
|
||||
public function isConfirm(direction:NoteDirection):Bool
|
||||
{
|
||||
return getByDirection(direction).isConfirm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a note splash for a given direction.
|
||||
* @param direction The direction of the note to play the splash animation for.
|
||||
*/
|
||||
public function playNoteSplash(direction:NoteDirection):Void
|
||||
{
|
||||
if (!showNotesplash) return;
|
||||
@@ -879,6 +1030,10 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a note hold cover for a given hold note.
|
||||
* @param holdNote The hold note to play the cover animation for.
|
||||
*/
|
||||
public function playNoteHoldCover(holdNote:SustainTrail):Void
|
||||
{
|
||||
if (!showNotesplash) return;
|
||||
@@ -909,6 +1064,11 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a note sprite for a given note data.
|
||||
* @param note The note data to build the note sprite for.
|
||||
* @return The note sprite. Will recycle a note sprite from the pool if available for performance.
|
||||
*/
|
||||
public function buildNoteSprite(note:SongNoteData):NoteSprite
|
||||
{
|
||||
var noteSprite:NoteSprite = constructNoteSprite();
|
||||
@@ -938,13 +1098,17 @@ class Strumline extends FlxSpriteGroup
|
||||
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;
|
||||
}
|
||||
|
||||
return noteSprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a hold note sprite for a given note data.
|
||||
* @param note The note data to build the hold note sprite for.
|
||||
* @return The hold note sprite. Will recycle a hold note sprite from the pool if available for performance.
|
||||
*/
|
||||
public function buildHoldNoteSprite(note:SongNoteData):SustainTrail
|
||||
{
|
||||
var holdNoteSprite:SustainTrail = constructHoldNoteSprite();
|
||||
@@ -979,7 +1143,7 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom recycling behavior.
|
||||
* Custom recycling behavior for note splashes.
|
||||
*/
|
||||
function constructNoteSplash():NoteSplash
|
||||
{
|
||||
@@ -1013,7 +1177,7 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom recycling behavior.
|
||||
* Custom recycling behavior for note hold covers.
|
||||
*/
|
||||
function constructNoteHoldCover():NoteHoldCover
|
||||
{
|
||||
@@ -1047,7 +1211,7 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom recycling behavior.
|
||||
* Custom recycling behavior for note sprites.
|
||||
*/
|
||||
function constructNoteSprite():NoteSprite
|
||||
{
|
||||
@@ -1073,7 +1237,7 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom recycling behavior.
|
||||
* Custom recycling behavior for hold note sprites.
|
||||
*/
|
||||
function constructHoldNoteSprite():SustainTrail
|
||||
{
|
||||
@@ -1133,6 +1297,10 @@ class Strumline extends FlxSpriteGroup
|
||||
FlxTween.tween(arrow, {y: arrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * index)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a fade in animation on all arrows in the strumline.
|
||||
* Used when starting a song in Freeplay mode.
|
||||
*/
|
||||
public function fadeInArrows():Void
|
||||
{
|
||||
for (index => arrow in this.strumlineNotes.members.keyValueIterator())
|
||||
@@ -1141,24 +1309,50 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two note data objects by their strumtime.
|
||||
* @param order The order to sort the notes in.
|
||||
* @param a The first note data object.
|
||||
* @param b The second note data object.
|
||||
* @return The comparison result, based on the time of the notes.
|
||||
*/
|
||||
function compareNoteData(order:Int, a:SongNoteData, b:SongNoteData):Int
|
||||
{
|
||||
return FlxSort.byValues(order, a.time, b.time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two note sprites by their strumtime.
|
||||
* @param order The order to sort the notes in.
|
||||
* @param a The first note sprite.
|
||||
* @param b The second note sprite.
|
||||
* @return The comparison result, based on the time of the notes.
|
||||
*/
|
||||
function compareNoteSprites(order:Int, a:NoteSprite, b:NoteSprite):Int
|
||||
{
|
||||
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two hold note sprites by their strumtime.
|
||||
* @param order The order to sort the notes in.
|
||||
* @param a The first hold note sprite.
|
||||
* @param b The second hold note sprite.
|
||||
* @return The comparison result, based on the time of the notes.
|
||||
*/
|
||||
function compareHoldNoteSprites(order:Int, a:SustainTrail, b:SustainTrail):Int
|
||||
{
|
||||
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
||||
}
|
||||
|
||||
override function findMinYHelper()
|
||||
/**
|
||||
* Find the minimum Y position of the strumline.
|
||||
* Ignores the background to ensure the strumline is positioned correctly.
|
||||
* @return The minimum Y position of the strumline.
|
||||
*/
|
||||
override function findMinYHelper():Float
|
||||
{
|
||||
var value = Math.POSITIVE_INFINITY;
|
||||
var value:Float = Math.POSITIVE_INFINITY;
|
||||
for (member in group.members)
|
||||
{
|
||||
if (member == null) continue;
|
||||
@@ -1166,18 +1360,28 @@ class Strumline extends FlxSpriteGroup
|
||||
if (member == this.background) continue;
|
||||
|
||||
var minY:Float;
|
||||
if (member.flixelType == SPRITEGROUP) minY = (cast member : FlxSpriteGroup).findMinY();
|
||||
if (member.flixelType == SPRITEGROUP)
|
||||
{
|
||||
minY = (cast member : FlxSpriteGroup).findMinY();
|
||||
}
|
||||
else
|
||||
{
|
||||
minY = member.y;
|
||||
}
|
||||
|
||||
if (minY < value) value = minY;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
override function findMaxYHelper()
|
||||
/**
|
||||
* Find the maximum Y position of the strumline.
|
||||
* Ignores the background to ensure the strumline is positioned correctly.
|
||||
* @return The maximum Y position of the strumline.
|
||||
*/
|
||||
override function findMaxYHelper():Float
|
||||
{
|
||||
var value = Math.NEGATIVE_INFINITY;
|
||||
var value:Float = Math.NEGATIVE_INFINITY;
|
||||
for (member in group.members)
|
||||
{
|
||||
if (member == null) continue;
|
||||
@@ -1185,9 +1389,14 @@ class Strumline extends FlxSpriteGroup
|
||||
if (member == this.background) continue;
|
||||
|
||||
var maxY:Float;
|
||||
if (member.flixelType == SPRITEGROUP) maxY = (cast member : FlxSpriteGroup).findMaxY();
|
||||
if (member.flixelType == SPRITEGROUP)
|
||||
{
|
||||
maxY = (cast member : FlxSpriteGroup).findMaxY();
|
||||
}
|
||||
else
|
||||
{
|
||||
maxY = member.y + member.height;
|
||||
}
|
||||
|
||||
if (maxY > value) value = maxY;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ class StrumlineNote extends FunkinSprite
|
||||
return this.direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Y Offset of the note.
|
||||
*/
|
||||
public var yOffset:Float = 0.0;
|
||||
|
||||
/**
|
||||
* Set this flag to `true` to disable performance optimizations that cause
|
||||
* the Strumline note sprite to ignore `velocity` and `acceleration`.
|
||||
|
||||
@@ -34,6 +34,11 @@ class SustainTrail extends FlxSprite
|
||||
|
||||
public var cover:NoteHoldCover = null;
|
||||
|
||||
/**
|
||||
* The Y Offset of the note.
|
||||
*/
|
||||
public var yOffset:Float = 0.0;
|
||||
|
||||
/**
|
||||
* Set to `true` if the user hit the note and is currently holding the sustain.
|
||||
* Should display associated effects.
|
||||
|
||||
@@ -25,6 +25,7 @@ import funkin.play.notes.NoteDirection;
|
||||
* MusicBeatState actually represents the core utility FlxState of the game.
|
||||
* It includes functionality for event handling, as well as maintaining BPM-based update events.
|
||||
*/
|
||||
@:nullSafety
|
||||
class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||
{
|
||||
var controls(get, never):Controls;
|
||||
@@ -32,8 +33,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||
inline function get_controls():Controls
|
||||
return PlayerSettings.player1.controls;
|
||||
|
||||
public var leftWatermarkText:FlxText = null;
|
||||
public var rightWatermarkText:FlxText = null;
|
||||
public var leftWatermarkText:Null<FlxText> = null;
|
||||
public var rightWatermarkText:Null<FlxText> = null;
|
||||
|
||||
public var conductorInUse(get, set):Conductor;
|
||||
|
||||
@@ -223,6 +224,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
override function startOutro(onComplete:() -> Void):Void
|
||||
{
|
||||
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
||||
|
||||
@@ -23,10 +23,11 @@ import funkin.play.notes.NoteDirection;
|
||||
/**
|
||||
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
|
||||
*/
|
||||
@:nullSafety
|
||||
class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||
{
|
||||
public var leftWatermarkText:FlxText = null;
|
||||
public var rightWatermarkText:FlxText = null;
|
||||
public var leftWatermarkText:Null<FlxText> = null;
|
||||
public var rightWatermarkText:Null<FlxText> = null;
|
||||
|
||||
public var conductorInUse(get, set):Conductor;
|
||||
|
||||
@@ -245,6 +246,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||
this._parentState.openSubState(substate);
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
override function startOutro(onComplete:() -> Void):Void
|
||||
{
|
||||
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
||||
|
||||
@@ -6,6 +6,7 @@ import funkin.graphics.FlxFilteredSprite;
|
||||
* The icon that gets used for Freeplay capsules and char select
|
||||
* NOT to be confused with the CharIcon class, which is for the in-game icons
|
||||
*/
|
||||
@:nullSafety
|
||||
class PixelatedIcon extends FlxFilteredSprite
|
||||
{
|
||||
public function new(x:Float, y:Float)
|
||||
|
||||
@@ -3,6 +3,7 @@ package funkin.ui;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.MenuList;
|
||||
|
||||
@:nullSafety
|
||||
class TextMenuList extends MenuTypedList<TextMenuItem>
|
||||
{
|
||||
public function new(navControls:NavControls = Vertical, ?wrapMode)
|
||||
@@ -18,6 +19,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
||||
{
|
||||
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void)
|
||||
@@ -27,6 +29,7 @@ class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
||||
}
|
||||
}
|
||||
|
||||
@:nullSafety
|
||||
class TextTypedMenuItem<T:AtlasText> extends MenuTypedItem<T>
|
||||
{
|
||||
public function new(x = 0.0, y = 0.0, label:T, name:String, ?callback:Void->Void)
|
||||
|
||||
@@ -518,7 +518,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
grpIcons.scrollFactor.set();
|
||||
}
|
||||
|
||||
function unLock()
|
||||
function unLock():Void
|
||||
{
|
||||
var index = nonLocks[0];
|
||||
|
||||
|
||||
@@ -18,9 +18,6 @@ class Nametag extends FlxSprite
|
||||
shader = mosaicShader;
|
||||
|
||||
switchChar("bf");
|
||||
|
||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(Nametag, ["midpointX", "midpointY"]));
|
||||
FlxG.debugger.track(this, "Nametag");
|
||||
}
|
||||
|
||||
public function updatePosition():Void
|
||||
@@ -71,16 +68,12 @@ class Nametag extends FlxSprite
|
||||
}
|
||||
}
|
||||
|
||||
function setBlockTimer(frame:Int, ?forceX:Float, ?forceY:Float)
|
||||
function setBlockTimer(frame:Int, ?forceX:Float, ?forceY:Float):Void
|
||||
{
|
||||
var daX:Float = 10 * FlxG.random.int(1, 4);
|
||||
var daY:Float = 10 * FlxG.random.int(1, 4);
|
||||
var daX:Float = forceX ?? 10 * FlxG.random.int(1, 4);
|
||||
var daY:Float = forceY ?? 10 * FlxG.random.int(1, 4);
|
||||
|
||||
if (forceX != null) daX = forceX;
|
||||
|
||||
if (forceY != null) daY = forceY;
|
||||
|
||||
new FlxTimer().start(frame / 30, _ -> {
|
||||
FlxTimer.wait(frame / 30, () -> {
|
||||
mosaicShader.setBlockSize(daX, daY);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -528,8 +528,11 @@ class DebugBoundingState extends FlxState
|
||||
trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
|
||||
|
||||
offsetAnimationDropdown.onChange = function(event:UIEvent) {
|
||||
trace('Selected animation ${event?.data?.id}');
|
||||
playCharacterAnimation(event.data.id, true);
|
||||
if (event.data != null)
|
||||
{
|
||||
trace('Selected animation ${event.data.id}');
|
||||
playCharacterAnimation(event.data.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||
|
||||
@@ -22,9 +22,9 @@ class FlxAnimateTest extends MusicBeatState
|
||||
{
|
||||
super.create();
|
||||
|
||||
sprite = new FlxAtlasSprite(0, 0, 'assets/images/charSelect/maskTest');
|
||||
sprite = new FlxAtlasSprite(-100, 0, 'week3:assets/week3/images/philly/erect/picoFlowers');
|
||||
add(sprite);
|
||||
sprite.playAnimation(null, false, false, true);
|
||||
sprite.playAnimation(sprite.anim.stageInstance.symbol.name, false, false, true);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
|
||||
@@ -2361,7 +2361,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileUtil.doesFileExist(chartPath))
|
||||
if (!FileUtil.fileExists(chartPath))
|
||||
{
|
||||
trace('Previously loaded chart file (${chartPath.toString()}) does not exist, disabling link...');
|
||||
menuItemRecentChart.disabled = true;
|
||||
@@ -5118,6 +5118,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
|
||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}.${songPosMilliseconds}';
|
||||
|
||||
var roundedBeat = FlxMath.roundDecimal(Conductor.instance.currentBeatTime, 2);
|
||||
var parts:Array<String> = Std.string(roundedBeat).split('.');
|
||||
if (parts.length == 1) parts.push('00');
|
||||
else if (parts[1].length < 2) parts[1] += '0';
|
||||
songPosString += ' | Beat: ${parts.join('.')} | Step: ${Conductor.instance.currentStep}';
|
||||
|
||||
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||
|
||||
var songRemaining:Float = Math.max(songLengthInMs - songPos, 0.0);
|
||||
|
||||
@@ -106,7 +106,7 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileUtil.doesFileExist(chartPath))
|
||||
if (!FileUtil.fileExists(chartPath))
|
||||
{
|
||||
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
||||
linkRecentChart.disabled = true;
|
||||
|
||||
@@ -55,6 +55,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
}
|
||||
|
||||
var eventType:String = event.data.id;
|
||||
var sameEvent:Bool = (eventType == chartEditorState.eventKindToPlace);
|
||||
|
||||
trace('ChartEditorEventDataToolbox - Event type changed: $eventType');
|
||||
|
||||
@@ -69,6 +70,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sameEvent) chartEditorState.eventDataToPlace = {};
|
||||
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace);
|
||||
|
||||
if (!_initializing && chartEditorState.currentEventSelection.length > 0)
|
||||
@@ -84,15 +86,21 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
chartEditorState.notePreviewDirty = true;
|
||||
}
|
||||
}
|
||||
toolboxEventsEventKind.pauseEvent(UIEvent.CHANGE, true);
|
||||
|
||||
var startingEventValue = ChartEditorDropdowns.populateDropdownWithSongEvents(toolboxEventsEventKind, chartEditorState.eventKindToPlace);
|
||||
trace('ChartEditorEventDataToolbox - Starting event kind: ${startingEventValue}');
|
||||
toolboxEventsEventKind.value = startingEventValue;
|
||||
|
||||
toolboxEventsEventKind.resumeEvent(UIEvent.CHANGE, true, true);
|
||||
}
|
||||
|
||||
public override function refresh():Void
|
||||
{
|
||||
super.refresh();
|
||||
|
||||
toolboxEventsEventKind.pauseEvent(UIEvent.CHANGE, true);
|
||||
|
||||
var newDropdownElement = ChartEditorDropdowns.findDropdownElement(chartEditorState.eventKindToPlace, toolboxEventsEventKind);
|
||||
|
||||
if (newDropdownElement == null)
|
||||
@@ -151,6 +159,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toolboxEventsEventKind.resumeEvent(UIEvent.CHANGE, true, true);
|
||||
}
|
||||
|
||||
var lastEventKind:String = 'unknown';
|
||||
@@ -165,8 +175,6 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
// Clear the frame.
|
||||
target.removeAllComponents();
|
||||
|
||||
chartEditorState.eventDataToPlace = {};
|
||||
|
||||
for (field in schema)
|
||||
{
|
||||
if (field == null) continue;
|
||||
@@ -259,7 +267,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
var value = event.target.value;
|
||||
if (field.type == ENUM)
|
||||
{
|
||||
value = event.target.value.value;
|
||||
var drp:DropDown = cast event.target;
|
||||
value = drp.selectedItem?.value ?? field.defaultValue;
|
||||
}
|
||||
else if (field.type == BOOL)
|
||||
{
|
||||
|
||||
@@ -1073,7 +1073,7 @@ class StageEditorState extends UIState
|
||||
var filePath = new haxe.io.Path(file);
|
||||
var item = new MenuItem();
|
||||
item.text = filePath.file + "." + filePath.ext;
|
||||
item.disabled = !FileUtil.doesFileExist(file);
|
||||
item.disabled = !FileUtil.fileExists(file);
|
||||
|
||||
var load = function(file:String) {
|
||||
currentFile = file;
|
||||
|
||||
@@ -32,7 +32,7 @@ class BackupAvailableDialog extends Dialog
|
||||
{
|
||||
super();
|
||||
|
||||
if (!FileUtil.doesFileExist(filePath)) return;
|
||||
if (!FileUtil.fileExists(filePath)) return;
|
||||
|
||||
// time text
|
||||
var fileDate = Path.withoutExtension(Path.withoutDirectory(filePath));
|
||||
@@ -62,7 +62,7 @@ class BackupAvailableDialog extends Dialog
|
||||
}
|
||||
|
||||
buttonOpenBackup.onClick = function(_) {
|
||||
if (FileUtil.doesFileExist(filePath) && state.welcomeDialog != null) // doing a check in case a sleezy FUCK decides to delete the backup file AFTER dialog opens
|
||||
if (FileUtil.fileExists(filePath) && state.welcomeDialog != null) // doing a check in case a sleezy FUCK decides to delete the backup file AFTER dialog opens
|
||||
{
|
||||
state.welcomeDialog.loadFromFilePath(filePath);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class WelcomeDialog extends Dialog
|
||||
{
|
||||
trace(file);
|
||||
|
||||
if (!FileUtil.doesFileExist(file)) continue; // whats the point of loading something that doesnt exist
|
||||
if (!FileUtil.fileExists(file)) continue; // whats the point of loading something that doesnt exist
|
||||
|
||||
var patj = new haxe.io.Path(file);
|
||||
|
||||
|
||||
@@ -91,20 +91,16 @@ class AlbumRoll extends FlxSpriteGroup
|
||||
if (albumId == null)
|
||||
{
|
||||
this.visible = false;
|
||||
difficultyStars.stars.visible = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
albumData = AlbumRegistry.instance.fetchEntry(albumId);
|
||||
|
||||
if (albumData == null)
|
||||
{
|
||||
FlxG.log.warn('Could not find album data for album ID: ${albumId}');
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -113,9 +109,7 @@ class AlbumRoll extends FlxSpriteGroup
|
||||
newAlbumArt.replaceFrameGraphic(0, albumGraphic);
|
||||
|
||||
buildAlbumTitle(albumData.getAlbumTitleAssetKey(), albumData.getAlbumTitleOffsets());
|
||||
|
||||
applyExitMovers();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
|
||||
var dumbNumb = Std.parseInt(Std.string(val));
|
||||
var prevNum:ScoreNum;
|
||||
|
||||
dumbNumb = Std.int(Math.min(dumbNumb, Math.pow(10, group.members.length) - 1));
|
||||
|
||||
while (dumbNumb > 0)
|
||||
{
|
||||
group.members[loopNum].digit = dumbNumb % 10;
|
||||
|
||||
@@ -236,6 +236,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
|
||||
currentCharacter = fetchPlayableCharacter();
|
||||
currentVariation = rememberedVariation;
|
||||
currentDifficulty = rememberedDifficulty;
|
||||
styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacter.getFreeplayStyleID());
|
||||
rememberedCharacterId = currentCharacter?.id ?? Constants.DEFAULT_CHARACTER;
|
||||
fromCharSelect = params?.fromCharSelect ?? false;
|
||||
@@ -798,7 +799,6 @@ class FreeplayState extends MusicBeatSubState
|
||||
|
||||
// Gets all available difficulties for our character, via our available variations
|
||||
var difficultiesAvailable:Array<String> = song.data.listDifficulties(null, characterVariations);
|
||||
|
||||
return difficultiesAvailable.contains(currentDifficulty);
|
||||
});
|
||||
|
||||
|
||||
@@ -24,11 +24,12 @@ import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.util.TouchUtil;
|
||||
import funkin.api.newgrounds.Referral;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.NG;
|
||||
import funkin.api.newgrounds.NewgroundsClient;
|
||||
#end
|
||||
|
||||
class MainMenuState extends MusicBeatState
|
||||
@@ -181,9 +182,9 @@ class MainMenuState extends MusicBeatState
|
||||
this.leftWatermarkText.text = Constants.VERSION;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NG.core?.loggedIn)
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
this.leftWatermarkText.text += ' | Newgrounds: Logged in as ${NG.core?.user?.name}';
|
||||
this.leftWatermarkText.text += ' | Newgrounds: Logged in as ${NewgroundsClient.instance.user?.name}';
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -252,18 +253,7 @@ class MainMenuState extends MusicBeatState
|
||||
|
||||
function selectMerch()
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS
|
||||
NG.core?.calls.loader.loadReferral(false)
|
||||
.addComponentParameter("referral_name", "merch_link")
|
||||
.addResponseHandler(response -> {
|
||||
if (response.success) WindowUtil.openURL(response.result.data.url)
|
||||
else
|
||||
WindowUtil.openURL(Constants.URL_MERCH_FALLBACK);
|
||||
})
|
||||
.send();
|
||||
#else
|
||||
WindowUtil.openURL(Constants.URL_MERCH_FALLBACK);
|
||||
#end
|
||||
Referral.doMerchReferral();
|
||||
}
|
||||
#end
|
||||
|
||||
|
||||
+905
-143
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,11 @@ class WindowUtil
|
||||
*/
|
||||
public static function openURL(targetUrl:String):Void
|
||||
{
|
||||
// Ensure you can't open protocols such as steam://, file://, etc
|
||||
var protocol:Array<String> = targetUrl.split("://");
|
||||
if (protocol.length == 1) targetUrl = 'https://${targetUrl}';
|
||||
else if (protocol[0] != 'http' && protocol[0] != 'https') throw "openURL can only open http and https links.";
|
||||
|
||||
#if FEATURE_OPEN_URL
|
||||
#if linux
|
||||
Sys.command('/usr/bin/xdg-open $targetUrl &');
|
||||
@@ -64,6 +69,10 @@ class WindowUtil
|
||||
*/
|
||||
public static function openFolder(targetPath:String):Void
|
||||
{
|
||||
#if html5
|
||||
throw 'Cannot open URLs on this platform.';
|
||||
#else
|
||||
if (!sys.FileSystem.exists(targetPath) || !sys.FileSystem.isDirectory(targetPath)) throw 'openFolder should only be used to open existing folders.';
|
||||
#if FEATURE_OPEN_URL
|
||||
#if windows
|
||||
Sys.command('explorer', [targetPath.replace('/', '\\')]);
|
||||
@@ -75,6 +84,7 @@ class WindowUtil
|
||||
#else
|
||||
throw 'Cannot open URLs on this platform.';
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +93,9 @@ class WindowUtil
|
||||
*/
|
||||
public static function openSelectFile(targetPath:String):Void
|
||||
{
|
||||
#if html5
|
||||
throw 'Cannot open URLs on this platform.';
|
||||
#else
|
||||
#if FEATURE_OPEN_URL
|
||||
#if windows
|
||||
Sys.command('explorer', ['/select,' + targetPath.replace('/', '\\')]);
|
||||
@@ -95,6 +108,7 @@ class WindowUtil
|
||||
#else
|
||||
throw 'Cannot open URLs on this platform.';
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,6 @@ import haxe.macro.Expr.TypeDefKind;
|
||||
import haxe.macro.Expr.MetadataEntry;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Type.ClassType;
|
||||
import sys.FileSystem;
|
||||
|
||||
using Lambda;
|
||||
using haxe.macro.ExprTools;
|
||||
@@ -366,7 +365,7 @@ class RegistryMacro
|
||||
static function listBaseGameEntryIds(dataFilePath:String):Array<Expr>
|
||||
{
|
||||
var result:Array<Expr> = [];
|
||||
var files:Array<String> = FileSystem.readDirectory(dataFilePath);
|
||||
var files:Array<String> = sys.FileSystem.readDirectory(dataFilePath);
|
||||
|
||||
for (file in files)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ package funkin.util.plugins;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxState;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
@@ -12,7 +11,6 @@ import flixel.util.FlxTimer;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import flixel.addons.util.FlxAsyncLoop;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.display.Bitmap;
|
||||
@@ -23,39 +21,44 @@ import openfl.geom.Rectangle;
|
||||
import openfl.utils.ByteArray;
|
||||
import openfl.events.MouseEvent;
|
||||
import funkin.Preferences;
|
||||
import flixel.addons.plugin.screengrab.FlxScreenGrab;
|
||||
|
||||
/**
|
||||
* Parameters for starting the `ScreenshotPlugin`.
|
||||
*/
|
||||
typedef ScreenshotPluginParams =
|
||||
{
|
||||
?region:Rectangle,
|
||||
flashColor:Null<FlxColor>,
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Contribute this upstream.
|
||||
* A Flixel plugin, which renders on top of the game, and provides the functionality
|
||||
* and visual feedback for taking screenshots.
|
||||
*/
|
||||
@:nullSafety
|
||||
class ScreenshotPlugin extends FlxBasic
|
||||
{
|
||||
/**
|
||||
* Current `ScreenshotPlugin` instance
|
||||
*/
|
||||
public static var instance:ScreenshotPlugin = null;
|
||||
public static var instance(get, never):ScreenshotPlugin;
|
||||
|
||||
public static final SCREENSHOT_FOLDER = 'screenshots';
|
||||
static var _instance:Null<ScreenshotPlugin> = null;
|
||||
|
||||
var region:Null<Rectangle>;
|
||||
static function get_instance():ScreenshotPlugin
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new ScreenshotPlugin({});
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color used for the flash
|
||||
* The folder where screenshots are saved
|
||||
*/
|
||||
public static var flashColor(default, set):Int = 0xFFFFFFFF;
|
||||
public static final SCREENSHOT_FOLDER:String = 'screenshots';
|
||||
|
||||
public static function set_flashColor(v:Int):Int
|
||||
{
|
||||
flashColor = v;
|
||||
if (instance != null && instance.flashBitmap != null) instance.flashBitmap.bitmapData = new BitmapData(lastWidth, lastHeight, true, v);
|
||||
return flashColor;
|
||||
}
|
||||
var region:Null<Rectangle>;
|
||||
|
||||
/**
|
||||
* A signal fired before the screenshot is taken.
|
||||
@@ -68,8 +71,8 @@ class ScreenshotPlugin extends FlxBasic
|
||||
*/
|
||||
public var onPostScreenshot(default, null):FlxTypedSignal<Bitmap->Void>;
|
||||
|
||||
private static var lastWidth:Int;
|
||||
private static var lastHeight:Int;
|
||||
static var lastWidth:Int = 0;
|
||||
static var lastHeight:Int = 0;
|
||||
|
||||
var flashSprite:Sprite;
|
||||
var flashBitmap:Bitmap;
|
||||
@@ -83,7 +86,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
|
||||
var screenshotBeingSpammed:Bool = false;
|
||||
|
||||
var screenshotSpammedTimer:FlxTimer;
|
||||
var screenshotSpammedTimer:Null<FlxTimer> = null;
|
||||
|
||||
var screenshotBuffer:Array<Bitmap> = [];
|
||||
var screenshotNameBuffer:Array<String> = [];
|
||||
@@ -94,31 +97,23 @@ class ScreenshotPlugin extends FlxBasic
|
||||
var stateChanging:Bool = false;
|
||||
var noSavingScreenshots:Bool = false;
|
||||
|
||||
var flashTween:FlxTween;
|
||||
var flashTween:Null<FlxTween> = null;
|
||||
|
||||
var previewFadeInTween:FlxTween;
|
||||
var previewFadeOutTween:FlxTween;
|
||||
var previewFadeInTween:Null<FlxTween> = null;
|
||||
var previewFadeOutTween:Null<FlxTween> = null;
|
||||
|
||||
var asyncLoop:FlxAsyncLoop;
|
||||
var asyncLoop:Null<FlxAsyncLoop> = null;
|
||||
|
||||
public function new(params:ScreenshotPluginParams)
|
||||
{
|
||||
super();
|
||||
|
||||
if (instance != null)
|
||||
{
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
instance = this;
|
||||
|
||||
lastWidth = FlxG.width;
|
||||
lastHeight = FlxG.height;
|
||||
|
||||
flashSprite = new Sprite();
|
||||
flashSprite.alpha = 0;
|
||||
flashBitmap = new Bitmap(new BitmapData(lastWidth, lastHeight, true, params.flashColor));
|
||||
flashBitmap = new Bitmap(new BitmapData(lastWidth, lastHeight, true, Preferences.flashingLights ? FlxColor.WHITE : FlxColor.TRANSPARENT));
|
||||
flashSprite.addChild(flashBitmap);
|
||||
|
||||
previewSprite = new Sprite();
|
||||
@@ -137,7 +132,6 @@ class ScreenshotPlugin extends FlxBasic
|
||||
FlxG.stage.addChild(flashSprite);
|
||||
|
||||
region = params.region ?? null;
|
||||
flashColor = params.flashColor;
|
||||
|
||||
onPreScreenshot = new FlxTypedSignal<Void->Void>();
|
||||
onPostScreenshot = new FlxTypedSignal<Bitmap->Void>();
|
||||
@@ -205,7 +199,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
sprite.alpha = 0;
|
||||
}
|
||||
// screenshot spamming timer
|
||||
if (screenshotSpammedTimer == null || screenshotSpammedTimer.finished == true)
|
||||
if (screenshotSpammedTimer == null || screenshotSpammedTimer.finished)
|
||||
{
|
||||
screenshotSpammedTimer = new FlxTimer().start(1, function(_) {
|
||||
// The player's stopped spamming shots, so we can stop the screenshot spam mode too
|
||||
@@ -242,12 +236,12 @@ class ScreenshotPlugin extends FlxBasic
|
||||
*/
|
||||
public static function initialize():Void
|
||||
{
|
||||
FlxG.plugins.addPlugin(new ScreenshotPlugin(
|
||||
{
|
||||
flashColor: Preferences.flashingLights ? FlxColor.WHITE : null, // Was originally a black flash.
|
||||
}));
|
||||
FlxG.plugins.addPlugin(new ScreenshotPlugin({}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if the screenshot key is currently pressed.
|
||||
*/
|
||||
public function hasPressedScreenshot():Bool
|
||||
{
|
||||
#if FEATURE_SCREENSHOTS
|
||||
@@ -257,16 +251,11 @@ class ScreenshotPlugin extends FlxBasic
|
||||
#end
|
||||
}
|
||||
|
||||
public function updateFlashColor():Void
|
||||
{
|
||||
Preferences.flashingLights ? set_flashColor(FlxColor.WHITE) : null;
|
||||
}
|
||||
|
||||
private function resizeBitmap(width:Int, height:Int)
|
||||
function resizeBitmap(width:Int, height:Int):Void
|
||||
{
|
||||
lastWidth = width;
|
||||
lastHeight = height;
|
||||
flashBitmap.bitmapData = new BitmapData(lastWidth, lastHeight, true, flashColor);
|
||||
flashBitmap.bitmapData = new BitmapData(lastWidth, lastHeight, true, Preferences.flashingLights ? FlxColor.WHITE : FlxColor.TRANSPARENT);
|
||||
outlineBitmap.bitmapData = new BitmapData(Std.int(lastWidth / 5) + 10, Std.int(lastHeight / 5) + 10, true, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
@@ -277,8 +266,8 @@ class ScreenshotPlugin extends FlxBasic
|
||||
{
|
||||
onPreScreenshot.dispatch();
|
||||
|
||||
var shot = new Bitmap(BitmapData.fromImage(FlxG.stage.window.readPixels()));
|
||||
if (screenshotBeingSpammed == true)
|
||||
var shot:Bitmap = new Bitmap(BitmapData.fromImage(FlxG.stage.window.readPixels()));
|
||||
if (screenshotBeingSpammed)
|
||||
{
|
||||
// Save the screenshots to the buffer instead
|
||||
if (screenshotBuffer.length < 100)
|
||||
@@ -318,7 +307,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
onPostScreenshot.dispatch(shot);
|
||||
}
|
||||
|
||||
final CAMERA_FLASH_DURATION = 0.25;
|
||||
static final CAMERA_FLASH_DURATION:Float = 0.25;
|
||||
|
||||
/**
|
||||
* Visual and audio feedback when a screenshot is taken.
|
||||
@@ -332,10 +321,10 @@ class ScreenshotPlugin extends FlxBasic
|
||||
FunkinSound.playOnce(Paths.sound('screenshot'), 1.0);
|
||||
}
|
||||
|
||||
static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in.
|
||||
static final PREVIEW_FADE_IN_DURATION = 0.3; // How long the preview takes to fade in.
|
||||
static final PREVIEW_FADE_OUT_DELAY = 1.25; // How long the preview stays on screen.
|
||||
static final PREVIEW_FADE_OUT_DURATION = 0.3; // How long the preview takes to fade out.
|
||||
static final PREVIEW_INITIAL_DELAY:Float = 0.25; // How long before the preview starts fading in.
|
||||
static final PREVIEW_FADE_IN_DURATION:Float = 0.3; // How long the preview takes to fade in.
|
||||
static final PREVIEW_FADE_OUT_DELAY:Float = 1.25; // How long the preview stays on screen.
|
||||
static final PREVIEW_FADE_OUT_DURATION:Float = 0.3; // How long the preview takes to fade out.
|
||||
|
||||
/**
|
||||
* Show a fancy preview for the screenshot
|
||||
@@ -367,12 +356,12 @@ class ScreenshotPlugin extends FlxBasic
|
||||
|
||||
// fuck it, cursed locally scoped functions, purely because im lazy
|
||||
// (and so we can check changingAlpha, which is locally scoped.... because I'm lazy...)
|
||||
var onHover = function(e:MouseEvent) {
|
||||
var onHover:MouseEvent->Void = function(e:MouseEvent) {
|
||||
if (!changingAlpha) e.target.alpha = 0.6;
|
||||
targetAlpha = 0.6;
|
||||
};
|
||||
|
||||
var onHoverOut = function(e:MouseEvent) {
|
||||
var onHoverOut:MouseEvent->Void = function(e:MouseEvent) {
|
||||
if (!changingAlpha) e.target.alpha = 1;
|
||||
targetAlpha = 1;
|
||||
}
|
||||
@@ -461,7 +450,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
|
||||
static function getCurrentState():FlxState
|
||||
{
|
||||
var state = FlxG.state;
|
||||
var state:FlxState = FlxG.state;
|
||||
while (state.subState != null)
|
||||
{
|
||||
state = state.subState;
|
||||
@@ -484,12 +473,12 @@ class ScreenshotPlugin extends FlxBasic
|
||||
*/
|
||||
function encode(bitmap:Bitmap):ByteArray
|
||||
{
|
||||
var compressor = returnEncoder(Preferences.saveFormat);
|
||||
var compressor:PNGEncoderOptions = new PNGEncoderOptions();
|
||||
return bitmap.bitmapData.encode(bitmap.bitmapData.rect, compressor);
|
||||
}
|
||||
|
||||
var previousScreenshotName:String;
|
||||
var previousScreenshotCopyNum:Int;
|
||||
var previousScreenshotName:Null<String> = null;
|
||||
var previousScreenshotCopyNum:Int = 0;
|
||||
|
||||
/**
|
||||
* Save the generated bitmap to a file.
|
||||
@@ -498,7 +487,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
* @param screenShotNum Used for the delay save option, to space out the saving of the images.
|
||||
* @param delaySave If true, the image gets saved with the screenShotNum as the delay.
|
||||
*/
|
||||
function saveScreenshot(bitmap:Bitmap, targetPath = "image", screenShotNum:Int = 0, delaySave:Bool = true)
|
||||
function saveScreenshot(bitmap:Bitmap, targetPath = "image", screenShotNum:Int = 0, delaySave:Bool = true):Void
|
||||
{
|
||||
makeScreenshotPath();
|
||||
// Check that we're not overriding a previous image, and keep making a unique path until we can
|
||||
@@ -520,13 +509,14 @@ class ScreenshotPlugin extends FlxBasic
|
||||
targetPath = getScreenshotPath() + newTargetPath + '.' + Std.string(Preferences.saveFormat).toLowerCase();
|
||||
}
|
||||
|
||||
// TODO: Make this work on browser.
|
||||
// TODO: Make screenshot saving work on browser.
|
||||
// Maybe save the images into a buffer that you can download as a zip or something? That'd work
|
||||
// Shouldn't be too hard to do something similar to the chart editor saving
|
||||
|
||||
if (delaySave) // Save the images with a delay (a timer)
|
||||
if (delaySave)
|
||||
{ // Save the images with a delay (a timer)
|
||||
new FlxTimer().start(screenShotNum, function(_) {
|
||||
var pngData = encode(bitmap);
|
||||
var pngData:ByteArray = encode(bitmap);
|
||||
|
||||
if (pngData == null)
|
||||
{
|
||||
@@ -547,9 +537,10 @@ class ScreenshotPlugin extends FlxBasic
|
||||
if (Preferences.previewOnSave) showFancyPreview(bitmap); // Only show the preview after a screenshot is saved
|
||||
}
|
||||
});
|
||||
}
|
||||
else // Save the screenshot immediately
|
||||
{
|
||||
var pngData = encode(bitmap);
|
||||
var pngData:ByteArray = encode(bitmap);
|
||||
|
||||
if (pngData == null)
|
||||
{
|
||||
@@ -567,7 +558,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
}
|
||||
|
||||
// I' m very happy with this code, all of it just works
|
||||
function saveBufferedScreenshots(screenshots:Array<Bitmap>, screenshotNames)
|
||||
function saveBufferedScreenshots(screenshots:Array<Bitmap>, screenshotNames:Array<String>):Void
|
||||
{
|
||||
trace('Saving screenshot buffer');
|
||||
var i:Int = 0;
|
||||
@@ -581,14 +572,16 @@ class ScreenshotPlugin extends FlxBasic
|
||||
}, 1);
|
||||
getCurrentState().add(asyncLoop);
|
||||
if (!Preferences.flashingLights && !Preferences.previewOnSave)
|
||||
{
|
||||
showFancyPreview(screenshots[screenshots.length - 1]); // show the preview for the last screenshot
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the above function, but cancels the tweens, undos the mouse
|
||||
* and doesn't have the async loop because this is called before the state changes
|
||||
*/
|
||||
function saveUnsavedBufferedScreenshots()
|
||||
function saveUnsavedBufferedScreenshots():Void
|
||||
{
|
||||
stateChanging = true;
|
||||
// Cancel the tweens of the capture feedback if they're running
|
||||
@@ -627,17 +620,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
unsavedScreenshotNameBuffer = [];
|
||||
}
|
||||
|
||||
public function returnEncoder(saveFormat:String):Any
|
||||
{
|
||||
return switch (saveFormat)
|
||||
{
|
||||
// JPEG encoder causes the game to crash?????
|
||||
// case "JPEG": new openfl.display.JPEGEncoderOptions(Preferences.jpegQuality);
|
||||
default: new openfl.display.PNGEncoderOptions();
|
||||
}
|
||||
}
|
||||
|
||||
function postStateSwitch()
|
||||
function postStateSwitch():Void
|
||||
{
|
||||
stateChanging = false;
|
||||
screenshotBeingSpammed = false;
|
||||
@@ -646,7 +629,7 @@ class ScreenshotPlugin extends FlxBasic
|
||||
|
||||
override public function destroy():Void
|
||||
{
|
||||
if (instance == this) instance = null;
|
||||
if (instance == this) _instance = null;
|
||||
|
||||
if (FlxG.plugins.list.contains(this)) FlxG.plugins.remove(this);
|
||||
|
||||
@@ -663,13 +646,18 @@ class ScreenshotPlugin extends FlxBasic
|
||||
|
||||
@:privateAccess
|
||||
for (parent in [flashSprite, previewSprite])
|
||||
{
|
||||
if (parent == null) continue;
|
||||
for (child in parent.__children)
|
||||
{
|
||||
parent.removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
flashSprite = null;
|
||||
flashBitmap = null;
|
||||
previewSprite = null;
|
||||
shotPreviewBitmap = null;
|
||||
outlineBitmap = null;
|
||||
// flashSprite = null
|
||||
// flashBitmap = null
|
||||
// previewSprite = null
|
||||
// shotPreviewBitmap = null
|
||||
// outlineBitmap = null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user