Merge branch 'rewrite/master' into funkin-mobile/main

This commit is contained in:
EliteMasterEric
2025-04-28 13:49:40 -04:00
54 changed files with 1653 additions and 430 deletions
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 }}
+2 -1
View File
@@ -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:
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+18 -5
View File
@@ -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
View File
@@ -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.
+3 -3
View File
@@ -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
View File
@@ -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.
+1
View File
@@ -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
{
/**
+4 -2
View File
@@ -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
+55
View File
@@ -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])
+1
View File
@@ -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
{
/**
+1
View File
@@ -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
+3
View File
@@ -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`
+21 -6
View File
@@ -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;
}
+2
View File
@@ -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;
+22 -6
View File
@@ -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)
+5
View File
@@ -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.
*/
+257 -48
View File
@@ -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`.
+5
View File
@@ -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.
+4 -2
View File
@@ -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);
+4 -2
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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];
+4 -11
View File
@@ -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);
-6
View 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;
+1 -1
View File
@@ -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);
});
+5 -15
View File
@@ -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
File diff suppressed because it is too large Load Diff
+14
View File
@@ -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
}
/**
+1 -2
View File
@@ -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)
{
+70 -82
View File
@@ -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
}
}