1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-09-01 19:32:22 +00:00

Compare commits

..

10 commits

Author SHA1 Message Date
anysad 62419fbe69
Merge 2c5c196cdc into c2eff142bd 2025-07-24 08:31:47 -05:00
Lasercar c2eff142bd Bunker infiltrated 2025-07-22 14:45:47 -07:00
Eric 2784fa18c0
Merge pull request #5476 from Trofem/api-version-example-mods-for-0.7.0
[CHORE] Updates example_mods's API version up to 0.7.0
2025-07-22 12:03:19 -04:00
Eric ad07fddf89
Merge pull request #5489 from FunkinCrew/main
Update develop to latest main
2025-07-22 11:59:29 -04:00
trofim.al 4768eedd5b api version 0.7.0
Update example mod's API version so it actually loads. part 2.
2025-07-22 15:04:20 +11:00
Hyper_ f1c3e99a11 lasercar................................................................................................................................................................................................. 2025-07-22 07:56:29 +08:00
Hyper_ eefe8927c4 The most deranged line loss of all time 2025-07-21 17:43:55 -05:00
cherry 3ff4f14510 Clear key from correct map 2025-07-21 17:13:42 -05:00
Abnormal 955b0db542 THE REALLY COOL STUTTERING FIX!!!!!!!!!!!! 2025-07-21 15:06:19 -05:00
Eric d2df4f0832
Merge pull request #5440 from FunkinCrew/main
Update develop branch
2025-07-21 13:08:44 -04:00
201 changed files with 2102 additions and 4061 deletions

View file

@ -53,8 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- You can import them in another script as usual.
- Added support for renaming imported classes using the `as` keyword. (Thanks KoloInDaCrib!)
- Fixed `try`/`catch` blocks not working properly. (Thanks NotHyper-474!)
- Fixed null-safe field access not working properly for functions (ex. `class?.someFunction()`). (Thanks KoloInDaCrib!)
- Fixed Linux being case-sensitive with filenames. (Thanks mikolka9144!)
- Fixed null-safe field access not working properly for functions (ex. `class?.someFunction()). (Thanks KoloInDaCrib!)
### Fixed
@ -89,7 +88,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* @Smokey555 made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
* @CCobaltDev made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
* @mikolka9144 made their first contribution in [polymod#212](https://github.com/larsiusprime/polymod/pull/212)
@ -707,7 +705,6 @@ This patch resolves a critical issue that could cause user's save data to become
## [0.5.0] - 2024-09-12
The Playable Pico Update!
### Added
- Added a new Character Select screen to switch between playable characters in Freeplay.
@ -828,7 +825,7 @@ The Playable Pico Update!
### Fixed
- Control binds in the controls menu no longer overlap their names.
- Attempting to exit the gameover screen and retry the song at the same time no longer crashes the game. ([thanks DM-kun for the PR!](https://github.com/FunkinCrew/Funkin/pull/2709))
- Attempting to exit the gameover screen and retry the song at the same time no longer crashes the game. ([thanks DMMaster636 for the PR!](https://github.com/FunkinCrew/Funkin/pull/2709))
- Botplay mode now handles the player's animations properly during hold notes. ([thanks Hundrec!](https://github.com/FunkinCrew/Funkin/pull/2683))
- Camera movement now pauses when the game is paused. ([thanks Matriculaso!](https://github.com/FunkinCrew/Funkin/pull/2684))
- Pico's gameplay sprite no longer appears on the gameover screen when dying from an explosion in 2hot.
@ -842,14 +839,13 @@ The Playable Pico Update!
## New Contributors for 0.4.1
* @Hundrec made their first contribution in [#2661](https://github.com/FunkinCrew/Funkin/pull/2661)
* @DM-kun made their first contribution in [#2709](https://github.com/FunkinCrew/Funkin/pull/2709)
* @DMMaster636 made their first contribution in [#2709](https://github.com/FunkinCrew/Funkin/pull/2709)
* @eltociear made their first contribution in [#2730](https://github.com/FunkinCrew/Funkin/pull/2730)
## [0.4.0] - 2024-06-06
The Pit Stop 1 update!
### Added
- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu!
@ -1021,7 +1017,6 @@ The Pit Stop 1 update!
## [0.3.0] - 2024-04-30
The Weekend 1 update!
### Added
- New Story Level: Weekend 1, starring Pico, Darnell, and Nene.

View file

@ -1,25 +1,14 @@
<div align='center'><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Friday_Night_Funkin%27_logo.svg/2560px-Friday_Night_Funkin%27_logo.svg.png" width="800">
# Friday Night Funkin'
<h2>Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for <a href="https://ldjam.com/events/ludum-dare/47">Ludum Dare 47.</a></h2>
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
This game was made with love to Newgrounds and its community. Extra love to Tom Fulp.
</div>
- [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
- [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
- [Download Android builds from Google Play!](https://play.google.com/store/apps/details?id=me.funkin.fnf)
- [Download iOS builds from the App Store!](https://apps.apple.com/app/id6740428530)
<div align='center'>
<table>
<tr>
<td><img src="https://fridaynightfunkin.wiki.gg/images/d/d7/Title_Card.gif" alt="Title Screen" width="350"/></td>
<td><img src="https://fridaynightfunkin.wiki.gg/images/9/99/Menu.png" alt="Main Menu" width="350"/></td>
</tr>
</table>
</div>
# Getting Started
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
@ -63,4 +52,4 @@ Full credits can be found in-game, or in the `credits.json` file which is locate
## Special Thanks
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest

2
art

@ -1 +1 @@
Subproject commit 094ff109197e35d21342bdf2d51132be23948d3f
Subproject commit 67e550dbd22a8ea429eecc8ce078a36f74ea7723

2
assets

@ -1 +1 @@
Subproject commit dcda88cdb094085c10cb3b5c3b04517cd7370ff9
Subproject commit 69c02fa5019f603324a7d2ae362327a1eef9d109

View file

@ -1,119 +0,0 @@
{
"input": "./assets",
"output": "./astc-textures/",
"quality": "medium",
"blocksize": "10x10",
"colorprofile": "cl",
"custom": [
{
"asset": "asset/shared/images/resultScreen/results-pico/resultsPERFECT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/preload/images/NOTE_assets.png",
"blocksize": "4x4"
},
{
"asset": "assets/preload/images/NOTE_hold_assets.png",
"blocksize": "4x4"
},
{
"asset": "assets/preload/images/StrumlineNotes.png",
"blocksize": "4x4"
},
{
"asset": "assets/preload/images/alphabet.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/NOTE_hold_assets.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/holdCoverBlue.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/holdCoverGreen.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/holdCoverPurple.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/holdCoverRed.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/latencyArrow.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/latencyReceptor.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/noteSplashes.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/noteStrumline.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/notes.png",
"blocksize": "4x4"
},
{
"asset": "assets/week1/image/erect/bg.png",
"blocksize": "8x8"
},
{
"asset": "assets/week1/image/erect/lights.png",
"blocksize": "6x6"
},
{
"asset": "assets/week1/images/erect/crowd.png",
"blocksize": "8x8"
},
{
"asset": "assets/week1/images/erect/server.png",
"blocksize": "8x8"
}
],
"excludes": [
"assets/preload/images/cursor/",
"assets/preload/images/fonts/",
"assets/preload/images/freeplay/",
"assets/preload/images/icons/",
"assets/preload/images/soundtray/",
"assets/preload/images/stageBuild/",
"assets/preload/images/titleEnter.png",
"assets/preload/images/titleEnter_mobile.png",
"assets/preload/images/ui/popup/pixel/*",
"assets/shared/images/characters/abotPixel/*",
"assets/shared/images/characters/bfPixel.png",
"assets/shared/images/characters/bfPixelsDEAD.png",
"assets/shared/images/characters/gfPixel.png",
"assets/shared/images/characters/nenePixel/*",
"assets/shared/images/characters/picoPixel/*",
"assets/shared/images/characters/senpai.png",
"assets/shared/images/characters/spirit.png",
"assets/shared/images/healthBar.png",
"assets/shared/images/resultScreen/*",
"assets/shared/images/resultScreen/clearPercent/",
"assets/shared/images/resultScreen/rankText/",
"assets/shared/images/ui/chart-editor/",
"assets/shared/images/ui/countdown/pixel/*",
"assets/week1/*",
"assets/week1/images/erect/brightLightSmall.png",
"assets/week1/images/erect/lightAbove.png",
"assets/week1/images/erect/lightgreen.png",
"assets/week1/images/erect/lightred.png",
"assets/week1/images/erect/orangeLight.png",
"assets/week2/images/erect/stairsDark.png",
"assets/week2/images/erect/stairsLight.png",
"assets/week6/"
]
}

View file

@ -80,7 +80,7 @@
{
"props": {
"ignoreExtern": true,
"format": "^?:_[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
"format": "^[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
"tokens": ["INLINE", "NOTINLINE"]
},
"type": "ConstantName"

21
compression-excludes.txt Normal file
View file

@ -0,0 +1,21 @@
assets/preload/images/cursor/*
assets/preload/images/freeplay/*
assets/preload/images/icons/*
assets/preload/images/titleEnter.png
assets/preload/images/titleEnter_mobile.png
assets/preload/images/soundtray/*
assets/preload/images/stageBuild/*
assets/preload/images/ui/popup/pixel/
assets/shared/images/characters/abotPixel/
assets/shared/images/characters/bfPixel.png
assets/shared/images/characters/bfPixelsDEAD.png
assets/shared/images/characters/gfPixel.png
assets/shared/images/characters/nenePixel/
assets/shared/images/characters/picoPixel/
assets/shared/images/characters/senpai.png
assets/shared/images/characters/spirit.png
assets/shared/images/resultScreen/*
assets/shared/images/ui/chart-editor/*
assets/shared/images/ui/countdown/pixel/
assets/week1/
assets/week6/*

View file

@ -11,41 +11,41 @@
"name": "astc-compressor",
"type": "git",
"dir": null,
"ref": "b7a91b3072dc16e785a9bde847f17ba0ef15dd47",
"ref": "8c9f56927c523df7b849352c6951f04112fe15cc",
"url": "https://github.com/KarimAkra/astc-compressor"
},
{
"name": "extension-admob",
"type": "git",
"dir": null,
"ref": "02334589ff9603a5f483077a44395009644f6274",
"ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1",
"url": "https://github.com/FunkinCrew/extension-admob"
},
{
"name": "extension-androidtools",
"type": "haxelib",
"version": "2.2.2"
"version": "2.2.1"
},
{
"name": "extension-haptics",
"type": "haxelib",
"version": "1.0.4"
"version": "1.0.3"
},
{
"name": "extension-iapcore",
"type": "haxelib",
"version": "1.0.4"
"version": "1.0.3"
},
{
"name": "extension-iarcore",
"type": "haxelib",
"version": "1.0.3"
"version": "1.0.2"
},
{
"name": "flixel",
"type": "git",
"dir": null,
"ref": "ac2a34fe4b3400bc04218f46320a65af0f337fc0",
"ref": "08fc955ca87f192a971719a675f1d3b21709725d",
"url": "https://github.com/FunkinCrew/flixel"
},
{
@ -59,7 +59,7 @@
"name": "flxanimate",
"type": "git",
"dir": null,
"ref": "49214278b9124823582cdcecd94f4a1de9a4b36b",
"ref": "39c1572add28869c558b218fffed13df1b64f376",
"url": "https://github.com/FunkinCrew/flxanimate"
},
{
@ -104,14 +104,14 @@
"name": "hscript",
"type": "git",
"dir": null,
"ref": "8c1d238b069ef97cec13f5121b29d2afe2b8985d",
"ref": "d60bb2947fa609fdc875ccfae89666a6984eeaf2",
"url": "https://github.com/FunkinCrew/hscript"
},
{
"name": "hxcpp",
"type": "git",
"dir": null,
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
"ref": "4e24283a047f11bded6affabbc9ec405156e026e",
"url": "https://github.com/FunkinCrew/hxcpp"
},
{
@ -170,9 +170,37 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "e5f8c27124598505917a001588b560244731adfb",
"ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a",
"url": "https://github.com/FunkinCrew/lime"
},
{
"name": "mconsole",
"type": "git",
"dir": null,
"ref": "06c0499ed8f80628a0e6e55ffa32c3cbd688a838",
"url": "https://github.com/massive-oss/mconsole"
},
{
"name": "mcover",
"type": "git",
"dir": "src",
"ref": "c3c47cd682b0b202a41caee95321989391b617ef",
"url": "https://github.com/massive-oss/mcover"
},
{
"name": "mockatoo",
"type": "git",
"dir": "src",
"ref": "13d77a0a8eaf5e789ef5dae6cd33eee812deda36",
"url": "https://github.com/FunkinCrew/mockatoo"
},
{
"name": "munit",
"type": "git",
"dir": "src",
"ref": "f61be7f7ba796595f45023ca65164a485aba0e7e",
"url": "https://github.com/FunkinCrew/MassiveUnit"
},
{
"name": "newgrounds",
"type": "git",
@ -191,14 +219,14 @@
"name": "polymod",
"type": "git",
"dir": null,
"ref": "55ee00236806330c89c3bff1e1563eb6425a9d96",
"ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1",
"url": "https://github.com/larsiusprime/polymod"
},
{
"name": "thx.core",
"type": "git",
"dir": null,
"ref": "2bf2b992e06159510f595554e6b952e47922f128",
"ref": "76d87418fadd92eb8e1b61f004cff27d656e53dd",
"url": "https://github.com/fponticelli/thx.core"
},
{

View file

@ -1,22 +0,0 @@
# Organizes astc-compression-data alphabetically because i hate it when it's not organized -zack
$InputFile = "./astc-compression-data.json"
$OutputFile = "./astc-compression-data.json"
if (-not (Test-Path $InputFile)) {
Write-Host "❌ File $InputFile not found"
exit 1
}
if (-not (Get-Command jq -ErrorAction SilentlyContinue)) {
Write-Host "jq is not installed. Download from https://jqlang.github.io/jq/ or install via:"
Write-Host " winget install jqlang.jq"
exit 1
}
jq '.
| .custom = ( .custom | sort_by(.asset) )
| .excludes = ( .excludes | sort )
' $InputFile | Out-File -Encoding utf8 $OutputFile
Write-Host "✅ Sorted JSON written to $OutputFile WOOHOOO !!"

View file

@ -1,25 +0,0 @@
#!/bin/bash
# Organizes astc-compression-data alphabetically because i hate it when it's not organized -zack
INPUT_FILE="./astc-compression-data.json"
OUTPUT_FILE="./astc-compression-data.json"
# Check jq
if ! command -v jq &> /dev/null; then
echo "jq is not installed. Install it with:"
echo " macOS: brew install jq"
echo " Linux (Debian/Ubuntu): sudo apt-get install jq"
exit 1
fi
if [ ! -f "$INPUT_FILE" ]; then
echo "❌ File $INPUT_FILE not found"
exit 1
fi
jq '.
| .custom = ( .custom | sort_by(.asset) )
| .excludes = ( .excludes | sort )
' "$INPUT_FILE" > "$OUTPUT_FILE".tmp && mv "$OUTPUT_FILE".tmp "$OUTPUT_FILE"
echo "✅ Sorted JSON written to $OUTPUT_FILE WOOHOOO !!"

View file

@ -5,10 +5,8 @@ import hxp.*;
import lime.tools.*;
import sys.FileSystem;
import sys.io.File;
import haxe.io.Bytes;
import haxe.io.Path;
import haxe.ds.Map;
import haxe.xml.Printer;
using StringTools;
@ -60,11 +58,6 @@ class Project extends HXProject
*/
static final SOURCE_DIR:String = "source";
/**
* The relative location of the templates folder.
*/
static final TEMPLATES_DIR:String = "templates";
/**
* The fully qualified class path for the game's preloader.
* Particularly important on HTML5 but we use it on all platforms.
@ -110,11 +103,30 @@ class Project extends HXProject
static final ANDROID_EXTENSIONS:Array<String> = ["funkin.extensions.CallbackUtil"];
//
// ASTC COMPRESSION
//
/**
* The quality of the compression process.
* This doesn't exactly reduce the quality of the outputted texture..
* But it changes how hard the CPU gotta work to maintin the quality and details of the image while compressing.
* possible values: fastest, fast, medium, thorough, exhaustive.
*/
static var ASTC_QUALITY:String = "medium";
/**
* The team ID to use for the iOS app. Configured in XCode.
*/
static var IOS_TEAM_ID:String = "Z7G7AVNGSH";
/**
* The block size of the ASTC compressed textures.
* Higher block size means lower quality and lighter file size for the generated compressed textures.
* possible block sizes: 4x4, 4x6, 6x6, 8x8 ... 12x12.
*/
static var ASTC_BLOCKSIZE:String = "10x10";
/**
* A list of asset file globs to exclude from ASTC compression when creating optimized mobile builds.
*/
@ -318,10 +330,10 @@ class Project extends HXProject
static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS";
/**
* `-DFEATURE_LAG_ADJUSTMENT`
* `-DFEATURE_INPUT_OFFSETS`
* If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets.
*/
static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT";
static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS";
/**
* `-DFEATURE_LOG_TRACE`
@ -475,7 +487,6 @@ class Project extends HXProject
envConfig = readEnvironmentFile("./.env");
flair();
configureApp();
displayTarget();
@ -487,10 +498,16 @@ class Project extends HXProject
configureOutputDir();
configurePolymod();
configureHaxelibs();
configureASTCTextures();
configureAssets();
configureIcons();
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this))
{
configureAdMobKeys();
@ -505,6 +522,8 @@ class Project extends HXProject
{
configureIOS();
}
info("Done configuring project.");
}
/**
@ -513,9 +532,10 @@ class Project extends HXProject
function flair()
{
// TODO: Implement this.
info("Friday Night Funkin' - " + VERSION);
info("Friday Night Funkin'");
info("Initializing build...");
info("Target Version: " + VERSION);
info("Git Branch: " + getGitBranch());
info("Git Commit: " + getGitCommit());
info("Git Modified? " + getGitModified());
@ -546,9 +566,6 @@ class Project extends HXProject
// If for some reason we have multiple source directories, we can add more entries here.
this.sources.push(SOURCE_DIR);
// Templates stuff
this.templatePaths.push(TEMPLATES_DIR);
// Tell Lime to run some prebuild and postbuild scripts.
this.preBuildCallbacks.push(buildHaxeCLICommand(PREBUILD_HX));
this.postBuildCallbacks.push(buildHaxeCLICommand(POSTBUILD_HX));
@ -812,7 +829,7 @@ class Project extends HXProject
FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile()));
// Should be true except on web builds (some asset stuff breaks it)
FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb());
FEATURE_INPUT_OFFSETS.apply(this, !isWeb());
// Should be true except on web and mobile builds.
// Screenshots doesn't work there, and mobile has its own screenshots anyway.
@ -843,79 +860,6 @@ class Project extends HXProject
// Since mobile is pretty memory safe (I'm looking at you Apple...) we need them BADLY to have the game run properly.
// It's kept off for desktop due to the low ASTC support on desktop GPUs (which seem to be only among Intergrated Graphics).
FEATURE_COMPRESSED_TEXTURES.apply(this, isMobile() && isRelease());
renderFlagsTable();
}
/**
* Renders a cute little table of which feature flags are enabled and which are disabled
*/
function renderFlagsTable():Void
{
var enabledWidth:Int = 0;
var disabledWidth:Int = 0;
var enabledFlags:Array<String> = [];
var disabledFlags:Array<String> = [];
var unicodeCheck:String = " ";
var unicodeCross:String = "× ";
if (isWindowsHost())
{
#if (!target.unicode)
// lime build tools uses neko (unless compiling with `-eval`), and neko on windows doesn't support UTF8/unicode characters
// so we need to remove our cute emoji!
unicodeCheck = "o ";
unicodeCross = "x ";
#end
}
for (flagStr in this.haxedefs.keys())
{
if (!flagStr.startsWith(FeatureFlag.INVERSE_PREFIX))
{
// we do flagStr.length + 3 to accomodate for adding the "✓ " at the beginning
enabledWidth = Std.int(Math.max(flagStr.length + 3, enabledWidth));
enabledFlags.push(unicodeCheck + flagStr);
}
else
{
disabledWidth = Std.int(Math.max(flagStr.length, disabledWidth));
disabledFlags.push(unicodeCross + flagStr.substring(FeatureFlag.INVERSE_PREFIX.length));
}
}
enabledFlags.sort(function(a, b) return a > b ? 1 : -1);
disabledFlags.sort(function(a, b) return a > b ? 1 : -1);
var horizontalDivider:String = "".rpad("-", enabledWidth + disabledWidth);
info(horizontalDivider);
// Header
info("FEATURE FLAGS");
info('Enabled Flags $unicodeCheck'.rpad(" ", enabledWidth + 5) + 'Disabled Flags $unicodeCross');
info(horizontalDivider);
while (enabledFlags.length > 0 || disabledFlags.length > 0)
{
var outputLine:String = "";
if (enabledFlags.length > 0)
outputLine += enabledFlags.shift();
outputLine = outputLine.rpad(" ", enabledWidth + 5);
outputLine += "| ";
if (disabledFlags.length > 0)
outputLine += disabledFlags.shift() ?? "";
info(outputLine);
}
info(horizontalDivider);
}
/**
@ -946,7 +890,7 @@ class Project extends HXProject
if (FEATURE_LOG_TRACE.isDisabled(this))
{
setHaxedef("no-traces");
addHaxeFlag("--no-traces");
}
// Disable the built in pause screen when unfocusing the game.
@ -1029,17 +973,17 @@ class Project extends HXProject
// we use a dedicated 'tracy' folder, since it generally needs a recompile when in use
if (FEATURE_DEBUG_TRACY.isEnabled(this)) buildDir += "-tracy";
info('Output directory: $buildDir');
// trailing slash might not be needed, works fine on macOS without it, but I haven't tested on Windows!
buildDir += "/";
info('Output directory: $buildDir');
// setenv('BUILD_DIR', buildDir);
app.path = buildDir;
}
function configureAndroid()
{
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/external/android/java']));
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java']));
if (isRelease())
{
@ -1051,37 +995,47 @@ class Project extends HXProject
keystore.password = Std.string(envConfig.get('ANDROID_KEYSTORE_PASSWORD'));
}
Reflect.setField(config.get('android.manifest'), 'xmlns:tools', 'http://schemas.android.com/tools');
final permissionsConfig:Array<String> = [
'<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />',
'<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />'
];
final xmlChildren:Null<Array<String>> = config.get('android.manifest').xmlChildren;
if (xmlChildren != null)
{
config.get('android.manifest').xmlChildren = xmlChildren.concat(permissionsConfig);
}
else
{
config.get('android.manifest').xmlChildren = permissionsConfig;
}
if (isBundle())
{
config.set("android.gradle-project-directory", "bin-bundle");
}
}
final modsFolderProvider:Xml = Xml.createElement('provider');
modsFolderProvider.set('android:authorities', '$PACKAGE_NAME.docprovider');
modsFolderProvider.set('android:name', 'funkin.provider.DataFolderProvider');
modsFolderProvider.set('android:grantUriPermissions', 'true');
modsFolderProvider.set('android:exported', 'true');
modsFolderProvider.set('android:permission', 'android.permission.MANAGE_DOCUMENTS');
final modsFolderProvider = [
'<provider android:authorities="$PACKAGE_NAME.docprovider" android:name="funkin.provider.DataFolderProvider" android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS">',
' <intent-filter>',
' <action android:name="android.content.action.DOCUMENTS_PROVIDER" />',
' </intent-filter>',
'</provider>'
];
final intentFilter:Xml = Xml.createElement('intent-filter');
final xmlChildren:Null<Array<String>> = config.get('android.application').xmlChildren;
final action:Xml = Xml.createElement('action');
action.set('android:name', 'android.content.action.DOCUMENTS_PROVIDER');
intentFilter.addChild(action);
modsFolderProvider.addChild(intentFilter);
@:nullSafety(Off)
if (xmlChildren != null)
{
if (config.get('android.application').xmlChildren != null)
{
config.get('android.application').xmlChildren.push(Printer.print(modsFolderProvider));
}
else
{
config.get('android.application').xmlChildren = [Printer.print(modsFolderProvider)];
}
config.get('android.application').xmlChildren = xmlChildren.concat(modsFolderProvider);
}
else
{
config.get('android.application').xmlChildren = modsFolderProvider;
}
final extensions:Null<Array<String>> = config.getArrayString('android.extension');
@ -1284,14 +1238,7 @@ class Project extends HXProject
addAsset("LICENSE.md", "LICENSE.md", "art", shouldEmbedArt);
addAsset("CHANGELOG.md", "CHANGELOG.md", "art", shouldEmbedArt);
if (shouldEmbed)
{
info('Done embedding assets.');
}
else
{
info('Done including assets.');
}
info('Done configuring assets.');
}
/**
@ -1304,7 +1251,27 @@ class Project extends HXProject
if (isAndroid())
{
// Adaptive icons
adaptiveIcon = new AdaptiveIcon('art/icons/android/', true);
// TODO: Add Adapative Icons in Lime
templatePaths.push("templates");
var androidTheme:String = "@style/LimeAppMainTheme";
if (window.fullscreen != null && window.fullscreen)
{
androidTheme += "Fullscreen";
}
final iconXmlChild:Dynamic = {
"android:label": meta.title,
"android:allowBackup": "true",
"android:theme": androidTheme,
"android:hardwareAccelerated": "true",
"android:allowNativeHeapPointerTagging": ANDROID_TARGET_SDK_VERSION >= 30 ? "false" : null,
"android:largeHeap": "true",
"android:icon": "@mipmap/ic_launcher",
"android:roundIcon": "@mipmap/ic_launcher_round"
};
config.set('android.application', iconXmlChild);
}
else if (isIOS())
{
@ -1336,24 +1303,7 @@ class Project extends HXProject
addIcon("art/icons/icon64.png", 64);
addIcon("art/icons/iconOG.png");
}
info('Done configuring icons.');
}
/**
* Configure the astc textures.
*/
function configureASTCTextures()
{
if (command != "display")
{
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
info('Done configuring icons.');
}
}
@ -1410,21 +1360,6 @@ class Project extends HXProject
return this.architectures.contains(Architecture.X64);
}
public function isWindowsHost():Bool
{
return System.hostPlatform == WINDOWS;
}
public function isMacHost():Bool
{
return System.hostPlatform == MAC;
}
public function isLinuxHost():Bool
{
return System.hostPlatform == LINUX;
}
public function isWindows():Bool
{
return this.target == Platform.WINDOWS;
@ -1768,8 +1703,7 @@ class Project extends HXProject
*/
public function error(message:String):Void
{
Sys.stderr().write(Bytes.ofString('[ERROR] ${message}'));
Sys.exit(1);
Log.error('${message}');
}
/**
@ -1779,7 +1713,7 @@ class Project extends HXProject
{
if (command != "display")
{
Sys.println('[INFO] ${message}');
Log.info('[INFO] ${message}');
}
}
@ -1806,67 +1740,46 @@ class Project extends HXProject
var env = new Map<String, Dynamic>();
for (line in envFile.split('\n'))
{
if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue;
if (line == "" || line.startsWith("#")) continue;
var index:Int = line.indexOf('=');
var parts = line.split('=');
if (parts.length != 2) continue;
if (index == -1) continue;
var field:String = line.substr(0, index);
var value:String = line.substr(index + 1);
env.set(field, value);
env.set(parts[0], parts[1]);
}
return env;
}
private function shouldExcludeEnvKey(key:String):Bool
{
final android:Bool = key.startsWith('ANDROID_');
final ios:Bool = key.startsWith('IOS_');
final mobile:Bool = key.startsWith('MOBILE_') || ios || android;
final web:Bool = key.startsWith('WEB_');
final desktop:Bool = key.startsWith('DESKTOP_');
if (isWeb() && (mobile || desktop)) return true;
if (isDesktop() && (mobile || web)) return true;
if (isAndroid() && (ios || web || desktop)) return true;
if (isIOS() && (android || web || desktop)) return true;
return false;
}
public function readASTCExclusion():Void
{
@:nullSafety(Off)
astcExcludes = haxe.Json.parse(File.getContent('./astc-compression-data.json')).excludes;
astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n');
for (i in 0...astcExcludes.length)
astcExcludes[i] = astcExcludes[i].trim();
}
public function isASTCExcluded(file:String):Bool
public function isASTCExcluded(filePath:String):Bool
{
for (exclusion in astcExcludes)
{
if (exclusion.endsWith("/"))
if (exclusion.endsWith("/*"))
{
var normalizedFilePath = Path.normalize(file);
var normalizedExclusion = Path.normalize(exclusion);
var normalizedFilePath = Path.normalize(filePath);
var normalizedExclusion = Path.normalize(exclusion.substr(0, exclusion.length - 2));
if (normalizedFilePath.startsWith(normalizedExclusion)) return true;
}
else if (exclusion.endsWith("/*"))
else if (exclusion.endsWith("/"))
{
var normalizedExclusion = Path.normalize(exclusion.substr(0, exclusion.length - 2));
var fileDirectory = Path.directory(Path.normalize(file));
var normalizedExclusion = Path.normalize(exclusion);
var fileDirectory = Path.directory(Path.normalize(filePath));
if (fileDirectory == normalizedExclusion) return true;
}
else
{
if (file == exclusion) return true;
if (filePath == exclusion) return true;
}
}
@ -1877,12 +1790,16 @@ class Project extends HXProject
{
info('Compressing ASTC textures...');
var args:Array<String> = ['run', 'astc-compressor', 'compress-from-json'];
args = args.concat(['-json', './astc-compression-data.json']);
var args:Array<String> = ['run', 'astc-compressor'];
args = args.concat(['-i', './assets']);
args = args.concat(['-o', './astc-textures/assets/']);
args = args.concat(['-blocksize', ASTC_BLOCKSIZE]);
args = args.concat(['-quality', ASTC_QUALITY]);
args = args.concat(['-excludes', './compression-excludes.txt']);
Sys.command('haxelib', args);
info('Done compressing ASTC textures.');
info('Finished compressing ASTC textures!');
}
}
@ -1890,9 +1807,9 @@ class Project extends HXProject
* An object representing a feature flag, which can be enabled or disabled.
* Includes features such as automatic generation of compile defines and inversion.
*/
abstract FeatureFlag(String) from String to String
abstract FeatureFlag(String)
{
public static final INVERSE_PREFIX:String = "NO_";
static final INVERSE_PREFIX:String = "NO_";
public function new(input:String)
{
@ -1917,11 +1834,13 @@ abstract FeatureFlag(String) from String to String
if (isEnabled(project))
{
// If this flag was already enabled, disable the inverse.
project.info('Enabling feature flag ${this}');
getInverse().disable(project, false);
}
else if (getInverse().isEnabled(project))
{
// If the inverse flag was already enabled, disable this flag.
project.info('Disabling feature flag ${this}');
disable(project, false);
}
else
@ -1929,11 +1848,13 @@ abstract FeatureFlag(String) from String to String
if (enableByDefault)
{
// Enable this flag if it was unset, and disable the inverse.
project.info('Enabling feature flag ${this}');
enable(project, true);
}
else
{
// Disable this flag if it was unset, and enable the inverse.
project.info('Disabling feature flag ${this}');
disable(project, true);
}
}

View file

@ -145,7 +145,6 @@ class Main extends Sprite
#if FEATURE_DEBUG_FUNCTIONS
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
funkin.util.macro.ConsoleMacro.init();
#end
#if !html5

View file

@ -29,7 +29,7 @@ class Postbuild
var buildTime:Float = roundToTwoDecimals(end - start);
Sys.println('[INFO] Build took: ${buildTime} seconds');
trace('Build took: ${buildTime} seconds');
}
}

View file

@ -9,16 +9,28 @@ class Prebuild
{
static inline final BUILD_TIME_FILE:String = '.build_time';
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
class NewgroundsCredentials
{
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
}";
static function main():Void
{
var start:Float = Sys.time();
// Sys.println('[INFO] Performing pre-build tasks...');
trace('[PREBUILD] Performing pre-build tasks...');
saveBuildTime();
buildCredsFile();
var end:Float = Sys.time();
var duration:Float = end - start;
// Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
trace('[PREBUILD] Finished pre-build tasks in $duration seconds.');
}
static function saveBuildTime():Void
@ -29,4 +41,20 @@ class Prebuild
fo.writeDouble(now);
fo.close();
}
static function buildCredsFile():Void
{
if (sys.FileSystem.exists(NG_CREDS_PATH))
{
trace('[PREBUILD] NewgroundsCredentials.hx already exists, skipping.');
}
else
{
trace('[PREBUILD] Creating NewgroundsCredentials.hx...');
var fileContents:String = NG_CREDS_TEMPLATE;
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
}
}
}

View file

@ -5,7 +5,6 @@ import flixel.util.FlxSignal;
import flixel.math.FlxMath;
import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.SongDataUtils;
import funkin.play.PlayState;
import funkin.save.Save;
import funkin.util.TimerUtil.SongSequence;
import haxe.Timer;
@ -114,17 +113,6 @@ class Conductor
if (currentTimeChange == null) return Constants.DEFAULT_BPM;
@:privateAccess
if (PlayState.instance != null && PlayState.instance.startingSong)
{
for (i in 0...timeChanges.length)
{
if (PlayState.instance.startTimestamp >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
if (PlayState.instance.startTimestamp < timeChanges[i].timeStamp) break;
}
}
return currentTimeChange.bpm;
}
@ -429,7 +417,7 @@ class Conductor
// If the song is playing, limit the song position to the length of the song or beginning of the song.
if (FlxG.sound.music != null && FlxG.sound.music.playing)
{
this.songPosition = Math.min(this.combinedOffset, 0).clamp(songPos, currentLength);
this.songPosition = FlxMath.bound(Math.min(this.combinedOffset, 0), songPos, currentLength);
this.songPositionDelta += FlxG.elapsed * 1000 * FlxG.sound.music.pitch;
}
else

View file

@ -117,7 +117,10 @@ class FunkinMemory
*/
public static function cacheTexture(key:String):Void
{
if (currentCachedTextures.exists(key)) return;
if (currentCachedTextures.exists(key))
{
return; // Already cached.
}
if (previousCachedTextures.exists(key))
{
@ -132,13 +135,13 @@ class FunkinMemory
if (graphic == null)
{
FlxG.log.warn('Failed to cache graphic: $key');
return;
}
trace('Successfully cached graphic: $key');
graphic.persist = true;
currentCachedTextures.set(key, graphic);
forceRender(graphic);
else
{
trace('Successfully cached graphic: $key');
graphic.persist = true;
currentCachedTextures.set(key, graphic);
}
}
/**
@ -147,54 +150,26 @@ class FunkinMemory
*/
static function permanentCacheTexture(key:String):Void
{
if (permanentCachedTextures.exists(key)) return;
if (permanentCachedTextures.exists(key))
{
return; // Already cached.
}
var graphic:Null<FlxGraphic> = FlxGraphic.fromAssetKey(key, false, null, true);
if (graphic == null)
{
FlxG.log.warn('Failed to cache graphic: $key');
return;
}
else
{
trace('Successfully cached graphic: $key');
graphic.persist = true;
permanentCachedTextures.set(key, graphic);
}
trace('Successfully cached graphic: $key');
graphic.persist = true;
permanentCachedTextures.set(key, graphic);
forceRender(graphic);
currentCachedTextures = permanentCachedTextures;
}
/**
* Forces the GPU to load and upload a FlxGraphic.
*/
private static function forceRender(graphic:FlxGraphic):Void
{
if (graphic == null) return;
var bmp:Null<FlxGraphic> = FlxG.bitmap.get(graphic.key);
if (bmp != null && bmp.bitmap != null) var _:Int = bmp.bitmap.width; // Trigger
// Draws sprite and actually caches it.
var sprite = new flixel.FlxSprite();
sprite.loadGraphic(graphic);
sprite.draw(); // Draw sprite and load it into game's memory.
sprite.destroy();
}
/**
* Checks, if graphic with given path cached in memory.
*/
public static inline function isGraphicCached(path:String):Bool
return permanentCachedTextures.exists(path) || currentCachedTextures.exists(path) || previousCachedTextures.exists(path);
public static function getCachedGraphic(path:String):Null<FlxGraphic>
{
if (permanentCachedTextures.exists(path)) return permanentCachedTextures.get(path);
if (currentCachedTextures.exists(path)) return currentCachedTextures.get(path);
if (previousCachedTextures.exists(path)) return previousCachedTextures.get(path); // just in case
return null;
}
/**
* Prepares the cache for purging unused textures.
*/

View file

@ -11,9 +11,9 @@ import flixel.math.FlxRect;
import flixel.system.debug.log.LogStyle;
import flixel.util.FlxColor;
import funkin.graphics.FunkinSprite;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
@ -55,13 +55,6 @@ import funkin.api.newgrounds.NewgroundsClient;
@:nullSafety
class InitState extends FlxState
{
/**
* Simply states whether the "core stuff" is ready or not.
* This is used to prevent re-initialization of specific core features.
*/
@:noCompletion
static var _coreInitialized:Bool = false;
/**
* Perform a bunch of game setup, then immediately transition to the title screen.
*/
@ -85,163 +78,159 @@ class InitState extends FlxState
*/
function setupShit():Void
{
if (!_coreInitialized)
{
//
// GAME SETUP
//
//
// GAME SETUP
//
// Setup window events (like callbacks for onWindowClose) and fullscreen keybind setup
WindowUtil.initWindowEvents();
// Setup window events (like callbacks for onWindowClose)
// and fullscreen keybind setup
WindowUtil.initWindowEvents();
// Disable the thing on Windows where it tries to send a bug report to Microsoft because why do they care?
WindowUtil.disableCrashHandler();
#if FEATURE_DEBUG_TRACY
funkin.util.WindowUtil.initTracy();
#end
#if FEATURE_DEBUG_TRACY
funkin.util.WindowUtil.initTracy();
#end
#if FEATURE_HAPTICS
// Setup Haptic feedback
extension.haptics.Haptic.initialize();
#end
#if FEATURE_HAPTICS
// Setup Haptic feedback
extension.haptics.Haptic.initialize();
#end
#if FEATURE_MOBILE_ADVERTISEMENTS
// Setup Admob
funkin.mobile.util.AdMobUtil.init();
#end
#if FEATURE_MOBILE_ADVERTISEMENTS
// Setup Admob
funkin.mobile.util.AdMobUtil.init();
#end
#if FEATURE_MOBILE_IAP
// Setup In-App purchases
funkin.mobile.util.InAppPurchasesUtil.init();
#end
#if FEATURE_MOBILE_IAP
// Setup In-App purchases
funkin.mobile.util.InAppPurchasesUtil.init();
#end
#if FEATURE_MOBILE_IAR
// Setup In-App reviews
funkin.mobile.util.InAppReviewUtil.init();
#end
#if FEATURE_MOBILE_IAR
// Setup In-App purchases
funkin.mobile.util.InAppReviewUtil.init();
#end
#if android
// Setup Callback util.
funkin.external.android.CallbackUtil.init();
#end
#if ios
// Setup Audio session
funkin.mobile.external.ios.AudioSession.initialize();
#end
#if ios
// Setup Audio session
funkin.external.ios.AudioSession.initialize();
#end
// This ain't a pixel art game! (most of the time)
FlxSprite.defaultAntialiasing = true;
// This ain't a pixel art game! (most of the time)
FlxSprite.defaultAntialiasing = true;
// Disable default keybinds for volume (we manually control volume in MusicBeatState with custom binds)
FlxG.sound.volumeUpKeys = [];
FlxG.sound.volumeDownKeys = [];
FlxG.sound.muteKeys = [];
// Disable default keybinds for volume (we manually control volume in MusicBeatState with custom binds)
FlxG.sound.volumeUpKeys = [];
FlxG.sound.volumeDownKeys = [];
FlxG.sound.muteKeys = [];
// A small jumpstart to the soundtray, it usually sets itself to inactive (somewhere...)
// but that makes our soundtray not show up on init if we have the game muted.
// We set it to active so it at least calls it's update function once (see FlxGame.onEnterFrame(), it's called there)
// and also see FunkinSoundTray.update() to see what we do and how we check if we are muted or not
#if !mobile
FlxG.game.soundTray.active = true;
#end
// A small jumpstart to the soundtray, it usually sets itself to inactive (somewhere...)
// but that makes our soundtray not show up on init if we have the game muted.
// We set it to active so it at least calls it's update function once (see FlxGame.onEnterFrame(), it's called there)
// and also see FunkinSoundTray.update() to see what we do and how we check if we are muted or not
#if !mobile
FlxG.game.soundTray.active = true;
#end
// Set the game to a lower frame rate while it is in the background.
FlxG.game.focusLostFramerate = 30;
// 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;
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
FlxG.fixedTimestep = false;
setupFlixelDebug();
setupFlixelDebug();
//
// FLIXEL TRANSITIONS
//
//
// FLIXEL TRANSITIONS
//
// Diamond Transition
var diamond:FlxGraphic = FlxGraphic.fromClass(GraphicTransTileDiamond);
diamond.persist = true;
diamond.destroyOnNoUse = false;
// Diamond Transition
var diamond:FlxGraphic = FlxGraphic.fromClass(GraphicTransTileDiamond);
diamond.persist = true;
diamond.destroyOnNoUse = false;
// NOTE: tileData is ignored if TransitionData.type is FADE instead of TILES.
var tileData:TransitionTileData = {asset: diamond, width: 32, height: 32};
// NOTE: tileData is ignored if TransitionData.type is FADE instead of TILES.
var tileData:TransitionTileData = {asset: diamond, width: 32, height: 32};
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
// Don't play transition in when entering the title state.
FlxTransitionableState.skipNextTransIn = true;
FlxG.signals.gameResized.add(function(width:Int, height:Int) {
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
});
FlxG.signals.gameResized.add(function(width:Int, height:Int) {
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), tileData,
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
});
// SDL for some reason enables VSync on focus lost/gained in Android
// Since we don't really need VSync on Android we're gonna forcefully disable it on these signals for now
// This is fixed on SDL3 from what I've heared but that doodoo isn't working poperly for Android
#if android
FlxG.signals.focusLost.add(function() {
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
});
FlxG.signals.focusGained.add(function() {
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
});
#end
// SDL for some reason enables VSync on focus lost/gained in Android
// Since we don't really need VSync on Android we're gonna forcefully disable it on these signals for now
// This is fixed on SDL3 from what I've heared but that doodoo isn't working poperly for Android
#if android
FlxG.signals.focusLost.add(function() {
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
});
FlxG.signals.focusGained.add(function() {
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
});
#end
//
// NEWGROUNDS API SETUP
//
#if FEATURE_NEWGROUNDS
NewgroundsClient.instance.init();
#end
//
// NEWGROUNDS API SETUP
//
#if FEATURE_NEWGROUNDS
NewgroundsClient.instance.init();
#end
//
// DISCORD API SETUP
//
#if FEATURE_DISCORD_RPC
DiscordClient.instance.init();
//
// DISCORD API SETUP
//
#if FEATURE_DISCORD_RPC
DiscordClient.instance.init();
lime.app.Application.current.onExit.add(function(exitCode) {
DiscordClient.instance.shutdown();
});
#end
lime.app.Application.current.onExit.add(function(exitCode) {
DiscordClient.instance.shutdown();
});
#end
//
// ANDROID SETUP
//
#if android
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
funkin.mobile.external.android.CallbackUtil.init();
#end
//
// ANDROID SETUP
//
#if android
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
#end
//
// FLIXEL PLUGINS
//
// Plugins provide a useful interface for globally active Flixel objects,
// that receive update events regardless of the current state.
// TODO: Move scripted Module behavior to a Flixel plugin.
#if FEATURE_DEBUG_FUNCTIONS
funkin.util.plugins.MemoryGCPlugin.initialize();
#end
#if FEATURE_SCREENSHOTS
funkin.util.plugins.ScreenshotPlugin.initialize();
#end
#if FEATURE_NEWGROUNDS
funkin.util.plugins.NewgroundsMedalPlugin.initialize();
#end
funkin.util.plugins.EvacuateDebugPlugin.initialize();
funkin.util.plugins.ForceCrashPlugin.initialize();
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
#if !mobile
funkin.util.plugins.VolumePlugin.initialize();
#end
funkin.util.plugins.WatchPlugin.initialize();
#if mobile
funkin.util.plugins.TouchPointerPlugin.initialize();
funkin.mobile.input.ControlsHandler.initInputTrackers();
#end
_coreInitialized = true;
}
//
// FLIXEL PLUGINS
//
// Plugins provide a useful interface for globally active Flixel objects,
// that receive update events regardless of the current state.
// TODO: Move scripted Module behavior to a Flixel plugin.
#if FEATURE_DEBUG_FUNCTIONS
funkin.util.plugins.MemoryGCPlugin.initialize();
#end
#if FEATURE_SCREENSHOTS
funkin.util.plugins.ScreenshotPlugin.initialize();
#end
#if FEATURE_NEWGROUNDS
funkin.util.plugins.NewgroundsMedalPlugin.initialize();
#end
funkin.util.plugins.EvacuateDebugPlugin.initialize();
funkin.util.plugins.ForceCrashPlugin.initialize();
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
#if !mobile
funkin.util.plugins.VolumePlugin.initialize();
#end
funkin.util.plugins.WatchPlugin.initialize();
#if mobile
funkin.util.plugins.TouchPointerPlugin.initialize();
funkin.mobile.input.ControlsHandler.initInputTrackers();
#end
//
// GAME DATA PARSING
@ -290,9 +279,6 @@ class InitState extends FlxState
*/
function startGame():Void
{
// Don't play transition in when entering the title state.
FlxTransitionableState.skipNextTransIn = true;
#if SONG
// -DSONG=bopeebo
startSong(defineSong(), defineDifficulty());

View file

@ -1,6 +1,5 @@
package funkin.api.discord;
import funkin.util.macro.EnvironmentConfigMacro;
#if FEATURE_DISCORD_RPC
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types.DiscordButton;
@ -12,7 +11,7 @@ import sys.thread.Thread;
@:nullSafety
class DiscordClient
{
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
static final CLIENT_ID:String = "816168432860790794";
public static var instance(get, never):DiscordClient;
static var _instance:Null<DiscordClient> = null;
@ -41,28 +40,12 @@ class DiscordClient
{
trace('[DISCORD] Initializing connection...');
if (!hasValidCredentials())
{
FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!");
return;
}
@:nullSafety(Off)
{
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
}
// Discord.initialize(CLIENT_ID, handlers, true, null);
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
createDaemon();
}
/**
* @returns `false` if the client ID is invalid.
*/
static function hasValidCredentials():Bool
{
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
}
var daemon:Null<Thread> = null;
function createDaemon():Void

View file

@ -1,156 +0,0 @@
package funkin.api.newgrounds;
#if FEATURE_NEWGROUNDS
import io.newgrounds.utils.SaveSlotList;
import io.newgrounds.objects.SaveSlot;
import io.newgrounds.Call.CallError;
import io.newgrounds.objects.events.Outcome;
import funkin.save.Save;
@:nullSafety
@:access(funkin.save.Save)
class NGSaveSlot
{
public static var instance(get, never):NGSaveSlot;
static var _instance:Null<NGSaveSlot> = null;
static function get_instance():NGSaveSlot
{
if (_instance == null)
{
return loadInstance();
}
return _instance;
}
public static function loadInstance():NGSaveSlot
{
var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT);
if (_instance == null) _instance = loadedSave;
return loadedSave;
}
static function loadSlot(slot:Int):NGSaveSlot
{
trace('[NEWGROUNDS] Getting save slot from ID $slot');
var saveSlot:Null<SaveSlot> = NewgroundsClient.instance.saveSlots?.getById(slot);
var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot);
return saveSlotObj;
}
public var ngSaveSlot:Null<SaveSlot> = null;
public function new(?ngSaveSlot:Null<SaveSlot>)
{
this.ngSaveSlot = ngSaveSlot;
#if FLX_DEBUG
FlxG.console.registerClass(NGSaveSlot);
FlxG.console.registerClass(Save);
#end
}
/**
* Saves `data` to the newgrounds save slot.
* @param data The raw save data.
*/
public function save(data:RawSaveData):Void
{
var encodedData:String = haxe.Serializer.run(data);
try
{
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>) {
switch (outcome)
{
case SUCCESS:
trace('[NEWGROUNDS] Successfully saved save data to save slot!');
case FAIL(error):
trace('[NEWGROUNDS] Failed to save data to save slot!');
trace(error);
}
});
}
catch (error:String)
{
trace('[NEWGROUNDS] Failed to save data to save slot!');
trace(error);
}
}
public function load(?onComplete:Null<Dynamic->Void>, ?onError:Null<CallError->Void>):Void
{
try
{
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void {
switch (outcome)
{
case SUCCESS(value):
trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!');
#if FEATURE_DEBUG_FUNCTIONS
trace('Save Slot Data:');
trace(value);
#end
if (onComplete != null && value != null)
{
var decodedData:Dynamic = haxe.Unserializer.run(value);
onComplete(decodedData);
}
case FAIL(error):
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
trace(error);
if (onError != null)
{
onError(error);
}
}
});
}
catch (error:String)
{
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
trace(error);
if (onError != null)
{
onError(RESPONSE({message: error, code: 500}));
}
}
}
public function clear():Void
{
try
{
ngSaveSlot?.clear(function(outcome:Outcome<CallError>) {
switch (outcome)
{
case SUCCESS:
trace('[NEWGROUNDS] Successfully cleared save slot!');
case FAIL(error):
trace('[NEWGROUNDS] Failed to clear save slot!');
trace(error);
}
});
}
catch (error:String)
{
trace('[NEWGROUNDS] Failed to clear save slot!');
trace(error);
}
}
public function checkSlot():Void
{
trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...');
trace(' Is null? ${ngSaveSlot == null}');
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
}
}
#end

View file

@ -1,6 +1,5 @@
package funkin.api.newgrounds;
import funkin.util.macro.EnvironmentConfigMacro;
import funkin.save.Save;
import funkin.api.newgrounds.Medals.Medal;
#if FEATURE_NEWGROUNDS
@ -11,18 +10,13 @@ import io.newgrounds.NGLite.LoginOutcome;
import io.newgrounds.NGLite.LoginFail;
import io.newgrounds.objects.events.Outcome;
import io.newgrounds.utils.MedalList;
import io.newgrounds.utils.SaveSlotList;
import io.newgrounds.utils.ScoreBoardList;
import io.newgrounds.objects.User;
@:nullSafety
class NewgroundsClient
{
static final APP_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID");
static final ENCRYPTION_KEY:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY");
public static var instance(get, never):NewgroundsClient;
static var _instance:Null<NewgroundsClient> = null;
static function get_instance():NewgroundsClient
@ -35,15 +29,14 @@ class NewgroundsClient
public var user(get, never):Null<User>;
public var medals(get, never):Null<MedalList>;
public var leaderboards(get, never):Null<ScoreBoardList>;
public var saveSlots(get, never):Null<SaveSlotList>;
private function new()
{
trace('[NEWGROUNDS] Initializing client...');
#if FEATURE_NEWGROUNDS_DEBUG
trace('[NEWGROUNDS] App ID: ${APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
#end
if (!hasValidCredentials())
@ -52,12 +45,9 @@ class NewgroundsClient
return;
}
@:nullSafety(Off)
{
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
NG.core.setupEncryption(ENCRYPTION_KEY);
}
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
}
public function init()
@ -176,12 +166,12 @@ class NewgroundsClient
*/
static function hasValidCredentials():Bool
{
return !(APP_ID == null
|| APP_ID == ""
|| (APP_ID != null && APP_ID.contains(" "))
|| ENCRYPTION_KEY == null
|| ENCRYPTION_KEY == ""
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
return !(NewgroundsCredentials.APP_ID == null
|| NewgroundsCredentials.APP_ID == ""
|| NewgroundsCredentials.APP_ID.contains(" ")
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
}
function onLoginResolved(outcome:LoginOutcome):Void
@ -246,8 +236,6 @@ class NewgroundsClient
trace('[NEWGROUNDS] Submitting leaderboard request...');
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
trace('[NEWGROUNDS] Submitting save slot request...');
NG.core.saveSlots.loadList(onFetchedSaveSlots);
}
function onLoginFailed(result:LoginFail):Void
@ -313,13 +301,6 @@ class NewgroundsClient
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
}
function onFetchedSaveSlots(outcome:Outcome<CallError>):Void
{
trace('[NEWGROUNDS] Fetched save slots!');
NGSaveSlot.instance.checkSlot();
}
function get_user():Null<User>
{
if (NG.core == null || !this.isLoggedIn()) return null;
@ -338,12 +319,6 @@ class NewgroundsClient
return NG.core.scoreBoards;
}
function get_saveSlots():Null<SaveSlotList>
{
if (NG.core == null || !this.isLoggedIn()) return null;
return NG.core.saveSlots;
}
static function getSessionId():Null<String>
{
#if js

View file

@ -72,7 +72,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
override function set_volume(value:Float):Float
{
// Uncap the volume.
_volume = value.clamp(0.0, MAX_VOLUME);
_volume = FlxMath.bound(value, 0.0, MAX_VOLUME);
updateTransform();
return _volume;
}
@ -568,8 +568,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
if (_sound == null) return;
// Create a channel manually if the sound is considered important.
var pan:Float = (SoundMixer.__soundTransform.pan + _transform.pan).clamp(-1, 1);
var volume:Float = (SoundMixer.__soundTransform.volume * _transform.volume).clamp(0, MAX_VOLUME);
var pan:Float = FlxMath.bound(SoundMixer.__soundTransform.pan + _transform.pan, -1, 1);
var volume:Float = FlxMath.bound(SoundMixer.__soundTransform.volume * _transform.volume, 0, MAX_VOLUME);
var audioSource:AudioSource = new AudioSource(_sound.__buffer);
audioSource.offset = Std.int(startTime);

View file

@ -5,21 +5,20 @@ import funkin.util.VersionUtil;
import haxe.Constraints.Constructible;
/**
* The entry's constructor function takes 2 arguments, the entry ID and optional parameters.
* The entry's constructor function must take a single argument, the entry's ID.
*/
typedef EntryConstructorFunction = (String, ?Dynamic) -> Void;
typedef EntryConstructorFunction = String->Void;
/**
* A base type for a Registry, which is an object which handles loading scriptable objects.
*
* @param T The type to construct. Must implement `IRegistryEntry`.
* @param J The type of the JSON data used when constructing.
* @param P The type of the parameters used for `fetchEntry()`.
*/
@:nullSafety
@:generic
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J, P>
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
{
/**
* The ID of the registry. Used when logging.
@ -187,7 +186,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
* @param id The ID of the entry to fetch.
* @return The entry, or `null` if it does not exist.
*/
public function fetchEntry(id:String, ?params:Null<P>):Null<T>
public function fetchEntry(id:String):Null<T>
{
return entries.get(id);
}

View file

@ -99,7 +99,7 @@ class DataParse
case JArray(values):
return Either.Left(legacyNoteSectionArray(json, name));
case JObject(fields):
return Either.Right(legacyNoteData(json, name));
return Either.Right(cast Tools.getValue(json));
default:
throw 'Expected property $name to be note data, but it was ${json.value}.';
}
@ -121,7 +121,7 @@ class DataParse
}
}
public static function backdropData(json:Json, name:String):funkin.data.dialogue.ConversationData.BackdropData
public static function backdropData(json:Json, name:String):funkin.data.dialogue.conversation.ConversationData.BackdropData
{
switch (json.value)
{
@ -152,7 +152,7 @@ class DataParse
}
}
public static function outroData(json:Json, name:String):Null<funkin.data.dialogue.ConversationData.OutroData>
public static function outroData(json:Json, name:String):Null<funkin.data.dialogue.conversation.ConversationData.OutroData>
{
switch (json.value)
{

View file

@ -1,39 +0,0 @@
# Dialogue Conversation Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.
# Dialogue Box Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0]
### Added
- Added an option to specify the font used by the dialogue box. Defaults to `Arial` if unspecified.
## [1.0.0]
Initial release.
# Dialogue Speaker Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -0,0 +1,9 @@
# Dialogue Conversation Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue;
package funkin.data.dialogue.conversation;
/**
* A type definition for the data for a specific conversation.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue;
package funkin.data.dialogue.conversation;
import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.ScriptedConversation;
@ -6,7 +6,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData, ConversationEntryParams> implements ISingleton implements DefaultRegistryImpl
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the dialogue box data format.
@ -22,8 +22,3 @@ class ConversationRegistry extends BaseRegistry<Conversation, ConversationData,
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
}
}
typedef ConversationEntryParams =
{
var placeholder:String;
}

View file

@ -0,0 +1,13 @@
# Dialogue Box Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0]
### Added
- Added an option to specify the font used by the dialogue box. Defaults to `Arial` if unspecified.
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue;
package funkin.data.dialogue.dialoguebox;
import funkin.data.animation.AnimationData;

View file

@ -1,13 +1,13 @@
package funkin.data.dialogue;
package funkin.data.dialogue.dialoguebox;
import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.data.dialogue.DialogueBoxData;
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData, DialogueBoxEntryParams> implements ISingleton implements DefaultRegistryImpl
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the dialogue box data format.
@ -23,5 +23,3 @@ class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData, Dia
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
}
}
typedef DialogueBoxEntryParams = Dynamic;

View file

@ -0,0 +1,9 @@
# Dialogue Speaker Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue;
package funkin.data.dialogue.speaker;
import funkin.data.animation.AnimationData;

View file

@ -1,4 +1,4 @@
package funkin.data.dialogue;
package funkin.data.dialogue.speaker;
import funkin.play.cutscene.dialogue.Speaker;
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
@ -6,7 +6,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData, SpeakerEntryParams> implements ISingleton implements DefaultRegistryImpl
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the speaker data format.
@ -22,5 +22,3 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData, SpeakerEntryPar
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
}
}
typedef SpeakerEntryParams = {}

View file

@ -7,7 +7,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class AlbumRegistry extends BaseRegistry<Album, AlbumData, AlbumEntryParams> implements ISingleton implements DefaultRegistryImpl
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the album data format.
@ -23,5 +23,3 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData, AlbumEntryParams> imp
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
}
}
typedef AlbumEntryParams = {}

View file

@ -8,7 +8,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerEntryParams> implements ISingleton implements DefaultRegistryImpl
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the stage data format.
@ -147,5 +147,3 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
#end
}
}
typedef PlayerEntryParams = {}

View file

@ -7,8 +7,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData, FreeplayStyleEntryParams> implements ISingleton
implements DefaultRegistryImpl
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the style data format.
@ -24,5 +23,3 @@ class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleDat
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
}
}
typedef FreeplayStyleEntryParams = {}

View file

@ -165,7 +165,7 @@ typedef NoteStyleAssetData<T> =
var assetPath:String;
/**
* The scale to render the note at.
* The scale to render the prop at.
* @default 1.0
*/
@:default(1.0)
@ -181,7 +181,7 @@ typedef NoteStyleAssetData<T> =
var offsets:Null<Array<Float>>;
/**
* If true, the note is a pixel sprite, and will be rendered without anti-aliasing.
* If true, the prop is a pixel sprite, and will be rendered without anti-aliasing.
*/
@:default(false)
@:optional

View file

@ -7,7 +7,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData, NoteStyleEntryParams> implements ISingleton implements DefaultRegistryImpl
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the note style data format.
@ -30,5 +30,3 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData, NoteStyle
return notestyle;
}
}
typedef NoteStyleEntryParams = {}

View file

@ -1117,23 +1117,6 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
}
public function buildTooltip():String
{
if ((this.kind?.length ?? 0) == 0) return "";
var result:String = 'Kind: ${this.kind}';
if (this.params.length == 0) return result;
result += "\nParams:";
for (param in params)
{
result += '\n- ${param.name}: ${param.value}';
}
return result;
}
}
/**

View file

@ -15,7 +15,8 @@ import funkin.data.DefaultRegistryImpl;
using funkin.data.song.migrator.SongDataMigrator;
@:nullSafety class SongRegistry extends BaseRegistry<Song, SongMetadata, SongEntryParams> implements ISingleton implements DefaultRegistryImpl
@:nullSafety
class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the stage data format.
@ -36,8 +37,6 @@ using funkin.data.song.migrator.SongDataMigrator;
public static var DEFAULT_GENERATEDBY(get, never):String;
public var scriptedSongVariations:Map<String, Song> = new Map<String, Song>();
static function get_DEFAULT_GENERATEDBY():String
{
return '${Constants.TITLE} - ${Constants.VERSION}';
@ -64,17 +63,9 @@ using funkin.data.song.migrator.SongDataMigrator;
if (entry != null)
{
if (entry.variation != null)
{
scriptedSongVariations.set('${entry.id}:${entry.variation}', entry);
log('Successfully created scripted entry (${entryCls} = ${entry.id}, ${entry.variation})');
}
else
{
entries.set(entry.id, entry);
scriptedEntryIds.set(entry.id, entryCls);
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
}
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
entries.set(entry.id, entry);
scriptedEntryIds.set(entry.id, entryCls);
}
else
{
@ -129,28 +120,6 @@ using funkin.data.song.migrator.SongDataMigrator;
return parseEntryMetadataRaw(contents);
}
/**
* We override `fetchEntry` to handle song variations!
*/
public override function fetchEntry(id:String, ?params:SongEntryParams):Null<Song>
{
var variation:String = params?.variation ?? Constants.DEFAULT_VARIATION;
if (variation != Constants.DEFAULT_VARIATION)
{
if (scriptedSongVariations.exists('${id}:${variation}'))
{
var variationSongScript:Null<Song> = scriptedSongVariations.get('${id}:${variation}');
if (variationSongScript != null)
{
return variationSongScript;
}
}
}
return super.fetchEntry(id, params);
}
public function parseEntryMetadata(id:String, ?variation:String):Null<SongMetadata>
{
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
@ -548,11 +517,3 @@ using funkin.data.song.migrator.SongDataMigrator;
return allDifficulties;
}
}
typedef SongEntryParams =
{
/**
* The variation ID for the song.
*/
var variation:String;
}

View file

@ -10,8 +10,6 @@ class ChartManifestData
*/
public static final CHART_MANIFEST_DATA_VERSION:thx.semver.Version = "1.0.0";
public static final invalidIdRegex:EReg = ~/[\/\\:*?"<>|]/g;
@:default(funkin.data.song.importer.ChartManifestData.CHART_MANIFEST_DATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
@ -21,11 +19,7 @@ class ChartManifestData
* The internal song ID for this chart.
* The metadata and chart data file names are derived from this.
*/
public var songId(default, set):String;
public function set_songId(value:String):String
{
return songId = invalidIdRegex.replace(value.trim(), '');
}
public var songId:String;
public function new(songId:String)
{

View file

@ -120,11 +120,6 @@ class LegacyNote
public inline function getKind():String
{
return this.alt ? 'alt' : '';
}
public function toString():String
{
return 'LegacyNote(${this.time}, ${this.data}, ${this.length}, ${this.alt})';
return this.alt ? 'alt' : 'normal';
}
}

View file

@ -95,8 +95,8 @@ class FNFLegacyImporter
switch (songData.song.speed)
{
case Left(speed):
// Sets the scroll speed for the difficulty.
songChartData.scrollSpeed.set(difficulty, speed);
// All difficulties will use the one scroll speed.
songChartData.scrollSpeed.set('default', speed);
case Right(speeds):
if (speeds.easy != null) songChartData.scrollSpeed.set('easy', speeds.easy);
if (speeds.normal != null) songChartData.scrollSpeed.set('normal', speeds.normal);

View file

@ -6,7 +6,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class StageRegistry extends BaseRegistry<Stage, StageData, StageEntryParams> implements ISingleton implements DefaultRegistryImpl
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the stage data format.
@ -22,5 +22,3 @@ class StageRegistry extends BaseRegistry<Stage, StageData, StageEntryParams> imp
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
}
}
typedef StageEntryParams = {}

View file

@ -5,7 +5,7 @@ import funkin.ui.transition.stickers.StickerPack;
import funkin.ui.transition.stickers.ScriptedStickerPack;
@:nullSafety
class StickerRegistry extends BaseRegistry<StickerPack, StickerData, StickerEntryParams>
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
{
/**
* The current version string for the sticker pack data format.
@ -90,5 +90,3 @@ class StickerRegistry extends BaseRegistry<StickerPack, StickerData, StickerEntr
return ScriptedStickerPack.listScriptClasses();
}
}
typedef StickerEntryParams = {}

View file

@ -7,7 +7,7 @@ import funkin.util.tools.ISingleton;
import funkin.data.DefaultRegistryImpl;
@:nullSafety
class LevelRegistry extends BaseRegistry<Level, LevelData, LevelEntryParams> implements ISingleton implements DefaultRegistryImpl
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
{
/**
* The current version string for the level data format.
@ -56,5 +56,3 @@ class LevelRegistry extends BaseRegistry<Level, LevelData, LevelEntryParams> imp
return result;
}
}
typedef LevelEntryParams = {}

View file

@ -1,75 +0,0 @@
package funkin.external.windows;
#if (windows && cpp)
/**
* This class provides handling for Windows API-related functions.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:include('winapi.hpp')
extern class WinAPI
{
/**
* Shows a message box with an error icon.
*
* @param message The message to display.
* @param title The title of the message box.
*/
@:native('WINAPI_ShowError')
static function showError(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
/**
* Shows a message box with a warning icon.
*
* @param message The message to display.
* @param title The title of the message box.
*/
@:native('WINAPI_ShowWarning')
static function showWarning(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
/**
* Shows a message box with an information icon.
*
* @param message The message to display.
* @param title The title of the message box.
*/
@:native('WINAPI_ShowInformation')
static function showInformation(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
/**
* Shows a message box with a question icon.
*
* @param message The message to display.
* @param title The title of the message box.
*/
@:native('WINAPI_ShowQuestion')
static function showQuestion(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
/**
* Disables the "Report to Microsoft" dialog that appears when the application crashes.
*/
@:native('WINAPI_DisableErrorReporting')
static function disableErrorReporting():Void;
/**
* Disables Windows ghosting, which prevents the system from marking unresponsive windows as "Not Responding."
*/
@:native('WINAPI_DisableWindowsGhosting')
static function disableWindowsGhosting():Void;
/**
* Sets the dark mode for the active window.
*
* @param enable A boolean value indicating whether to enable (true) or disable (false) dark mode.
*/
@:native('WINAPI_SetDarkMode')
static function setDarkMode(enable:Bool):Void;
/**
* Checks if the system is using dark mode.
*
* @return True if system is in dark mode, false otherwise.
*/
@:native('WINAPI_IsSystemDarkMode')
static function isSystemDarkMode():Bool;
}
#end

View file

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<pragma once="true" />
<files id="haxe">
<compilerflag value="-I${this_dir}/include" />
</files>
<files id="__main__">
<compilerflag value="-I${this_dir}/include" />
</files>
<files id="winapi">
<compilerflag value="-I${this_dir}/include" />
<file name="${this_dir}/src/winapi.cpp" />
</files>
<target id="haxe">
<section if="mingw">
<lib name="-luser32" />
<lib name="-lkernel32" />
<lib name="-ldwmapi" />
<lib name="-ladvapi32" />
</section>
<section if="windows" unless="mingw">
<lib name="user32.lib" />
<lib name="kernel32.lib" />
<lib name="dwmapi.lib" />
<lib name="advapi32.lib" />
</section>
<files id="winapi" />
</target>
</xml>

View file

@ -1,51 +0,0 @@
#pragma once
/**
* @brief Shows an error message box
* @param message The error message to display
* @param title The title of the message box
*/
void WINAPI_ShowError(const char *message, const char *title);
/**
* @brief Shows a warning message box
* @param message The warning message to display
* @param title The title of the message box
*/
void WINAPI_ShowWarning(const char *message, const char *title);
/**
* @brief Shows an information message box
* @param message The information message to display
* @param title The title of the message box
*/
void WINAPI_ShowInformation(const char *message, const char *title);
/**
* @brief Shows a question message box with OK/Cancel buttons
* @param message The question message to display
* @param title The title of the message box
*/
void WINAPI_ShowQuestion(const char *message, const char *title);
/**
* @brief Disables Windows error reporting dialogs
*/
void WINAPI_DisableErrorReporting();
/**
* @brief Disables Windows ghosting for the current process
*/
void WINAPI_DisableWindowsGhosting();
/**
* @brief Sets dark mode for the active window
* @param enable True to enable dark mode, false to disable
*/
void WINAPI_SetDarkMode(bool enable);
/**
* @brief Checks if the system is using dark mode
* @return True if system is in dark mode, false otherwise
*/
bool WINAPI_IsSystemDarkMode();

View file

@ -1,75 +0,0 @@
#define WIN32_LEAN_AND_MEAN // Excludes rarely-used APIs like cryptography, DDE, RPC, and shell functions, reducing compile time and binary size.
#define NOMINMAX // Prevents Windows from defining min() and max() macros, which can conflict with standard C++ functions.
#define NOCRYPT // Excludes Cryptographic APIs, such as Encrypt/Decrypt functions.
#define NOCOMM // Excludes serial communication APIs, such as COM port handling.
#define NOKANJI // Excludes Kanji character set support (not needed unless working with Japanese text processing).
#define NOHELP // Excludes Windows Help APIs, removing functions related to WinHelp and other help systems.
#include <windows.h>
#include <psapi.h>
#include <dwmapi.h>
#include <stdint.h>
#include <stdio.h>
void WINAPI_ShowError(const char *message, const char *title)
{
MessageBox(GetActiveWindow(), message, title, MB_OK | MB_ICONERROR);
}
void WINAPI_ShowWarning(const char *message, const char *title)
{
MessageBox(GetActiveWindow(), message, title, MB_OK | MB_ICONWARNING);
}
void WINAPI_ShowInformation(const char *message, const char *title)
{
MessageBox(GetActiveWindow(), message, title, MB_OK | MB_ICONINFORMATION);
}
void WINAPI_ShowQuestion(const char *message, const char *title)
{
MessageBox(GetActiveWindow(), message, title, MB_OKCANCEL | MB_ICONQUESTION);
}
void WINAPI_DisableErrorReporting()
{
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
}
void WINAPI_DisableWindowsGhosting()
{
DisableProcessWindowsGhosting();
}
void WINAPI_SetDarkMode(bool enable)
{
HWND window = GetActiveWindow();
int darkMode = enable ? 1 : 0;
if (DwmSetWindowAttribute(window, 20, &darkMode, sizeof(darkMode)) != S_OK)
DwmSetWindowAttribute(window, 19, &darkMode, sizeof(darkMode));
UpdateWindow(window);
}
bool WINAPI_IsSystemDarkMode()
{
HKEY hKey;
DWORD dwValue = 0;
DWORD dwSize = sizeof(DWORD);
DWORD dwType = REG_DWORD;
if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
if (RegQueryValueEx(hKey, "AppsUseLightTheme", NULL, &dwType, (LPBYTE)&dwValue, &dwSize) == ERROR_SUCCESS)
{
RegCloseKey(hKey);
return dwValue == 0;
}
RegCloseKey(hKey);
}
return false;
}

View file

@ -12,7 +12,6 @@ import flixel.math.FlxPoint;
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
import flixel.FlxCamera;
import openfl.system.System;
import funkin.FunkinMemory;
using StringTools;
@ -24,6 +23,20 @@ using StringTools;
@:nullSafety
class FunkinSprite extends FlxSprite
{
/**
* An internal list of all the textures cached with `cacheTexture`.
* This excludes any temporary textures like those from `FlxText` or `makeSolidColor`.
*/
static var currentCachedTextures:Map<String, FlxGraphic> = [];
/**
* An internal list of textures that were cached in the previous state.
* We don't know whether we want to keep them cached or not.
*/
static var previousCachedTextures:Map<String, FlxGraphic> = [];
static var permanentCachedTextures:Map<String, FlxGraphic> = [];
/**
* @param x Starting X position
* @param y Starting Y position
@ -203,40 +216,122 @@ class FunkinSprite extends FlxSprite
return FlxG.bitmap.get(key) != null;
}
@:deprecated("Use FunkinMemory.cacheTexture() instead")
/**
* Ensure the texture with the given key is cached.
* @param key The key of the texture to cache.
*/
public static function cacheTexture(key:String):Void
{
FunkinMemory.cacheTexture(Paths.image(key));
// We don't want to cache the same texture twice.
if (currentCachedTextures.exists(key)) return;
if (previousCachedTextures.exists(key))
{
// Move the graphic from the previous cache to the current cache.
var graphic = previousCachedTextures.get(key);
previousCachedTextures.remove(key);
if (graphic != null) currentCachedTextures.set(key, graphic);
return;
}
// Else, texture is currently uncached.
var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true);
if (graphic == null)
{
FlxG.log.warn('Failed to cache graphic: $key');
}
else
{
trace('Successfully cached graphic: $key');
graphic.persist = true;
currentCachedTextures.set(key, graphic);
}
}
@:deprecated("Use FunkinMemory.permanentCacheTexture() instead")
public static function permanentCacheTexture(key:String):Void
{
@:privateAccess FunkinMemory.permanentCacheTexture(Paths.image(key));
// We don't want to cache the same texture twice.
if (permanentCachedTextures.exists(key)) return;
// Else, texture is currently uncached.
var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true);
if (graphic == null)
{
FlxG.log.warn('Failed to cache graphic: $key');
}
else
{
trace('Successfully cached graphic: $key');
graphic.persist = true;
permanentCachedTextures.set(key, graphic);
}
currentCachedTextures = permanentCachedTextures;
}
@:deprecated("Use FunkinMemory.cacheTexture() instead")
public static function cacheSparrow(key:String):Void
{
FunkinMemory.cacheTexture(Paths.image(key));
cacheTexture(Paths.image(key));
}
@:deprecated("Use FunkinMemory.cacheTexture() instead")
public static function cachePacker(key:String):Void
{
FunkinMemory.cacheTexture(Paths.image(key));
cacheTexture(Paths.image(key));
}
@:deprecated("Use FunkinMemory.preparePurgeTextureCache() instead")
/**
* Call this, then `cacheTexture` to keep the textures we still need, then `purgeCache` to remove the textures that we won't be using anymore.
*/
public static function preparePurgeCache():Void
{
FunkinMemory.preparePurgeTextureCache();
previousCachedTextures = currentCachedTextures;
for (graphicKey in previousCachedTextures.keys())
{
if (!permanentCachedTextures.exists(graphicKey)) continue;
previousCachedTextures.remove(graphicKey);
}
currentCachedTextures = permanentCachedTextures;
}
@:deprecated("Use FunkinMemory.purgeCache() instead")
public static function purgeCache():Void
{
FunkinMemory.purgeCache();
// Everything that is in previousCachedTextures but not in currentCachedTextures should be destroyed.
for (graphicKey in previousCachedTextures.keys())
{
var graphic = previousCachedTextures.get(graphicKey);
if (graphic == null) continue;
FlxG.bitmap.remove(graphic);
graphic.destroy();
previousCachedTextures.remove(graphicKey);
}
@:privateAccess
if (FlxG.bitmap._cache == null)
{
@:privateAccess
FlxG.bitmap._cache = new Map();
System.gc();
return;
}
@:privateAccess
for (key in FlxG.bitmap._cache.keys())
{
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
if (obj == null) continue;
if (obj.persist) continue;
if (permanentCachedTextures.exists(key)) continue;
if (!(obj.useCount <= 0 || key.contains("characters") || key.contains("charSelect") || key.contains("results"))) continue;
FlxG.bitmap.removeKey(key);
obj.destroy();
}
openfl.Assets.cache.clear("songs");
openfl.Assets.cache.clear("sounds");
openfl.Assets.cache.clear("music");
System.gc();
}
static function isGraphicCached(graphic:FlxGraphic):Bool

View file

@ -71,7 +71,7 @@ class FlxAtlasSprite extends FlxAnimate
onAnimationComplete.add(cleanupAnimation);
// This defaults the sprite to play the first animation in the atlas,
// then pauses it. This ensures symbols are initialized properly.
// then pauses it. This ensures symbols are intialized properly.
this.anim.play('');
this.anim.pause();

View file

@ -17,7 +17,7 @@ class PureColor extends FlxShader
function set_col(val:FlxColor):FlxColor
{
funnyColor.value = [val.redFloat, val.greenFloat, val.blueFloat, val.alphaFloat];
funnyColor.value = [val.red, val.green, val.blue, val.alpha];
return val;
}
@ -33,7 +33,7 @@ class PureColor extends FlxShader
vec4 color = flixel_texture2D(bitmap, openfl_TextureCoordv);
if (color.a > 0.0 && colSet)
color = funnyColor * color.a;
color = vec4(funnyColor.r, funnyColor.g, funnyColor.b, color.a);
gl_FragColor = color;
}

View file

@ -1,6 +1,5 @@
package funkin.external.android;
package funkin.mobile.external.android;
#if android
import lime.system.JNI;
import flixel.util.FlxSignal;
@ -10,6 +9,7 @@ import flixel.util.FlxSignal;
@:unreflective
class CallbackUtil
{
#if android
/**
* The result code for `DATA_FOLDER_CLOSED` activity.
*/
@ -42,6 +42,7 @@ class CallbackUtil
initCallBackJNI(new CallbackHandler());
}
}
#end
}
/**
@ -49,7 +50,8 @@ class CallbackUtil
*/
class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
{
@:allow(funkin.external.android.CallbackUtil)
#if android
@:allow(funkin.mobile.external.android.CallbackUtil)
function new():Void {}
/**
@ -66,5 +68,5 @@ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
{
if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode);
}
#end
}
#end

View file

@ -1,12 +1,12 @@
package funkin.external.android;
package funkin.mobile.external.android;
#if android
/**
* A Utility class to manage the Application's Data folder on Android.
*/
@:unreflective
class DataFolderUtil
{
#if android
/**
* Opens the data folder on an Android device using JNI.
*/
@ -19,5 +19,5 @@ class DataFolderUtil
openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED);
}
}
#end
}
#end

View file

@ -1,4 +1,4 @@
package funkin.external.android;
package funkin.mobile.external.android;
#if android
import lime.system.JNI;

View file

@ -1,6 +1,5 @@
package funkin.external.android;
package funkin.mobile.external.android;
#if android
import lime.math.Rectangle;
import lime.system.JNI;
@ -10,6 +9,7 @@ import lime.system.JNI;
@:unreflective
class ScreenUtil
{
#if android
/**
* Retrieves the dimensions of display cutouts (such as notches) on Android devices.
*
@ -48,5 +48,5 @@ class ScreenUtil
return [];
}
#end
}
#end

View file

@ -1,10 +1,9 @@
package funkin.external.ios;
package funkin.mobile.external.ios;
#if ios
/**
* A Utility class to manage iOS audio.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
@:include('AudioSession.hpp')
@:unreflective
extern class AudioSession
@ -14,4 +13,3 @@ extern class AudioSession
@:native('setActive')
static function setActive(active:Bool):Void;
}
#end

View file

@ -1,10 +1,9 @@
package funkin.external.ios;
package funkin.mobile.external.ios;
#if ios
/**
* A Utility class to get iOS screen related informations.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
@:include('ScreenUtil.hpp')
@:unreflective
extern class ScreenUtil
@ -15,4 +14,3 @@ extern class ScreenUtil
@:native('getScreenSize')
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
}
#end

View file

@ -125,7 +125,7 @@ class ControlsHandler
@:noCompletion
private static function get_hasExternalInputDevice():Bool
{
return FlxG.gamepads.numActiveGamepads > 0 #if android || extension.androidtools.Tools.isChromebook() #end;
return FlxG.gamepads.numActiveGamepads > 0;
}
@:noCompletion

View file

@ -0,0 +1,37 @@
package funkin.mobile.macros;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.xml.Printer;
import sys.FileSystem;
using haxe.io.Path;
/**
* This class provides a macro to include an XML build file in the metadata of a Haxe class.
* The file must be located relative to the directory of the Haxe class that uses this macro.
*/
@:nullSafety
class LinkerMacro
{
/**
* Adds an XML `<include>` element to the class's metadata, pointing to a specified build file.
* @param file_name The name of the XML file to include. Defaults to `Build.xml` if not provided.
* @return An array of fields that are processed during the build.
*/
public static macro function xml(?file_name:String = 'Build.xml'):Array<Field>
{
final pos:Position = Context.currentPos();
final sourcePath:String = FileSystem.absolutePath(Context.getPosInfos(pos).file.directory()).removeTrailingSlashes();
final fileToInclude:String = Path.join([sourcePath, file_name?.length > 0 ? file_name : 'Build.xml']);
if (!FileSystem.exists(fileToInclude)) Context.error('The specified file "$fileToInclude" could not be found at "$sourcePath".', pos);
final includeElement:Xml = Xml.createElement('include');
includeElement.set('name', fileToInclude);
Context.getLocalClass().get().meta.add(':buildXml', [
{expr: EConst(CString(Printer.print(includeElement, true))), pos: pos}], pos);
return Context.getBuildFields();
}
}

View file

@ -15,14 +15,7 @@ class FunkinBackButton extends FunkinButton
public var enabled:Bool = true;
public var confirming(get, never):Bool;
function get_confirming():Bool
{
return _confirming;
}
var _confirming:Bool = false;
var confirming:Bool = false;
public var restingOpacity:Float;
@ -93,7 +86,7 @@ class FunkinBackButton extends FunkinButton
return;
}
_confirming = true;
confirming = true;
FlxTween.cancelTweensOf(this);
HapticUtil.vibrate(0, 0.05, 0.5);
@ -105,7 +98,7 @@ class FunkinBackButton extends FunkinButton
animation.onFinish.addOnce(function(name:String) {
if (name != 'confirm') return;
_confirming = false;
confirming = false;
held = false;
onConfirmEnd.dispatch();
});
@ -134,7 +127,7 @@ class FunkinBackButton extends FunkinButton
onDown.removeAll();
onOut.removeAll();
_confirming = false;
confirming = false;
held = false;
onUp.add(playConfirmAnim);

View file

@ -12,16 +12,7 @@ class FunkinOptionsButton extends FunkinButton
public var onConfirmStart(default, null):FlxSignal = new FlxSignal();
public var onConfirmEnd(default, null):FlxSignal = new FlxSignal();
public var enabled:Bool = true;
public var confirming(get, never):Bool;
function get_confirming():Bool
{
return _confirming;
}
var _confirming:Bool = false;
var confirming:Bool = false;
var instant:Bool = false;
var held:Bool = false;
@ -58,7 +49,7 @@ class FunkinOptionsButton extends FunkinButton
function playHoldAnim():Void
{
if (confirming || held || !enabled) return;
if (confirming || held) return;
held = true;
@ -79,7 +70,7 @@ class FunkinOptionsButton extends FunkinButton
return;
}
_confirming = true;
confirming = true;
FlxTween.cancelTweensOf(this);
HapticUtil.vibrate(0, 0.05, 0.5);
@ -95,20 +86,17 @@ class FunkinOptionsButton extends FunkinButton
animation.onFinish.addOnce(function(name:String) {
if (name != 'confirm') return;
_confirming = false;
held = false;
onConfirmEnd.dispatch();
});
}
function playOutAnim():Void
{
if (confirming || !enabled) return;
if (confirming) return;
FlxTween.cancelTweensOf(this);
HapticUtil.vibrate(0, 0.01, 0.2);
animation.play('idle');
held = false;
}
public function resetCallbacks():Void
@ -117,7 +105,7 @@ class FunkinOptionsButton extends FunkinButton
onDown.removeAll();
onOut.removeAll();
_confirming = false;
confirming = false;
held = false;
onUp.add(playConfirmAnim);

View file

@ -270,7 +270,7 @@ class ControlsSchemeMenu extends MusicBeatSubState
*/
function setSelection(index:Int):Void
{
final newIndex:Int = Math.floor(index.clamp(0, hitboxShowcases.length - 1));
final newIndex:Int = Math.floor(FlxMath.bound(index, 0, hitboxShowcases.length - 1));
if (currentIndex != newIndex)
{
@ -348,7 +348,7 @@ class ControlsSchemeMenu extends MusicBeatSubState
hitboxShowcases.x = MathUtil.smoothLerpPrecision(hitboxShowcases.x, showcasesTargetX, elapsed, 0.5);
final minShowcasesX:Float = -1500 * availableSchemes.length;
hitboxShowcases.x = hitboxShowcases.x.clamp(minShowcasesX, 400);
hitboxShowcases.x = FlxMath.bound(hitboxShowcases.x, minShowcasesX, 400);
final targetIndex:Int = Math.round(hitboxShowcases.x / -1500);
@ -356,9 +356,8 @@ class ControlsSchemeMenu extends MusicBeatSubState
}
else
{
hitboxShowcases.x = MathUtil.smoothLerpPrecision(hitboxShowcases.x, (-1500 * currentIndex) + (-1500 / (availableSchemes.length + 1) * currentIndex),
elapsed, 0.5);
hitboxShowcases.x = MathUtil.smoothLerpPrecision(hitboxShowcases.x, (-1500 * currentIndex) + (-1500 / (availableSchemes.length + 1) * currentIndex), elapsed, 0.5);
}
}
#end
}
}

View file

@ -29,7 +29,7 @@ class AdMobUtil
/**
* AdMob publisher ID used for the application.
*/
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER");
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("GLOBAL_ADMOB_PUBLISHER");
/**
* Test ad unit IDs for development and testing purposes.

View file

@ -1,9 +1,9 @@
package funkin.mobile.util;
#if ios
import funkin.external.ios.ScreenUtil as NativeScreenUtil;
import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil;
#elseif android
import funkin.external.android.ScreenUtil as NativeScreenUtil;
import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil;
#end
import lime.math.Rectangle;
import lime.system.System;

View file

@ -1,54 +0,0 @@
package funkin.modding;
import haxe.ds.StringMap;
/**
* Temporary persistent data storage for mods to use.
*/
@:nullSafety
class ModStore
{
/**
* All registered stores for this session.
*/
public static final stores:StringMap<Dynamic> = new StringMap<Dynamic>();
/**
* Attempts to register a new store with the given ID and return it.
* If a store with the same ID already exists, that store will be returned instead (discards `data`).
*
* @id The unique ID for this store.
* @data Optional initial data, uses an empty object by default.
* @return The store data at the given ID.
*/
public static function register(id:String, ?data:Dynamic):Dynamic
{
if (stores.exists(id)) return stores.get(id);
stores.set(id, data ??= {});
return data;
}
/**
* Helper function to get a store by ID.
*
* @id The target ID of the store.
* @return The store data, or `null` if the store did not exist.
*/
public static function get(id:String):Null<Dynamic>
{
return stores.get(id);
}
/**
* Helper function to remove a store by ID and return it.
*
* @id The target ID of the store.
* @return The store data, or `null` if the store did not exist.
*/
public static function remove(id:String):Null<Dynamic>
{
var data:Null<Dynamic> = stores.get(id);
stores.remove(id);
return data;
}
}

View file

@ -5,6 +5,19 @@ import polymod.Polymod;
@:nullSafety
class PolymodErrorHandler
{
/**
* Show a popup with the given text.
* This displays a system popup, it WILL interrupt the game.
* Make sure to only use this when it's important, like when there's a script error.
*
* @param name The name at the top of the popup.
* @param desc The body text of the popup.
*/
public static function showAlert(name:String, desc:String):Void
{
lime.app.Application.current.window.alert(desc, name);
}
public static function onPolymodError(error:PolymodError):Void
{
// Perform an action based on the error code.
@ -24,12 +37,12 @@ class PolymodErrorHandler
// A syntax error when parsing a script.
logError(error.message);
// Notify the user via popup.
funkin.util.WindowUtil.showError('Polymod Script Parsing Error', error.message);
showAlert('Polymod Script Parsing Error', error.message);
case SCRIPT_RUNTIME_EXCEPTION:
// A runtime error when running a script.
logError(error.message);
// Notify the user via popup.
funkin.util.WindowUtil.showError('Polymod Script Exception', error.message);
showAlert('Polymod Script Exception', error.message);
case SCRIPT_CLASS_MODULE_NOT_FOUND:
// A scripted class tried to reference an unknown class or module.
logError(error.message);
@ -41,12 +54,13 @@ class PolymodErrorHandler
msg += '\nCheck to ensure the class exists and is spelled correctly.';
// Notify the user via popup.
funkin.util.WindowUtil.showError('Polymod Script Import Error', msg);
showAlert('Polymod Script Import Error', msg);
case SCRIPT_CLASS_MODULE_BLACKLISTED:
// A scripted class tried to reference a blacklisted class or module.
logError(error.message);
// Notify the user via popup.
funkin.util.WindowUtil.showError('Polymod Script Blacklist Violation', error.message);
showAlert('Polymod Script Blacklist Violation', error.message);
default:
// Log the message based on its severity.
switch (error.severity)

View file

@ -1,9 +1,9 @@
package funkin.modding;
import polymod.fs.ZipFileSystem;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
@ -267,11 +267,6 @@ class PolymodHandler
Polymod.addImportAlias('funkin.modding.base.ScriptedMusicBeatState', funkin.ui.ScriptedMusicBeatState);
Polymod.addImportAlias('funkin.modding.base.ScriptedMusicBeatSubState', funkin.ui.ScriptedMusicBeatSubState);
// Backward compatibility for some classes that moved.
Polymod.addImportAlias('funkin.data.dialogue.conversation.ConversationRegistry', funkin.data.dialogue.ConversationRegistry);
Polymod.addImportAlias('funkin.data.dialogue.dialoguebox.DialogueBoxRegistry', funkin.data.dialogue.DialogueBoxRegistry);
Polymod.addImportAlias('funkin.data.dialogue.speaker.SpeakerRegistry', funkin.data.dialogue.SpeakerRegistry);
// `funkin.util.FileUtil` has unrestricted access to the file system.
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
@ -562,10 +557,12 @@ class PolymodHandler
Polymod.clearScripts();
// Forcibly reload Polymod so it finds any new files.
// This will also register all scripts.
// TODO: Replace this with loadEnabledMods().
funkin.modding.PolymodHandler.loadAllMods();
// Reload scripted classes so stages and modules will update.
Polymod.registerAllScriptClasses();
// Reload everything that is cached.
// Currently this freezes the game for a second but I guess that's tolerable?

View file

@ -4,18 +4,6 @@ import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
import funkin.modding.events.ScriptEvent;
/**
* Parameters used to initialize a module.
*/
typedef ModuleParams =
{
/**
* The state this module is associated with.
* If set, this module will only receive events when the game is in this state.
*/
?state:Class<Dynamic>
}
/**
* A module is a scripted class which receives all events without requiring a specific context.
* You may have the module active at all times, or only when another script enables it.
@ -51,27 +39,16 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
return value;
}
/**
* The state this module is associated with.
* If set, this module will only receive events when the game is in this state.
*/
public var state:Null<Class<Dynamic>> = null;
/**
* Called when the module is initialized.
* It may not be safe to reference other modules here since they may not be loaded yet.
*
* NOTE: To make the module start inactive, call `this.active = false` in the constructor.
*/
public function new(moduleId:String, priority:Int = 1000, ?params:ModuleParams):Void
public function new(moduleId:String, priority:Int = 1000):Void
{
this.moduleId = moduleId;
this.priority = priority;
if (params != null)
{
this.state = params.state ?? null;
}
}
public function toString()

View file

@ -6,7 +6,6 @@ import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.module.Module;
import funkin.modding.module.ScriptedModule;
import flixel.FlxG;
/**
* Utility functions for loading and manipulating active modules.
@ -146,14 +145,6 @@ class ModuleHandler
// The module needs to be active to receive events.
if (module != null && module.active)
{
if (module.state != null)
{
// Only call the event if the current state is what the module's state is.
if (!(Type.getClass(FlxG.state) == module.state) && !(Type.getClass(FlxG.state?.subState) == module.state))
{
continue;
}
}
ScriptEventDispatcher.callEvent(module, event);
}
}

View file

@ -21,6 +21,7 @@ import funkin.util.MathUtil;
import funkin.effects.RetroCameraFade;
import flixel.math.FlxPoint;
import funkin.util.TouchUtil;
import openfl.utils.Assets;
#if FEATURE_MOBILE_ADVERTISEMENTS
import funkin.mobile.util.AdMobUtil;
#end
@ -101,6 +102,18 @@ class GameOverSubState extends MusicBeatSubState
var canInput:Bool = false;
var justDied:Bool = true;
var isSpecialAnimation:Bool = false;
var gameOverVibrationPreset:VibrationPreset =
{
period: 0,
duration: Constants.DEFAULT_VIBRATION_DURATION,
amplitude: Constants.MIN_VIBRATION_AMPLITUDE,
sharpness: Constants.DEFAULT_VIBRATION_SHARPNESS
};
public function new(params:GameOverParams)
{
super();
@ -162,7 +175,7 @@ class GameOverSubState extends MusicBeatSubState
if ((parentPlayState?.isMinimalMode ?? true)) {}
else
{
boyfriend = parentPlayState?.currentStage?.getBoyfriend(true);
boyfriend = parentPlayState?.currentStage.getBoyfriend(true);
if (boyfriend != null)
{
boyfriend.canPlayOtherAnims = true;
@ -185,17 +198,16 @@ class GameOverSubState extends MusicBeatSubState
addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack);
#end
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
// Allow input a second later to prevent accidental gameover skips.
new FlxTimer().start(1, function(tmr:FlxTimer) {
canInput = true;
});
}
@:nullSafety(Off)
function setCameraTarget():Void
{
if (parentPlayState == null || parentPlayState.isMinimalMode || boyfriend == null) return;
if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return;
// Assign a camera follow point to the boyfriend's position.
cameraFollowPoint = new FlxObject(parentPlayState.cameraFollowPoint.x, parentPlayState.cameraFollowPoint.y, 1, 1);
@ -206,7 +218,6 @@ class GameOverSubState extends MusicBeatSubState
cameraFollowPoint.y += offsets[1];
add(cameraFollowPoint);
@:nullSafety(Off)
FlxG.camera.target = null;
FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE / 2);
targetCameraZoom = (parentPlayState?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom();
@ -319,6 +330,9 @@ class GameOverSubState extends MusicBeatSubState
}
}
// Handle vibrations on update.
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
// Start death music before firstDeath gets replaced
super.update(elapsed);
}
@ -394,7 +408,7 @@ class GameOverSubState extends MusicBeatSubState
// Readd Boyfriend to the stage.
boyfriend.isDead = false;
remove(boyfriend);
parentPlayState?.currentStage?.addCharacter(boyfriend, BF);
parentPlayState?.currentStage.addCharacter(boyfriend, BF);
}
// Snap reset the camera which may have changed because of the player character data.
@ -440,7 +454,7 @@ class GameOverSubState extends MusicBeatSubState
#else
resetPlaying();
#end
}, true);
});
}
});
}
@ -564,11 +578,11 @@ class GameOverSubState extends MusicBeatSubState
PlayStatePlaylist.reset();
}
var stickerPackId:Null<String> = parentPlayState?.currentChart?.stickerPack;
var stickerPackId:Null<String> = parentPlayState?.currentChart.stickerPack;
if (stickerPackId == null)
{
var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart?.characters.player);
var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart.characters.player);
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER);
if (playerCharacter != null)
@ -601,6 +615,108 @@ class GameOverSubState extends MusicBeatSubState
var hasPlayedDeathQuote:Bool = false;
/**
* Used for death haptics.
*/
var startedTimerHaptics:Bool = false;
/**
* Unique vibrations for each death animation.
*/
function handleAnimationVibrations():Void
{
if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return;
if (justDied)
{
if (isSpecialAnimation)
{
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION * 5);
trace("It's a special game over animation.");
}
else
{
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
}
justDied = false;
}
if (boyfriend.animation == null) return;
final curFrame:Int = (boyfriend.animation.curAnim != null) ? boyfriend.animation.curAnim.curFrame : -1;
if (boyfriend.characterId.startsWith("bf"))
{
// BF's mic drops.
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 27)
{
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
}
// BF's balls pulsating.
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && (curFrame == 0 || curFrame == 18))
{
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
}
return;
}
// Pico dies because of Darnell beating him up.
if (boyfriend.characterId == "pico-blazin")
{
if (!startedTimerHaptics)
{
startedTimerHaptics = true;
new FlxTimer().start(0.5, function(tmr:FlxTimer) {
// Pico falls on his knees.
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
new FlxTimer().start(0.6, function(tmr:FlxTimer) {
// Pico falls "asleep". :)
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
});
});
return;
}
}
else if (boyfriend.characterId.startsWith("pico") && boyfriend.characterId != "pico-holding-nene")
{
if (isSpecialAnimation)
{
if (startedTimerHaptics) return;
startedTimerHaptics = true;
// Death by Darnell's can.
new FlxTimer().start(1.85, function(tmr:FlxTimer) {
// Pico falls on his knees.
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
});
}
else
{
// Pico falls on his back.
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 20)
{
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
}
// Blood firework woohoo!!!!
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && curFrame % 2 == 0)
{
final randomAmplitude:Float = FlxG.random.float(Constants.MIN_VIBRATION_AMPLITUDE / 100, Constants.MIN_VIBRATION_AMPLITUDE);
final randomDuration:Float = FlxG.random.float(Constants.DEFAULT_VIBRATION_DURATION / 10, Constants.DEFAULT_VIBRATION_DURATION);
HapticUtil.vibrate(0, randomDuration, randomAmplitude);
}
}
return;
}
}
public override function destroy():Void
{
super.destroy();

View file

@ -18,9 +18,9 @@ class GitarooPause extends MusicBeatState
var replaySelect:Bool = false;
var previousParams:Null<PlayStateParams>;
var previousParams:PlayStateParams;
public function new(?previousParams:PlayStateParams):Void
public function new(previousParams:PlayStateParams):Void
{
super();

View file

@ -213,11 +213,6 @@ class PauseSubState extends MusicBeatSubState
*/
var menuEntryText:FlxTypedSpriteGroup<AtlasText>;
/**
* Callback that gets called once substate gets open.
*/
var onPause:Void->Void;
// ===============
// Audio Variables
// ===============
@ -227,11 +222,10 @@ class PauseSubState extends MusicBeatSubState
// Constructor
// ===============
public function new(?params:PauseSubStateParams, ?onPause:Void->Void)
public function new(?params:PauseSubStateParams)
{
super();
this.currentMode = params?.mode ?? Standard;
this.onPause = onPause;
}
// ===============
@ -250,8 +244,6 @@ class PauseSubState extends MusicBeatSubState
AdMobUtil.addBanner(extension.admob.AdmobBannerSize.BANNER, extension.admob.AdmobBannerAlign.TOP_LEFT);
#end
if (onPause != null) onPause();
super.create();
startPauseMusic();
@ -294,7 +286,6 @@ class PauseSubState extends MusicBeatSubState
hapticTimer.cancel();
hapticTimer = null;
pauseMusic.stop();
onPause = null;
}
// ===============
@ -939,8 +930,7 @@ class PauseSubState extends MusicBeatSubState
*/
static function changeDifficulty(state:PauseSubState, difficulty:String):Void
{
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase(),
{variation: PlayState.instance.currentChart.variation});
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
// Reset campaign score when changing difficulty
// So if you switch difficulty on the last song of a week you get a really low overall score.

File diff suppressed because it is too large Load diff

View file

@ -527,7 +527,8 @@ class ResultState extends MusicBeatSubState
bgFlash.visible = true;
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100;
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors.
@ -743,7 +744,6 @@ class ResultState extends MusicBeatSubState
super.draw();
songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
clearPercentSmall.forEachAlive(spr -> spr.clipRect = FlxRect.get(Math.max(0, 520 - spr.x), 0, FlxG.width, spr.height));
// PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
@ -751,6 +751,105 @@ class ResultState extends MusicBeatSubState
// maskShaderSongName.frameUV = songName.frame.uv;
}
private function handleAnimationVibrations()
{
for (atlas in characterAtlasAnimations)
{
if (atlas == null || atlas.sprite == null) continue;
switch (rank)
{
case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD:
switch (playerCharacterId)
{
// Feel the bed fun :freaky:
case "bf":
if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0)
{
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
// GF slams into the wall.
if (atlas.sprite.anim.curFrame == 51)
{
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
break;
}
// Pico drop-kicking Nene.
case "pico":
if (atlas.sprite.anim.curFrame == 52)
{
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
case ScoringRank.GREAT | ScoringRank.EXCELLENT:
switch (playerCharacterId)
{
// Pico explodes the targets with a rocket launcher.
case "pico":
// Pico shoots.
if (atlas.sprite.anim.curFrame == 45)
{
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
break;
}
// The targets explode.
if (atlas.sprite.anim.curFrame == 50)
{
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
case ScoringRank.GOOD:
switch (playerCharacterId)
{
// Pico shooting the targets.
case "pico":
if (atlas.sprite.anim.curFrame % 2 != 0) continue;
final frames:Array<Array<Int>> = [[40, 50], [80, 90], [140, 157]];
for (i in 0...frames.length)
{
if (atlas.sprite.anim.curFrame < frames[i][0] || atlas.sprite.anim.curFrame > frames[i][1]) continue;
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
case ScoringRank.SHIT:
switch (playerCharacterId)
{
// BF falling and GF slams on BF with her ass.
case "bf":
if (atlas.sprite.anim.curFrame == 5 || atlas.sprite.anim.curFrame == 90)
{
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 2, Constants.DEFAULT_VIBRATION_DURATION * 2, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
}
}
}
override function update(elapsed:Float):Void
{
maskShaderDifficulty.swagSprX = difficulty.x;
@ -832,10 +931,7 @@ class ResultState extends MusicBeatSubState
var stickerPackId:Null<String> = null;
var song:Null<Song> = params.songId == null ? null : SongRegistry.instance.fetchEntry(params.songId,
{
variation: params?.variationId
});
var song:Null<Song> = params.songId == null ? null : SongRegistry.instance.fetchEntry(params.songId);
if (song != null)
{
@ -939,6 +1035,8 @@ class ResultState extends MusicBeatSubState
#end
}
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
super.update(elapsed);
}

View file

@ -580,7 +580,7 @@ class AnimateAtlasCharacter extends BaseCharacter
override function set_alpha(value:Float):Float
{
value = value.clamp(0, 1);
value = FlxMath.bound(value, 0, 1);
if (exists && alpha != value)
{

View file

@ -288,23 +288,35 @@ class CharacterDataParser
{
var charPath:String = "freeplay/icons/";
final charIDParts:Array<String> = char.split("-");
var iconName:String = "";
var lastValidIconName:String = "";
for (i in 0...charIDParts.length)
// FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
switch (char)
{
iconName += charIDParts[i];
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
{
lastValidIconName = iconName;
}
if (i < charIDParts.length - 1) iconName += '-';
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark":
charPath += "bfpixel";
case "monster-christmas":
charPath += "monsterpixel";
case "mom" | "mom-car":
charPath += "mommypixel";
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
charPath += "picopixel";
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
charPath += "gfpixel";
case "dad":
charPath += "dadpixel";
case "darnell-blazin":
charPath += "darnellpixel";
case "senpai-angry":
charPath += "senpaipixel";
case "spooky-dark":
charPath += "spookypixel";
case "tankman-atlas" | "tankman-bloody":
charPath += "tankmanpixel";
case "pico-christmas" | "pico-dark":
charPath += "picopixel";
default:
charPath += '${char}pixel';
}
charPath += '${lastValidIconName}pixel';
if (!Assets.exists(Paths.image(charPath)))
{
trace('[WARN] Character ${char} has no freeplay icon.');

View file

@ -94,7 +94,6 @@ class MultiSparrowCharacter extends BaseCharacter
{
trace('Concatenating multi-sparrow atlas: ${asset}');
subTexture.parent.destroyOnNoUse = false;
FunkinMemory.cacheTexture(Paths.image(asset));
}
texture.addAtlas(subTexture);

View file

@ -115,8 +115,6 @@ class HealthIcon extends FunkinSprite
*/
static final POSITION_OFFSET:Int = 26;
public var iconOffset:FlxPoint = FlxPoint.get();
public function new(char:Null<String>, playerId:Int = 0)
{
super(0, 0);
@ -153,12 +151,10 @@ class HealthIcon extends FunkinSprite
*/
public function toggleOldIcon():Void
{
final playState:Null<PlayState> = PlayState.instance;
if (playState == null || playState.currentStage == null) return;
if (characterId == 'bf-old')
{
isPixel = playState.currentStage.getBoyfriend()?.isPixel ?? false;
playState.currentStage.getBoyfriend()?.initHealthIcon(false);
isPixel = PlayState.instance.currentStage.getBoyfriend().isPixel;
PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false);
}
else
{
@ -184,7 +180,8 @@ class HealthIcon extends FunkinSprite
loadCharacter(characterId);
this.size.set(1.0, 1.0);
this.iconOffset.set();
this.offset.x = 0.0;
this.offset.y = 0.0;
this.flipX = false;
}
else
@ -195,15 +192,8 @@ class HealthIcon extends FunkinSprite
loadCharacter(characterId);
this.size.set(data.scale ?? 1.0, data.scale ?? 1.0);
if (data.offsets != null && data.offsets.length == 2)
{
this.iconOffset.set(data.offsets[0], data.offsets[1]);
}
else
{
this.iconOffset.set(0, 0);
}
this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0;
this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0;
this.flipX = data.flipX ?? false; // Face the OTHER way by default, since that is more common.
}
}
@ -299,8 +289,6 @@ class HealthIcon extends FunkinSprite
// Keep the icon centered vertically on the health bar.
this.y = PlayState.instance.healthBar.y - (this.height / 2); // - (PlayState.instance.healthBar.height / 2)
offset += iconOffset;
}
}

View file

@ -66,7 +66,7 @@ class VideoCutscene
if (!openfl.Assets.exists(filePath))
{
// Display a popup.
// funkin.util.WindowUtil.showError('Error playing video', 'Video file does not exist: ${filePath}');
// lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video');
// return;
// TODO: After moving videos to their own library,

View file

@ -6,11 +6,11 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
import funkin.audio.FunkinSound;
import funkin.data.dialogue.ConversationData;
import funkin.data.dialogue.ConversationData.DialogueEntryData;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.dialogue.conversation.ConversationData;
import funkin.data.dialogue.conversation.ConversationData.DialogueEntryData;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.IRegistryEntry;
import funkin.graphics.FunkinSprite;
import funkin.modding.events.ScriptEvent;
@ -89,7 +89,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
var currentDialogueBox:Null<DialogueBox>;
public function new(id:String, ?params:Dynamic)
public function new(id:String)
{
super();

View file

@ -11,8 +11,8 @@ import funkin.audio.FunkinSound;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import flixel.util.FlxColor;
import funkin.ui.FullScreenScaleMode;
import funkin.data.dialogue.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData>
{
@ -106,7 +106,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
return FullScreenScaleMode.wideScale.x - 0.05;
}
public function new(id:String, ?params:Dynamic)
public function new(id:String)
{
super();
this.id = id;

View file

@ -6,8 +6,8 @@ import funkin.modding.events.ScriptEvent;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.util.assets.FlxAnimationUtil;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import funkin.data.dialogue.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.dialogue.speaker.SpeakerData;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.ui.FullScreenScaleMode;
/**
@ -86,7 +86,7 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
return FullScreenScaleMode.wideScale.x - 0.05;
}
public function new(id:String, ?params:Dynamic)
public function new(id:String)
{
super();

View file

@ -186,7 +186,6 @@ class FocusCameraSongEvent extends SongEvent
name: 'duration',
title: 'Duration',
defaultValue: 4.0,
min: 0,
step: 0.5,
type: SongEventFieldType.FLOAT,
units: 'steps'

View file

@ -108,7 +108,6 @@ class ScrollSpeedEvent extends SongEvent
name: 'scroll',
title: 'Target Value',
defaultValue: 1.0,
min: 0.1,
step: 0.1,
type: SongEventFieldType.FLOAT,
units: 'x'
@ -117,7 +116,6 @@ class ScrollSpeedEvent extends SongEvent
name: 'duration',
title: 'Duration',
defaultValue: 4.0,
min: 0,
step: 0.5,
type: SongEventFieldType.FLOAT,
units: 'steps'

View file

@ -42,15 +42,12 @@ class SetCameraBopSongEvent extends SongEvent
var rate:Null<Int> = data.getInt('rate');
if (rate == null) rate = Constants.DEFAULT_ZOOM_RATE;
var offset:Null<Int> = data.getInt('offset');
if (rate == null) offset = Constants.DEFAULT_ZOOM_OFFSET;
var intensity:Null<Float> = data.getFloat('intensity');
if (intensity == null) intensity = 1.0;
PlayState.instance.cameraBopIntensity = (Constants.DEFAULT_BOP_INTENSITY - 1.0) * intensity + 1.0;
PlayState.instance.hudCameraZoomIntensity = (Constants.DEFAULT_BOP_INTENSITY - 1.0) * intensity * 2.0;
PlayState.instance.cameraZoomRate = rate;
PlayState.instance.cameraZoomRateOffset = offset;
trace('Set camera zoom rate to ${PlayState.instance.cameraZoomRate}');
}
@ -75,24 +72,14 @@ class SetCameraBopSongEvent extends SongEvent
name: 'intensity',
title: 'Intensity',
defaultValue: 1.0,
min: 0,
step: 0.1,
type: SongEventFieldType.FLOAT,
units: 'x'
},
{
name: 'offset',
title: 'Offset',
defaultValue: 0,
step: 1,
type: SongEventFieldType.INTEGER,
units: 'beats'
},
{
name: 'rate',
title: 'Rate',
defaultValue: 4,
min: 0,
step: 1,
type: SongEventFieldType.INTEGER,
units: 'beats/zoom'

View file

@ -90,7 +90,6 @@ class SetHealthIconSongEvent extends SongEvent
name: 'scale',
title: 'Scale',
defaultValue: 1.0,
min: 0,
type: SongEventFieldType.FLOAT,
},
{

View file

@ -110,7 +110,6 @@ class ZoomCameraSongEvent extends SongEvent
name: 'zoom',
title: 'Zoom Level',
defaultValue: 1.0,
min: 0,
step: 0.05,
type: SongEventFieldType.FLOAT,
units: 'x'
@ -119,7 +118,6 @@ class ZoomCameraSongEvent extends SongEvent
name: 'duration',
title: 'Duration',
defaultValue: 4.0,
min: 0,
step: 0.5,
type: SongEventFieldType.FLOAT,
units: 'steps'

View file

@ -10,9 +10,6 @@ class NoteSprite extends FunkinSprite
{
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];
/**
* The hold note sprite for this note.
*/
public var holdNoteSprite:SustainTrail;
var hsvShader:HSVShader;
@ -98,23 +95,8 @@ class NoteSprite extends FunkinSprite
return this.direction;
}
/**
* The note data associated with this note sprite.
* This is used to store the strum time, length, and other properties.
*/
public var noteData:SongNoteData;
/**
* If this note kind is scoreable (i.e., counted towards score and accuracy)
* Only accessible in scripts
* Defaults to true
*/
public var scoreable:Bool = true;
/**
* Whether this note is a hold note.
* This is true if the length is greater than 0.
*/
public var isHoldNote(get, never):Bool;
function get_isHoldNote():Bool
@ -185,6 +167,8 @@ class NoteSprite extends FunkinSprite
{
noteStyle.buildNoteSprite(this);
this.shader = hsvShader;
// `false` disables the update() function for performance.
this.active = noteStyle.isNoteAnimated();
}
@ -237,13 +221,11 @@ class NoteSprite extends FunkinSprite
public function desaturate():Void
{
this.hsvShader.saturation = 0.2;
this.shader = this.hsvShader;
}
public function setHue(hue:Float):Void
{
this.hsvShader.hue = hue;
if (hue != 1.0) this.shader = this.hsvShader;
}
public override function revive():Void
@ -256,12 +238,7 @@ class NoteSprite extends FunkinSprite
this.hasBeenHit = false;
this.mayHit = false;
this.hasMissed = false;
this.handledMiss = false;
this.holdNoteSprite = null;
// The hsvShader should only be applied when it's necessary.
// Otherwise, it should be turned off to keep note batching.
this.shader = null;
this.hsvShader.hue = 1.0;
this.hsvShader.saturation = 1.0;
this.hsvShader.value = 1.0;

View file

@ -17,7 +17,6 @@ import funkin.play.notes.NoteVibrationsHandler;
import funkin.data.song.SongData.SongNoteData;
import funkin.util.SortUtil;
import funkin.util.GRhythmUtil;
import funkin.play.notes.notekind.NoteKind;
import funkin.play.notes.notekind.NoteKindManager;
import flixel.math.FlxPoint;
#if mobile
@ -69,8 +68,7 @@ class Strumline extends FlxSpriteGroup
function get_renderDistanceMs():Float
{
if (useCustomRenderDistance) return customRenderDistanceMs;
// Only divide by lower scroll speeds to fix renderDistance being too short. Dividing by higher scroll speeds breaks the input system by hitting later notes first!
return FlxG.height / Constants.PIXELS_PER_MS / (scrollSpeed < 1 ? scrollSpeed : 1);
return FlxG.height / Constants.PIXELS_PER_MS / scrollSpeed;
}
/**
@ -98,9 +96,9 @@ class Strumline extends FlxSpriteGroup
/**
* Reset the scroll speed to the current chart's scroll speed.
*/
public function resetScrollSpeed(?newScrollSpeed:Float):Void
public function resetScrollSpeed():Void
{
scrollSpeed = newScrollSpeed ?? PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
}
var _conductorInUse:Null<Conductor>;
@ -185,7 +183,7 @@ class Strumline extends FlxSpriteGroup
static final BACKGROUND_PAD:Int = 16;
public function new(noteStyle:NoteStyle, isPlayer:Bool, ?scrollSpeed:Float)
public function new(noteStyle:NoteStyle, isPlayer:Bool)
{
super();
@ -237,13 +235,14 @@ class Strumline extends FlxSpriteGroup
if (inArrowContorlSchemeMode && isPlayer) this.background.x -= 100;
#end
this.add(this.background);
strumlineScale = new FlxCallbackPoint(strumlineScaleCallback);
strumlineScale = new FlxCallbackPoint(strumlineScaleCallback);
this.refresh();
this.onNoteIncoming = new FlxTypedSignal<NoteSprite->Void>();
resetScrollSpeed(scrollSpeed);
resetScrollSpeed();
for (i in 0...KEY_COUNT)
{
@ -644,6 +643,8 @@ class Strumline extends FlxSpriteGroup
if (holdNote.cover != null && isPlayer)
{
holdNote.cover.playEnd();
trace("Sustain Note Splash Vibration");
}
else if (holdNote.cover != null)
{
@ -947,6 +948,7 @@ class Strumline extends FlxSpriteGroup
{
if (note == null) return;
note.visible = false;
notes.remove(note, false);
note.kill();
if (note.holdNoteSprite != null)
@ -1101,7 +1103,6 @@ class Strumline extends FlxSpriteGroup
if (noteSprite != null)
{
var noteKind:NoteKind = NoteKindManager.getNoteKind(note.kind);
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
noteSprite.setupNoteGraphic(noteKindStyle);
@ -1126,7 +1127,6 @@ class Strumline extends FlxSpriteGroup
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
noteSprite.x -= NUDGE;
noteSprite.y = -9999;
if (noteKind != null) noteSprite.scoreable = noteKind.scoreable;
}
return noteSprite;

Some files were not shown because too many files have changed in this diff Show more