mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-08-31 19:04:55 +00:00
Compare commits
136 commits
211ae319d0
...
7392371a39
Author | SHA1 | Date | |
---|---|---|---|
|
7392371a39 | ||
|
2a38359443 | ||
|
11bb30cd67 | ||
|
b772bcfb2f | ||
|
203e8eb372 | ||
|
e097553f7d | ||
|
a15e3810fe | ||
|
039e1b07c9 | ||
|
250c661576 | ||
|
ec464969b4 | ||
|
0cab4ae169 | ||
|
41984e78af | ||
|
467a54e212 | ||
|
b5bfb27185 | ||
|
229803dc05 | ||
|
c6c5f0c028 | ||
|
7508e4e1a6 | ||
|
03e8ef80c2 | ||
|
91cb1da992 | ||
|
dff8bc623d | ||
|
159e3b8f29 | ||
|
719d115616 | ||
|
6674c1583b | ||
|
1937a4a04e | ||
|
5fb445b99b | ||
|
911c70311b | ||
|
fe335be607 | ||
|
33e8eed060 | ||
|
f35bc4e944 | ||
|
4b82b001c6 | ||
|
fad7528372 | ||
|
85cce30ec8 | ||
|
b52a7d08ff | ||
|
137ffb91c9 | ||
|
f531cf3141 | ||
|
8179beca84 | ||
|
909669f0dc | ||
|
81aca6a045 | ||
|
2ed1ebb8bc | ||
|
508540e117 | ||
|
dca47d382c | ||
|
d12d01c402 | ||
|
d10f2a4b82 | ||
|
5700eba599 | ||
|
85de2a9246 | ||
|
d9ffe55d06 | ||
|
e28fc1478d | ||
|
a772fd2a20 | ||
|
0d32f15658 | ||
|
bd3cd1fd3b | ||
|
9e4365e43f | ||
|
cacd044467 | ||
|
589c01972d | ||
|
460536beba | ||
|
98c9cee395 | ||
|
f17aa92f60 | ||
|
a36a3f0576 | ||
|
a4ee5f9048 | ||
|
1c68fbac61 | ||
|
f1346feffb | ||
|
b1019b822f | ||
|
fbb07501d2 | ||
|
8018cf6ff0 | ||
|
2d14304461 | ||
|
e31ef3665a | ||
|
100a97ba97 | ||
|
1ccd12cfcd | ||
|
a3755d113d | ||
|
451ee3b008 | ||
|
8625f5807a | ||
|
79425bfd62 | ||
|
564b72f90b | ||
|
c1ad4426f2 | ||
|
f597a87d10 | ||
|
15f07e8e26 | ||
|
cb60bb9b68 | ||
|
e563c2c59d | ||
|
65a2673e38 | ||
|
8fcc841e79 | ||
|
9626f354ef | ||
|
57ee401f8e | ||
|
a4ef2514c1 | ||
|
dcc989782e | ||
|
6b712e71e6 | ||
|
bbcc5ab81e | ||
|
1c59256871 | ||
|
3bb5d7c12f | ||
|
cb9e335d79 | ||
|
c59fb53142 | ||
|
afb90615e5 | ||
|
5333cdf0bc | ||
|
36cef9f915 | ||
|
cc8aff94a8 | ||
|
9420842f5e | ||
|
63da7fcab1 | ||
|
456596a34b | ||
|
e640ba1274 | ||
|
42226e32f2 | ||
|
08d1eff7af | ||
|
3d65a18c00 | ||
|
568e57c32f | ||
|
c0f9281ee4 | ||
|
621b4b8ccc | ||
|
940ece9c07 | ||
|
c9405ec609 | ||
|
62058ee971 | ||
|
9a01083d9f | ||
|
64f25395c5 | ||
|
bdc044ce04 | ||
|
9cffb3fd6b | ||
|
50a8e47293 | ||
|
923078bd57 | ||
|
7c136ff38f | ||
|
01b8f69553 | ||
|
fd53ff89a4 | ||
|
657564322d | ||
|
605b5ba5c2 | ||
|
5a4d8344d2 | ||
|
1794a9e57e | ||
|
39c2e7136d | ||
|
d1d96f58e2 | ||
|
3b667044e1 | ||
|
726cda48c9 | ||
|
e736229024 | ||
|
1fecb6ea3e | ||
|
4294e9a6ec | ||
|
a73be72ba0 | ||
|
7947ecaa00 | ||
|
5801411be0 | ||
|
f16e42368b | ||
|
a9bd80d5ec | ||
|
908d6ca834 | ||
|
7be65fc2bd | ||
|
62b2815d04 | ||
|
caf56d496c | ||
|
2a815e91db |
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
|
@ -150,6 +150,16 @@
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DRESULTS"]
|
"args": ["-debug", "-DRESULTS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Windows / Debug (Straight to Stage Editor)",
|
||||||
|
"target": "windows",
|
||||||
|
"args": ["-debug", "-DSTAGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Windows / Debug (Straight to Stage Builder)",
|
||||||
|
"target": "windows",
|
||||||
|
"args": ["-debug", "-DSTAGEBUILD", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
|
|
14
alsoft.txt
Normal file
14
alsoft.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[general]
|
||||||
|
sample-type=float32
|
||||||
|
period_size=441
|
||||||
|
stereo-mode=speakers
|
||||||
|
stereo-encoding=panpot
|
||||||
|
hrtf=false
|
||||||
|
cf_level=0
|
||||||
|
resampler=fast_bsinc24
|
||||||
|
front-stablizer=false
|
||||||
|
output-limiter=false
|
||||||
|
[decoder]
|
||||||
|
hq-mode=false
|
||||||
|
distance-comp=false
|
||||||
|
nfc=false
|
2
art
2
art
|
@ -1 +1 @@
|
||||||
Subproject commit 8402339e2e63ae99c441941a46d54bf3f0c0d5fa
|
Subproject commit 78dc310219370144719b4eeef9b3b511c5a44532
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit c108a7ff0d11bf328e7b232160b8f68c71e21bca
|
Subproject commit 88778a44853255b082ada57e9cb77d8fb4956494
|
4
hmm.json
4
hmm.json
|
@ -77,7 +77,7 @@
|
||||||
"name": "hscript",
|
"name": "hscript",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "27c86f9a761c1d16d4433c4cf252eccb7b2e18de",
|
"ref": "d60bb2947fa609fdc875ccfae89666a6984eeaf2",
|
||||||
"url": "https://github.com/FunkinCrew/hscript"
|
"url": "https://github.com/FunkinCrew/hscript"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -192,7 +192,7 @@
|
||||||
"name": "polymod",
|
"name": "polymod",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "0fbdf27fe124549730accd540cec8a183f8652c0",
|
"ref": "3e030c81de99ca84acde681431f806d8103bcf6e",
|
||||||
"url": "https://github.com/larsiusprime/polymod"
|
"url": "https://github.com/larsiusprime/polymod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
1896
project.hxp
1896
project.hxp
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@ import flixel.math.FlxMath;
|
||||||
import funkin.data.song.SongData.SongTimeChange;
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
import funkin.data.song.SongDataUtils;
|
import funkin.data.song.SongDataUtils;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
|
import funkin.util.TimerUtil.SongSequence;
|
||||||
import haxe.Timer;
|
import haxe.Timer;
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
|
|
||||||
|
@ -92,6 +93,12 @@ class Conductor
|
||||||
*/
|
*/
|
||||||
public var songPosition(default, null):Float = 0;
|
public var songPosition(default, null):Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset between frame time and music time.
|
||||||
|
* Used in `getTimeWithDelta()` to get a more accurate music time when on higher framerates.
|
||||||
|
*/
|
||||||
|
var songPositionDelta(default, null):Float = 0;
|
||||||
|
|
||||||
var prevTimestamp:Float = 0;
|
var prevTimestamp:Float = 0;
|
||||||
var prevTime:Float = 0;
|
var prevTime:Float = 0;
|
||||||
|
|
||||||
|
@ -422,7 +429,8 @@ class Conductor
|
||||||
// If the song is playing, limit the song position to the length of the song or beginning of the song.
|
// 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)
|
if (FlxG.sound.music != null && FlxG.sound.music.playing)
|
||||||
{
|
{
|
||||||
this.songPosition = Math.min(currentLength, Math.max(0, songPos));
|
this.songPosition = FlxMath.bound(Math.min(this.combinedOffset, 0), songPos, currentLength);
|
||||||
|
this.songPositionDelta += FlxG.elapsed * 1000 * FlxG.sound.music.pitch;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -488,10 +496,23 @@ class Conductor
|
||||||
// which it doesn't do every frame!
|
// which it doesn't do every frame!
|
||||||
if (prevTime != this.songPosition)
|
if (prevTime != this.songPosition)
|
||||||
{
|
{
|
||||||
|
this.songPositionDelta = 0;
|
||||||
|
|
||||||
// Update the timestamp for use in-between frames
|
// Update the timestamp for use in-between frames
|
||||||
prevTime = this.songPosition;
|
prevTime = this.songPosition;
|
||||||
prevTimestamp = Std.int(Timer.stamp() * 1000);
|
prevTimestamp = Std.int(Timer.stamp() * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this == Conductor.instance) @:privateAccess SongSequence.update.dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a more accurate music time for higher framerates.
|
||||||
|
* @return Float
|
||||||
|
*/
|
||||||
|
public function getTimeWithDelta():Float
|
||||||
|
{
|
||||||
|
return this.songPosition + this.songPositionDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,7 @@ package funkin;
|
||||||
/**
|
/**
|
||||||
* A core class which handles tracking score and combo for the current song.
|
* A core class which handles tracking score and combo for the current song.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class Highscore
|
class Highscore
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,6 +27,7 @@ import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.notes.notekind.NoteKindManager;
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
import funkin.play.PlayStatePlaylist;
|
import funkin.play.PlayStatePlaylist;
|
||||||
import funkin.ui.debug.charting.ChartEditorState;
|
import funkin.ui.debug.charting.ChartEditorState;
|
||||||
|
import funkin.ui.debug.stageeditor.StageEditorState;
|
||||||
import funkin.ui.title.TitleState;
|
import funkin.ui.title.TitleState;
|
||||||
import funkin.ui.transition.LoadingState;
|
import funkin.ui.transition.LoadingState;
|
||||||
import funkin.util.CLIUtil;
|
import funkin.util.CLIUtil;
|
||||||
|
@ -51,6 +52,7 @@ import funkin.api.newgrounds.NewgroundsClient;
|
||||||
*
|
*
|
||||||
* It should not contain any sprites or rendering.
|
* It should not contain any sprites or rendering.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class InitState extends FlxState
|
class InitState extends FlxState
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -241,6 +243,9 @@ class InitState extends FlxState
|
||||||
#elseif CHARTING
|
#elseif CHARTING
|
||||||
// -DCHARTING
|
// -DCHARTING
|
||||||
FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState());
|
FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState());
|
||||||
|
#elseif STAGING
|
||||||
|
// -DSTAGING
|
||||||
|
FlxG.switchState(() -> new funkin.ui.debug.stageeditor.StageEditorState());
|
||||||
#elseif STAGEBUILD
|
#elseif STAGEBUILD
|
||||||
// -DSTAGEBUILD
|
// -DSTAGEBUILD
|
||||||
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
|
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
|
||||||
|
@ -303,6 +308,13 @@ class InitState extends FlxState
|
||||||
fnfcTargetPath: params.chart.chartPath,
|
fnfcTargetPath: params.chart.chartPath,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
else if (params.stage.shouldLoadStage)
|
||||||
|
{
|
||||||
|
FlxG.switchState(() -> new StageEditorState(
|
||||||
|
{
|
||||||
|
fnfsTargetPath: params.stage.stagePath,
|
||||||
|
}));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||||
|
@ -317,7 +329,7 @@ class InitState extends FlxState
|
||||||
*/
|
*/
|
||||||
function startSong(songId:String, difficultyId:String = 'normal'):Void
|
function startSong(songId:String, difficultyId:String = 'normal'):Void
|
||||||
{
|
{
|
||||||
var songData:funkin.play.song.Song = funkin.data.song.SongRegistry.instance.fetchEntry(songId);
|
var songData:Null<funkin.play.song.Song> = funkin.data.song.SongRegistry.instance.fetchEntry(songId);
|
||||||
|
|
||||||
if (songData == null)
|
if (songData == null)
|
||||||
{
|
{
|
||||||
|
@ -354,6 +366,7 @@ class InitState extends FlxState
|
||||||
PlayStatePlaylist.campaignId = 'weekend1';
|
PlayStatePlaylist.campaignId = 'weekend1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off) // Cannot unify?
|
||||||
LoadingState.loadPlayState(
|
LoadingState.loadPlayState(
|
||||||
{
|
{
|
||||||
targetSong: songData,
|
targetSong: songData,
|
||||||
|
@ -368,7 +381,7 @@ class InitState extends FlxState
|
||||||
*/
|
*/
|
||||||
function startLevel(levelId:String, difficultyId:String = 'normal'):Void
|
function startLevel(levelId:String, difficultyId:String = 'normal'):Void
|
||||||
{
|
{
|
||||||
var currentLevel:funkin.ui.story.Level = funkin.data.story.level.LevelRegistry.instance.fetchEntry(levelId);
|
var currentLevel:Null<funkin.ui.story.Level> = funkin.data.story.level.LevelRegistry.instance.fetchEntry(levelId);
|
||||||
|
|
||||||
if (currentLevel == null)
|
if (currentLevel == null)
|
||||||
{
|
{
|
||||||
|
@ -384,10 +397,19 @@ class InitState extends FlxState
|
||||||
PlayStatePlaylist.isStoryMode = true;
|
PlayStatePlaylist.isStoryMode = true;
|
||||||
PlayStatePlaylist.campaignScore = 0;
|
PlayStatePlaylist.campaignScore = 0;
|
||||||
|
|
||||||
var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift();
|
var targetSongId:Null<String> = PlayStatePlaylist.playlistSongIds.shift();
|
||||||
|
|
||||||
var targetSong:funkin.play.song.Song = SongRegistry.instance.fetchEntry(targetSongId);
|
var targetSong:Null<funkin.play.song.Song> = null;
|
||||||
|
|
||||||
|
if (targetSongId != null) targetSong = SongRegistry.instance.fetchEntry(targetSongId);
|
||||||
|
|
||||||
|
if (targetSongId == null)
|
||||||
|
{
|
||||||
|
startGameNormally();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
LoadingState.loadPlayState(
|
LoadingState.loadPlayState(
|
||||||
{
|
{
|
||||||
targetSong: targetSong,
|
targetSong: targetSong,
|
||||||
|
@ -395,6 +417,7 @@ class InitState extends FlxState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off) // Meh, remove when flixel.system.debug.log.LogStyle is null safe
|
||||||
function setupFlixelDebug():Void
|
function setupFlixelDebug():Void
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
|
@ -474,17 +497,17 @@ class InitState extends FlxState
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineSong():String
|
function defineSong():Null<String>
|
||||||
{
|
{
|
||||||
return MacroUtil.getDefine('SONG');
|
return MacroUtil.getDefine('SONG');
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineLevel():String
|
function defineLevel():Null<String>
|
||||||
{
|
{
|
||||||
return MacroUtil.getDefine('LEVEL');
|
return MacroUtil.getDefine('LEVEL');
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineDifficulty():String
|
function defineDifficulty():Null<String>
|
||||||
{
|
{
|
||||||
return MacroUtil.getDefine('DIFFICULTY');
|
return MacroUtil.getDefine('DIFFICULTY');
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import openfl.utils.AssetType;
|
||||||
/**
|
/**
|
||||||
* A core class which handles determining asset paths.
|
* A core class which handles determining asset paths.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class Paths
|
class Paths
|
||||||
{
|
{
|
||||||
static var currentLevel:Null<String> = null;
|
static var currentLevel:Null<String> = null;
|
||||||
|
@ -136,7 +137,7 @@ class Paths
|
||||||
* @param withExtension if it should return with the audio file extension `.mp3` or `.ogg`.
|
* @param withExtension if it should return with the audio file extension `.mp3` or `.ogg`.
|
||||||
* @return String
|
* @return String
|
||||||
*/
|
*/
|
||||||
public static function inst(song:String, ?suffix:String = '', ?withExtension:Bool = true):String
|
public static function inst(song:String, ?suffix:String = '', withExtension:Bool = true):String
|
||||||
{
|
{
|
||||||
var ext:String = withExtension ? '.${Constants.EXT_SOUND}' : '';
|
var ext:String = withExtension ? '.${Constants.EXT_SOUND}' : '';
|
||||||
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix$ext';
|
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix$ext';
|
||||||
|
|
|
@ -9,12 +9,17 @@ import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
/**
|
/**
|
||||||
* A core class which represents the current player(s) and their controls and other configuration.
|
* A core class which represents the current player(s) and their controls and other configuration.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class PlayerSettings
|
class PlayerSettings
|
||||||
{
|
{
|
||||||
// TODO: Finish implementation of second player.
|
// TODO: Finish implementation of second player.
|
||||||
public static var numPlayers(default, null) = 0;
|
public static var numPlayers(default, null) = 0;
|
||||||
public static var numAvatars(default, null) = 0;
|
public static var numAvatars(default, null) = 0;
|
||||||
|
// TODO: Making both of these null makes a lot of errors with the controls.
|
||||||
|
// That'd explain why unplugging input devices can cause the game to crash?
|
||||||
|
@:nullSafety(Off)
|
||||||
public static var player1(default, null):PlayerSettings;
|
public static var player1(default, null):PlayerSettings;
|
||||||
|
@:nullSafety(Off)
|
||||||
public static var player2(default, null):PlayerSettings;
|
public static var player2(default, null):PlayerSettings;
|
||||||
|
|
||||||
public static var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
public static var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||||
|
@ -70,6 +75,7 @@ class PlayerSettings
|
||||||
/**
|
/**
|
||||||
* Forcibly destroy the PlayerSettings singletons for each player.
|
* Forcibly destroy the PlayerSettings singletons for each player.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety(Off)
|
||||||
public static function reset():Void
|
public static function reset():Void
|
||||||
{
|
{
|
||||||
player1 = null;
|
player1 = null;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.util.WindowUtil;
|
||||||
/**
|
/**
|
||||||
* A core class which provides a store of user-configurable, globally relevant values.
|
* A core class which provides a store of user-configurable, globally relevant values.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class Preferences
|
class Preferences
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +46,7 @@ class Preferences
|
||||||
|
|
||||||
static function get_naughtyness():Bool
|
static function get_naughtyness():Bool
|
||||||
{
|
{
|
||||||
return Save?.instance?.options?.naughtyness;
|
return Save?.instance?.options?.naughtyness ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_naughtyness(value:Bool):Bool
|
static function set_naughtyness(value:Bool):Bool
|
||||||
|
@ -64,7 +65,7 @@ class Preferences
|
||||||
|
|
||||||
static function get_downscroll():Bool
|
static function get_downscroll():Bool
|
||||||
{
|
{
|
||||||
return Save?.instance?.options?.downscroll;
|
return Save?.instance?.options?.downscroll ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_downscroll(value:Bool):Bool
|
static function set_downscroll(value:Bool):Bool
|
||||||
|
@ -102,7 +103,7 @@ class Preferences
|
||||||
|
|
||||||
static function get_zoomCamera():Bool
|
static function get_zoomCamera():Bool
|
||||||
{
|
{
|
||||||
return Save?.instance?.options?.zoomCamera;
|
return Save?.instance?.options?.zoomCamera ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_zoomCamera(value:Bool):Bool
|
static function set_zoomCamera(value:Bool):Bool
|
||||||
|
@ -121,7 +122,7 @@ class Preferences
|
||||||
|
|
||||||
static function get_debugDisplay():Bool
|
static function get_debugDisplay():Bool
|
||||||
{
|
{
|
||||||
return Save?.instance?.options?.debugDisplay;
|
return Save?.instance?.options?.debugDisplay ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_debugDisplay(value:Bool):Bool
|
static function set_debugDisplay(value:Bool):Bool
|
||||||
|
@ -228,7 +229,7 @@ class Preferences
|
||||||
|
|
||||||
static function get_unlockedFramerate():Bool
|
static function get_unlockedFramerate():Bool
|
||||||
{
|
{
|
||||||
return Save?.instance?.options?.unlockedFramerate;
|
return Save?.instance?.options?.unlockedFramerate ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function set_unlockedFramerate(value:Bool):Bool
|
static function set_unlockedFramerate(value:Bool):Bool
|
||||||
|
@ -343,44 +344,6 @@ class Preferences
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The game will save any screenshots taken to this format.
|
|
||||||
* @default `PNG`
|
|
||||||
*/
|
|
||||||
public static var saveFormat(get, set):Any;
|
|
||||||
|
|
||||||
static function get_saveFormat():Any
|
|
||||||
{
|
|
||||||
return Save?.instance?.options?.screenshot?.saveFormat ?? 'PNG';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function set_saveFormat(value):Any
|
|
||||||
{
|
|
||||||
var save:Save = Save.instance;
|
|
||||||
save.options.screenshot.saveFormat = value;
|
|
||||||
save.flush();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The game will save JPEG screenshots with this quality percentage.
|
|
||||||
* @default `80`
|
|
||||||
*/
|
|
||||||
public static var jpegQuality(get, set):Int;
|
|
||||||
|
|
||||||
static function get_jpegQuality():Int
|
|
||||||
{
|
|
||||||
return Save?.instance?.options?.screenshot?.jpegQuality ?? 80;
|
|
||||||
}
|
|
||||||
|
|
||||||
static function set_jpegQuality(value:Int):Int
|
|
||||||
{
|
|
||||||
var save:Save = Save.instance;
|
|
||||||
save.options.screenshot.jpegQuality = value;
|
|
||||||
save.flush();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the user's preferences from the save data and apply them.
|
* Loads the user's preferences from the save data and apply them.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,6 +8,7 @@ import hxdiscord_rpc.Types.DiscordRichPresence;
|
||||||
import hxdiscord_rpc.Types.DiscordUser;
|
import hxdiscord_rpc.Types.DiscordUser;
|
||||||
import sys.thread.Thread;
|
import sys.thread.Thread;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class DiscordClient
|
class DiscordClient
|
||||||
{
|
{
|
||||||
static final CLIENT_ID:String = "816168432860790794";
|
static final CLIENT_ID:String = "816168432860790794";
|
||||||
|
@ -40,12 +41,12 @@ class DiscordClient
|
||||||
trace('[DISCORD] Initializing connection...');
|
trace('[DISCORD] Initializing connection...');
|
||||||
|
|
||||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null);
|
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||||
|
|
||||||
createDaemon();
|
createDaemon();
|
||||||
}
|
}
|
||||||
|
|
||||||
var daemon:Thread = null;
|
var daemon:Null<Thread> = null;
|
||||||
|
|
||||||
function createDaemon():Void
|
function createDaemon():Void
|
||||||
{
|
{
|
||||||
|
@ -56,8 +57,6 @@ class DiscordClient
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
trace('[DISCORD] Performing client update...');
|
|
||||||
|
|
||||||
#if DISCORD_DISABLE_IO_THREAD
|
#if DISCORD_DISABLE_IO_THREAD
|
||||||
Discord.updateConnection();
|
Discord.updateConnection();
|
||||||
#end
|
#end
|
||||||
|
@ -76,8 +75,6 @@ class DiscordClient
|
||||||
|
|
||||||
public function setPresence(params:DiscordClientPresenceParams):Void
|
public function setPresence(params:DiscordClientPresenceParams):Void
|
||||||
{
|
{
|
||||||
trace('[DISCORD] Updating presence... (${params})');
|
|
||||||
|
|
||||||
Discord.updatePresence(buildPresence(params));
|
Discord.updatePresence(buildPresence(params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,17 +89,15 @@ class DiscordClient
|
||||||
presence.largeImageText = "Friday Night Funkin'";
|
presence.largeImageText = "Friday Night Funkin'";
|
||||||
|
|
||||||
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
|
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
|
||||||
presence.state = cast(params.state, Null<String>);
|
presence.state = cast(params.state, Null<String>) ?? "";
|
||||||
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
|
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
|
||||||
presence.details = cast(params.details, Null<String>);
|
presence.details = cast(params.details, Null<String>) ?? "";
|
||||||
|
|
||||||
// The large image displaying what the user is doing.
|
// The large image displaying what the user is doing.
|
||||||
// This should probably be album art.
|
// This should probably be album art.
|
||||||
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
|
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
|
||||||
presence.largeImageKey = cast(params.largeImageKey, Null<String>) ?? "album-volume1";
|
presence.largeImageKey = cast(params.largeImageKey, Null<String>) ?? "album-volume1";
|
||||||
|
|
||||||
trace('[DISCORD] largeImageKey: ${presence.largeImageKey}');
|
|
||||||
|
|
||||||
// TODO: Make this use the song's album art.
|
// TODO: Make this use the song's album art.
|
||||||
// presence.largeImageKey = "icon";
|
// presence.largeImageKey = "icon";
|
||||||
// presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg";
|
// presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg";
|
||||||
|
@ -110,7 +105,7 @@ class DiscordClient
|
||||||
// The small inset image for what the user is doing.
|
// The small inset image for what the user is doing.
|
||||||
// This can be the opponent's health icon?
|
// This can be the opponent's health icon?
|
||||||
// NOTE: Like largeImageKey, this can be a URL, or an asset key.
|
// NOTE: Like largeImageKey, this can be a URL, or an asset key.
|
||||||
presence.smallImageKey = cast(params.smallImageKey, Null<String>);
|
presence.smallImageKey = cast(params.smallImageKey, Null<String>) ?? "";
|
||||||
|
|
||||||
// NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good
|
// NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good
|
||||||
// presence.startTimestamp = time - 10;
|
// presence.startTimestamp = time - 10;
|
||||||
|
@ -136,9 +131,9 @@ class DiscordClient
|
||||||
|
|
||||||
final username:String = request[0].username;
|
final username:String = request[0].username;
|
||||||
final globalName:String = request[0].username;
|
final globalName:String = request[0].username;
|
||||||
final discriminator:Int = Std.parseInt(request[0].discriminator);
|
final discriminator:Null<Int> = Std.parseInt(request[0].discriminator);
|
||||||
|
|
||||||
if (discriminator != 0)
|
if (discriminator != null && discriminator != 0)
|
||||||
{
|
{
|
||||||
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
||||||
}
|
}
|
||||||
|
@ -204,4 +199,17 @@ typedef DiscordClientPresenceParams =
|
||||||
*/
|
*/
|
||||||
var ?smallImageKey:String;
|
var ?smallImageKey:String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DiscordClientSandboxed
|
||||||
|
{
|
||||||
|
public static function setPresence(params:DiscordClientPresenceParams)
|
||||||
|
{
|
||||||
|
return DiscordClient.instance.setPresence(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function shutdown()
|
||||||
|
{
|
||||||
|
DiscordClient.instance.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -2,10 +2,14 @@ package funkin.api.newgrounds;
|
||||||
|
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
import io.newgrounds.Call.CallError;
|
import io.newgrounds.Call.CallError;
|
||||||
|
import io.newgrounds.components.ScoreBoardComponent;
|
||||||
|
import io.newgrounds.objects.Score;
|
||||||
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
||||||
|
import io.newgrounds.objects.User;
|
||||||
import io.newgrounds.objects.events.Outcome;
|
import io.newgrounds.objects.events.Outcome;
|
||||||
import io.newgrounds.utils.ScoreBoardList;
|
import io.newgrounds.utils.ScoreBoardList;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class Leaderboards
|
class Leaderboards
|
||||||
{
|
{
|
||||||
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||||
|
@ -16,21 +20,8 @@ class Leaderboards
|
||||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var result:Map<Leaderboard, LeaderboardData> = [];
|
|
||||||
|
|
||||||
for (leaderboardId in leaderboardList.keys())
|
return @:privateAccess leaderboardList._map?.copy() ?? [];
|
||||||
{
|
|
||||||
var leaderboardData = leaderboardList.get(leaderboardId);
|
|
||||||
if (leaderboardData == null) continue;
|
|
||||||
|
|
||||||
// A little hacky, but it works.
|
|
||||||
result.set(cast leaderboardId, leaderboardData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,6 +57,41 @@ class Leaderboards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to receive scores from Newgrounds.
|
||||||
|
* @param leaderboard The leaderboard to fetch scores from.
|
||||||
|
* @param params Additional parameters for fetching the score.
|
||||||
|
*/
|
||||||
|
public static function requestScores(leaderboard:Leaderboard, ?params:RequestScoresParams)
|
||||||
|
{
|
||||||
|
// Silently reject retrieving scores from unknown leaderboards.
|
||||||
|
if (leaderboard == Leaderboard.Unknown) return;
|
||||||
|
|
||||||
|
var leaderboardList = NewgroundsClient.instance.leaderboards;
|
||||||
|
if (leaderboardList == null) return;
|
||||||
|
|
||||||
|
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
|
||||||
|
if (leaderboardData == null) return;
|
||||||
|
|
||||||
|
var user:Null<User> = null;
|
||||||
|
if ((params?.useCurrentUser ?? false) && NewgroundsClient.instance.isLoggedIn()) user = NewgroundsClient.instance.user;
|
||||||
|
|
||||||
|
leaderboardData.requestScores(params?.limit ?? 10, params?.skip ?? 0, params?.period ?? ALL, params?.social ?? false, params?.tag, user,
|
||||||
|
function(outcome:Outcome<CallError>):Void {
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
trace('[NEWGROUNDS] Fetched scores!');
|
||||||
|
if (params != null && params.onComplete != null) params.onComplete(leaderboardData.scores);
|
||||||
|
|
||||||
|
case FAIL(error):
|
||||||
|
trace('[NEWGROUNDS] Failed to fetch scores!');
|
||||||
|
trace(error);
|
||||||
|
if (params != null && params.onFail != null) params.onFail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit a score for a Story Level to Newgrounds.
|
* Submit a score for a Story Level to Newgrounds.
|
||||||
*/
|
*/
|
||||||
|
@ -84,9 +110,77 @@ class Leaderboards
|
||||||
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
|
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for `Leaderboards` that prevents submitting scores.
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
class LeaderboardsSandboxed
|
||||||
|
{
|
||||||
|
public static function getLeaderboardBySong(songId:String, difficultyId:String)
|
||||||
|
{
|
||||||
|
return Leaderboard.getLeaderboardBySong(songId, difficultyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLeaderboardByLevel(levelId:String)
|
||||||
|
{
|
||||||
|
return Leaderboard.getLeaderboardByLevel(levelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestScores(leaderboard:Leaderboard, params:RequestScoresParams)
|
||||||
|
{
|
||||||
|
Leaderboards.requestScores(leaderboard, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional parameters for `Leaderboards.requestScores()`
|
||||||
|
*/
|
||||||
|
typedef RequestScoresParams =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* How many scores to include in a list.
|
||||||
|
* @default `10`
|
||||||
|
*/
|
||||||
|
var ?limit:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many scores to skip before starting the list.
|
||||||
|
* @default `0`
|
||||||
|
*/
|
||||||
|
var ?skip:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time-frame to pull the scores from.
|
||||||
|
* @default `Period.ALL`
|
||||||
|
*/
|
||||||
|
var ?period:Period;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, only scores by the user and their friends will be loaded. Ignored if no user is set.
|
||||||
|
* @default `false`
|
||||||
|
*/
|
||||||
|
var ?social:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional tag to filter the results by.
|
||||||
|
* @default `null`
|
||||||
|
*/
|
||||||
|
var ?tag:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, only the scores from the currently logged in user will be loaded.
|
||||||
|
* Additionally, if `social` is set to true, the scores of the user's friend will be loaded.
|
||||||
|
* @default `false`
|
||||||
|
*/
|
||||||
|
var ?useCurrentUser:Bool;
|
||||||
|
|
||||||
|
var ?onComplete:Array<Score>->Void;
|
||||||
|
var ?onFail:Void->Void;
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
enum abstract Leaderboard(Int)
|
enum abstract Leaderboard(Int) from Int to Int
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Represents an undefined or invalid leaderboard.
|
* Represents an undefined or invalid leaderboard.
|
||||||
|
@ -285,7 +379,7 @@ enum abstract Leaderboard(Int)
|
||||||
{
|
{
|
||||||
case "darnell":
|
case "darnell":
|
||||||
return DarnellBFMix;
|
return DarnellBFMix;
|
||||||
case "litup":
|
case "lit-up":
|
||||||
return LitUpBFMix;
|
return LitUpBFMix;
|
||||||
default:
|
default:
|
||||||
return Unknown;
|
return Unknown;
|
||||||
|
@ -379,7 +473,7 @@ enum abstract Leaderboard(Int)
|
||||||
return Stress;
|
return Stress;
|
||||||
case "darnell":
|
case "darnell":
|
||||||
return Darnell;
|
return Darnell;
|
||||||
case "litup":
|
case "lit-up":
|
||||||
return LitUp;
|
return LitUp;
|
||||||
case "2hot":
|
case "2hot":
|
||||||
return TwoHot;
|
return TwoHot;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import openfl.display.BitmapData;
|
||||||
import io.newgrounds.utils.MedalList;
|
import io.newgrounds.utils.MedalList;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class Medals
|
class Medals
|
||||||
{
|
{
|
||||||
public static var medalJSON:Array<MedalJSON> = [];
|
public static var medalJSON:Array<MedalJSON> = [];
|
||||||
|
@ -21,22 +22,8 @@ class Medals
|
||||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Why do I have to do this, @:nullSafety is fucked up
|
|
||||||
var result:Map<Medal, MedalData> = [];
|
|
||||||
|
|
||||||
for (medalId in medalList.keys())
|
return @:privateAccess medalList._map?.copy() ?? [];
|
||||||
{
|
|
||||||
var medalData = medalList.get(medalId);
|
|
||||||
if (medalData == null) continue;
|
|
||||||
|
|
||||||
// A little hacky, but it works.
|
|
||||||
result.set(cast medalId, medalData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function award(medal:Medal):Void
|
public static function award(medal:Medal):Void
|
||||||
|
@ -131,32 +118,78 @@ class Medals
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function awardStoryLevel(id:String):Void
|
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
|
||||||
{
|
{
|
||||||
switch (id)
|
var medalList = NewgroundsClient.instance.medals;
|
||||||
|
@:privateAccess
|
||||||
|
if (medalList == null || medalList._map == null) return null;
|
||||||
|
|
||||||
|
var medalData:Null<MedalData> = medalList.get(medal.getId());
|
||||||
|
@:privateAccess
|
||||||
|
if (medalData == null || medalData._data == null)
|
||||||
{
|
{
|
||||||
case 'tutorial':
|
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||||
Medals.award(Medal.StoryTutorial);
|
return null;
|
||||||
case 'week1':
|
}
|
||||||
Medals.award(Medal.StoryWeek1);
|
|
||||||
case 'week2':
|
return {
|
||||||
Medals.award(Medal.StoryWeek2);
|
id: medalData.id,
|
||||||
case 'week3':
|
name: medalData.name,
|
||||||
Medals.award(Medal.StoryWeek3);
|
description: medalData.description,
|
||||||
case 'week4':
|
icon: medalData.icon,
|
||||||
Medals.award(Medal.StoryWeek4);
|
value: medalData.value,
|
||||||
case 'week5':
|
difficulty: medalData.difficulty,
|
||||||
Medals.award(Medal.StoryWeek5);
|
secret: medalData.secret,
|
||||||
case 'week6':
|
unlocked: medalData.unlocked
|
||||||
Medals.award(Medal.StoryWeek6);
|
|
||||||
case 'week7':
|
|
||||||
Medals.award(Medal.StoryWeek7);
|
|
||||||
case 'weekend1':
|
|
||||||
Medals.award(Medal.StoryWeekend1);
|
|
||||||
default:
|
|
||||||
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function awardStoryLevel(id:String):Void
|
||||||
|
{
|
||||||
|
var medal:Medal = Medal.getMedalByStoryLevel(id);
|
||||||
|
if (medal == Medal.Unknown)
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Medals.award(medal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for `Medals` that prevents awarding medals.
|
||||||
|
*/
|
||||||
|
class MedalsSandboxed
|
||||||
|
{
|
||||||
|
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
|
||||||
|
{
|
||||||
|
return Medals.fetchMedalData(medal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMedalByStoryLevel(id:String):Medal
|
||||||
|
{
|
||||||
|
return Medal.getMedalByStoryLevel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAllMedals():Array<Medal>
|
||||||
|
{
|
||||||
|
return Medal.getAllMedals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains data for a Medal, but excludes functions like `sendUnlock()`.
|
||||||
|
*/
|
||||||
|
typedef FetchedMedalData =
|
||||||
|
{
|
||||||
|
var id:Int;
|
||||||
|
var name:String;
|
||||||
|
var description:String;
|
||||||
|
var icon:String;
|
||||||
|
var value:Int;
|
||||||
|
var difficulty:Int;
|
||||||
|
var secret:Bool;
|
||||||
|
var unlocked:Bool;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
@ -324,6 +357,8 @@ enum abstract Medal(Int) from Int to Int
|
||||||
{
|
{
|
||||||
switch (levelId)
|
switch (levelId)
|
||||||
{
|
{
|
||||||
|
case "tutorial":
|
||||||
|
return StoryTutorial;
|
||||||
case "week1":
|
case "week1":
|
||||||
return StoryWeek1;
|
return StoryWeek1;
|
||||||
case "week2":
|
case "week2":
|
||||||
|
@ -344,4 +379,33 @@ enum abstract Medal(Int) from Int to Int
|
||||||
return Unknown;
|
return Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all medals aside from the `Unknown` one.
|
||||||
|
*/
|
||||||
|
public static function getAllMedals()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
StartGame,
|
||||||
|
StoryTutorial,
|
||||||
|
StoryWeek1,
|
||||||
|
StoryWeek2,
|
||||||
|
StoryWeek3,
|
||||||
|
StoryWeek4,
|
||||||
|
StoryWeek5,
|
||||||
|
StoryWeek6,
|
||||||
|
StoryWeek7,
|
||||||
|
StoryWeekend1,
|
||||||
|
CharSelect,
|
||||||
|
FreeplayPicoMix,
|
||||||
|
FreeplayStressPico,
|
||||||
|
LossRating,
|
||||||
|
PerfectRatingHard,
|
||||||
|
GoldPerfectRatingHard,
|
||||||
|
ErectDifficulty,
|
||||||
|
GoldPerfectRatingNightmare,
|
||||||
|
FridayNight,
|
||||||
|
Nice
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -331,4 +331,22 @@ class NewgroundsClient
|
||||||
return Save.instance.ngSessionId;
|
return Save.instance.ngSessionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for `NewgroundsClient` that prevents submitting cheated data.
|
||||||
|
*/
|
||||||
|
class NewgroundsClientSandboxed
|
||||||
|
{
|
||||||
|
public static var user(get, never):Null<User>;
|
||||||
|
|
||||||
|
static function get_user()
|
||||||
|
{
|
||||||
|
return NewgroundsClient.instance.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isLoggedIn()
|
||||||
|
{
|
||||||
|
return NewgroundsClient.instance.isLoggedIn();
|
||||||
|
}
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
|
|
32
source/funkin/audio/ALSoftConfig.hx
Normal file
32
source/funkin/audio/ALSoftConfig.hx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package funkin.audio;
|
||||||
|
|
||||||
|
#if desktop
|
||||||
|
import haxe.io.Path;
|
||||||
|
|
||||||
|
import sys.FileSystem;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A class that simply points the audio backend OpenALSoft to use a custom
|
||||||
|
* configuration when the game starts up.
|
||||||
|
*
|
||||||
|
* The config file overrides a few global OpenALSoft settings to improve audio
|
||||||
|
* quality on desktop targets.
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
class ALSoftConfig
|
||||||
|
{
|
||||||
|
private static function __init__():Void
|
||||||
|
{
|
||||||
|
var configPath:String = Path.directory(Path.withoutExtension(#if hl Sys.getCwd() #else Sys.programPath() #end));
|
||||||
|
#if windows
|
||||||
|
configPath += "/plugins/alsoft.ini";
|
||||||
|
#elseif mac
|
||||||
|
configPath = '${Path.directory(configPath)}/Resources/plugins/alsoft.conf';
|
||||||
|
#else
|
||||||
|
configPath += "/plugins/alsoft.conf";
|
||||||
|
#end
|
||||||
|
|
||||||
|
Sys.putEnv("ALSOFT_CONF", FileSystem.fullPath(configPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
|
@ -11,6 +11,7 @@ import openfl.utils.AssetType;
|
||||||
/**
|
/**
|
||||||
* a FlxSound that just overrides loadEmbedded to allow for "streamed" sounds to load with better performance!
|
* a FlxSound that just overrides loadEmbedded to allow for "streamed" sounds to load with better performance!
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FlxStreamSound extends FlxSound
|
class FlxStreamSound extends FlxSound
|
||||||
{
|
{
|
||||||
public function new()
|
public function new()
|
||||||
|
@ -18,7 +19,7 @@ class FlxStreamSound extends FlxSound
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function loadEmbedded(EmbeddedSound:FlxSoundAsset, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound
|
override public function loadEmbedded(EmbeddedSound:Null<FlxSoundAsset>, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound
|
||||||
{
|
{
|
||||||
if (EmbeddedSound == null) return this;
|
if (EmbeddedSound == null) return this;
|
||||||
|
|
||||||
|
|
|
@ -551,6 +551,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
}
|
}
|
||||||
FlxTween.cancelTweensOf(this);
|
FlxTween.cancelTweensOf(this);
|
||||||
this._label = 'unknown';
|
this._label = 'unknown';
|
||||||
|
this._waveformData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:access(openfl.media.Sound)
|
@:access(openfl.media.Sound)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import flixel.tweens.FlxTween;
|
||||||
* A group of FunkinSounds that are all synced together.
|
* A group of FunkinSounds that are all synced together.
|
||||||
* Unlike FlxSoundGroup, you can also control their time and pitch.
|
* Unlike FlxSoundGroup, you can also control their time and pitch.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class SoundGroup extends FlxTypedGroup<FunkinSound>
|
class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
{
|
{
|
||||||
public var time(get, set):Float;
|
public var time(get, set):Float;
|
||||||
|
@ -36,6 +37,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
for (sndFile in files)
|
for (sndFile in files)
|
||||||
{
|
{
|
||||||
var snd:FunkinSound = FunkinSound.load(Paths.voices(song, '$sndFile'));
|
var snd:FunkinSound = FunkinSound.load(Paths.voices(song, '$sndFile'));
|
||||||
|
@ -70,7 +72,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
/**
|
/**
|
||||||
* Add a sound to the group.
|
* Add a sound to the group.
|
||||||
*/
|
*/
|
||||||
public override function add(sound:FunkinSound):FunkinSound
|
public override function add(sound:FunkinSound):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
var result:FunkinSound = super.add(sound);
|
var result:FunkinSound = super.add(sound);
|
||||||
|
|
||||||
|
@ -134,6 +136,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
/**
|
/**
|
||||||
* Fade in all the sounds in the group.
|
* Fade in all the sounds in the group.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety(Off)
|
||||||
public function fadeIn(duration:Float, ?from:Float = 0.0, ?to:Float = 1.0, ?onComplete:FlxTween->Void):Void
|
public function fadeIn(duration:Float, ?from:Float = 0.0, ?to:Float = 1.0, ?onComplete:FlxTween->Void):Void
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FunkinSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
|
@ -144,6 +147,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
/**
|
/**
|
||||||
* Fade out all the sounds in the group.
|
* Fade out all the sounds in the group.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety(Off)
|
||||||
public function fadeOut(duration:Float, ?to:Float = 0.0, ?onComplete:FlxTween->Void):Void
|
public function fadeOut(duration:Float, ?to:Float = 0.0, ?onComplete:FlxTween->Void):Void
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FunkinSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
|
@ -238,7 +242,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
|
|
||||||
function get_muted():Bool
|
function get_muted():Bool
|
||||||
{
|
{
|
||||||
if (getFirstAlive() != null) return getFirstAlive().muted;
|
if (getFirstAlive() != null) return getFirstAlive()?.muted ?? false;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@ package funkin.audio;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import funkin.audio.waveform.WaveformData;
|
import funkin.audio.waveform.WaveformData;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class VoicesGroup extends SoundGroup
|
class VoicesGroup extends SoundGroup
|
||||||
{
|
{
|
||||||
var playerVoices:FlxTypedGroup<FunkinSound>;
|
var playerVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||||
var opponentVoices:FlxTypedGroup<FunkinSound>;
|
var opponentVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the volume of only the sounds in the player group.
|
* Control the volume of only the sounds in the player group.
|
||||||
|
@ -41,12 +42,12 @@ class VoicesGroup extends SoundGroup
|
||||||
public function addPlayerVoice(sound:FunkinSound):Void
|
public function addPlayerVoice(sound:FunkinSound):Void
|
||||||
{
|
{
|
||||||
super.add(sound);
|
super.add(sound);
|
||||||
playerVoices.add(sound);
|
playerVoices?.add(sound);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_playerVolume(volume:Float):Float
|
function set_playerVolume(volume:Float):Float
|
||||||
{
|
{
|
||||||
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.volume = volume;
|
voice.volume = volume;
|
||||||
});
|
});
|
||||||
return playerVolume = volume;
|
return playerVolume = volume;
|
||||||
|
@ -59,10 +60,10 @@ class VoicesGroup extends SoundGroup
|
||||||
snd.time = time;
|
snd.time = time;
|
||||||
});
|
});
|
||||||
|
|
||||||
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time -= playerVoicesOffset;
|
voice.time -= playerVoicesOffset;
|
||||||
});
|
});
|
||||||
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time -= opponentVoicesOffset;
|
voice.time -= opponentVoicesOffset;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
function set_playerVoicesOffset(offset:Float):Float
|
function set_playerVoicesOffset(offset:Float):Float
|
||||||
{
|
{
|
||||||
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time += playerVoicesOffset;
|
voice.time += playerVoicesOffset;
|
||||||
voice.time -= offset;
|
voice.time -= offset;
|
||||||
});
|
});
|
||||||
|
@ -80,7 +81,7 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
function set_opponentVoicesOffset(offset:Float):Float
|
function set_opponentVoicesOffset(offset:Float):Float
|
||||||
{
|
{
|
||||||
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time += opponentVoicesOffset;
|
voice.time += opponentVoicesOffset;
|
||||||
voice.time -= offset;
|
voice.time -= offset;
|
||||||
});
|
});
|
||||||
|
@ -93,12 +94,12 @@ class VoicesGroup extends SoundGroup
|
||||||
public function addOpponentVoice(sound:FunkinSound):Void
|
public function addOpponentVoice(sound:FunkinSound):Void
|
||||||
{
|
{
|
||||||
super.add(sound);
|
super.add(sound);
|
||||||
opponentVoices.add(sound);
|
opponentVoices?.add(sound);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_opponentVolume(volume:Float):Float
|
function set_opponentVolume(volume:Float):Float
|
||||||
{
|
{
|
||||||
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.volume = volume;
|
voice.volume = volume;
|
||||||
});
|
});
|
||||||
return opponentVolume = volume;
|
return opponentVolume = volume;
|
||||||
|
@ -106,26 +107,26 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
public function getPlayerVoice(index:Int = 0):Null<FunkinSound>
|
public function getPlayerVoice(index:Int = 0):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
return playerVoices.members[index];
|
return playerVoices?.members[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOpponentVoice(index:Int = 0):Null<FunkinSound>
|
public function getOpponentVoice(index:Int = 0):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
return opponentVoices.members[index];
|
return opponentVoices?.members[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlayerVoiceWaveform():Null<WaveformData>
|
public function getPlayerVoiceWaveform():Null<WaveformData>
|
||||||
{
|
{
|
||||||
if (playerVoices.members.length == 0) return null;
|
if (playerVoices?.members.length == 0) return null;
|
||||||
|
|
||||||
return playerVoices.members[0].waveformData;
|
return playerVoices?.members[0].waveformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOpponentVoiceWaveform():Null<WaveformData>
|
public function getOpponentVoiceWaveform():Null<WaveformData>
|
||||||
{
|
{
|
||||||
if (opponentVoices.members.length == 0) return null;
|
if (opponentVoices?.members.length == 0) return null;
|
||||||
|
|
||||||
return opponentVoices.members[0].waveformData;
|
return opponentVoices?.members[0].waveformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,9 +134,9 @@ class VoicesGroup extends SoundGroup
|
||||||
*/
|
*/
|
||||||
public function getPlayerVoiceLength():Float
|
public function getPlayerVoiceLength():Float
|
||||||
{
|
{
|
||||||
if (playerVoices.members.length == 0) return 0.0;
|
if (playerVoices?.members.length == 0) return 0.0;
|
||||||
|
|
||||||
return playerVoices.members[0].length;
|
return playerVoices?.members[0]?.length ?? 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,15 +144,15 @@ class VoicesGroup extends SoundGroup
|
||||||
*/
|
*/
|
||||||
public function getOpponentVoiceLength():Float
|
public function getOpponentVoiceLength():Float
|
||||||
{
|
{
|
||||||
if (opponentVoices.members.length == 0) return 0.0;
|
if (opponentVoices?.members.length == 0) return 0.0;
|
||||||
|
|
||||||
return opponentVoices.members[0].length;
|
return opponentVoices?.members[0]?.length ?? 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function clear():Void
|
public override function clear():Void
|
||||||
{
|
{
|
||||||
playerVoices.clear();
|
playerVoices?.clear();
|
||||||
opponentVoices.clear();
|
opponentVoices?.clear();
|
||||||
super.clear();
|
super.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,13 +160,13 @@ class VoicesGroup extends SoundGroup
|
||||||
{
|
{
|
||||||
if (playerVoices != null)
|
if (playerVoices != null)
|
||||||
{
|
{
|
||||||
playerVoices.destroy();
|
playerVoices?.destroy();
|
||||||
playerVoices = null;
|
playerVoices = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opponentVoices != null)
|
if (opponentVoices != null)
|
||||||
{
|
{
|
||||||
opponentVoices.destroy();
|
opponentVoices?.destroy();
|
||||||
opponentVoices = null;
|
opponentVoices = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package funkin.audio.visualize;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class ABot extends FlxTypedSpriteGroup<FlxSprite>
|
class ABot extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
{
|
{
|
||||||
public function new()
|
public function new()
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
|
|
||||||
for (i in 0...min(group.members.length, levels.length))
|
for (i in 0...min(group.members.length, levels.length))
|
||||||
{
|
{
|
||||||
var animFrame:Int = Math.round(levels[i].value * 6);
|
var animFrame:Int = (FlxG.sound.volume == 0 || FlxG.sound.muted) ? 0 : Math.round(levels[i].value * 6);
|
||||||
|
|
||||||
// don't display if we're at 0 volume from the level
|
// don't display if we're at 0 volume from the level
|
||||||
group.members[i].visible = animFrame > 0;
|
group.members[i].visible = animFrame > 0;
|
||||||
|
|
|
@ -3,11 +3,12 @@ package funkin.audio.visualize;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class PolygonVisGroup extends FlxTypedGroup<PolygonSpectogram>
|
class PolygonVisGroup extends FlxTypedGroup<PolygonSpectogram>
|
||||||
{
|
{
|
||||||
public var playerVis:PolygonSpectogram;
|
public var playerVis:Null<PolygonSpectogram>;
|
||||||
public var opponentVis:PolygonSpectogram;
|
public var opponentVis:Null<PolygonSpectogram>;
|
||||||
public var instVis:PolygonSpectogram;
|
public var instVis:Null<PolygonSpectogram>;
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
|
@ -99,8 +100,14 @@ class PolygonVisGroup extends FlxTypedGroup<PolygonSpectogram>
|
||||||
|
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
playerVis.destroy();
|
if (playerVis != null)
|
||||||
opponentVis.destroy();
|
{
|
||||||
|
playerVis.destroy();
|
||||||
|
}
|
||||||
|
if (opponentVis != null)
|
||||||
|
{
|
||||||
|
opponentVis.destroy();
|
||||||
|
}
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ package funkin.audio.visualize.dsp;
|
||||||
Complex number representation.
|
Complex number representation.
|
||||||
**/
|
**/
|
||||||
@:forward(real, imag) @:notNull @:pure
|
@:forward(real, imag) @:notNull @:pure
|
||||||
|
@:nullSafety
|
||||||
abstract Complex({
|
abstract Complex({
|
||||||
final real:Float;
|
final real:Float;
|
||||||
final imag:Float;
|
final imag:Float;
|
||||||
|
|
|
@ -8,6 +8,7 @@ using funkin.audio.visualize.dsp.Signal;
|
||||||
/**
|
/**
|
||||||
Fast/Finite Fourier Transforms.
|
Fast/Finite Fourier Transforms.
|
||||||
**/
|
**/
|
||||||
|
@:nullSafety
|
||||||
class FFT
|
class FFT
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ package funkin.audio.visualize.dsp;
|
||||||
Usages include 1-indexed sequences or zero-centered buffers with negative indexing.
|
Usages include 1-indexed sequences or zero-centered buffers with negative indexing.
|
||||||
**/
|
**/
|
||||||
@:forward(array, offset)
|
@:forward(array, offset)
|
||||||
|
@:nullSafety
|
||||||
abstract OffsetArray<T>({
|
abstract OffsetArray<T>({
|
||||||
final array:Array<T>;
|
final array:Array<T>;
|
||||||
final offset:Int;
|
final offset:Int;
|
||||||
|
|
|
@ -5,12 +5,13 @@ using Lambda;
|
||||||
/**
|
/**
|
||||||
Signal processing miscellaneous utilities.
|
Signal processing miscellaneous utilities.
|
||||||
**/
|
**/
|
||||||
|
@:nullSafety
|
||||||
class Signal
|
class Signal
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
Returns a smoothed version of the input array using a moving average.
|
Returns a smoothed version of the input array using a moving average.
|
||||||
**/
|
**/
|
||||||
public static function smooth(y:Array<Float>, n:Int):Array<Float>
|
public static function smooth(y:Array<Float>, n:Int):Null<Array<Float>>
|
||||||
{
|
{
|
||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@ package funkin.audio.waveform;
|
||||||
|
|
||||||
import funkin.util.TimerUtil;
|
import funkin.util.TimerUtil;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class WaveformDataParser
|
class WaveformDataParser
|
||||||
{
|
{
|
||||||
static final INT16_MAX:Int = 32767;
|
static final INT16_MAX:Int = 32767;
|
||||||
|
@ -10,7 +11,7 @@ class WaveformDataParser
|
||||||
static final INT8_MAX:Int = 127;
|
static final INT8_MAX:Int = 127;
|
||||||
static final INT8_MIN:Int = -128;
|
static final INT8_MIN:Int = -128;
|
||||||
|
|
||||||
public static function interpretFlxSound(sound:flixel.sound.FlxSound):Null<WaveformData>
|
public static function interpretFlxSound(sound:Null<flixel.sound.FlxSound>):Null<WaveformData>
|
||||||
{
|
{
|
||||||
if (sound == null) return null;
|
if (sound == null) return null;
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ typedef EntryConstructorFunction = String->Void;
|
||||||
* @param T The type to construct. Must implement `IRegistryEntry`.
|
* @param T The type to construct. Must implement `IRegistryEntry`.
|
||||||
* @param J The type of the JSON data used when constructing.
|
* @param J The type of the JSON data used when constructing.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
@:generic
|
@:generic
|
||||||
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
||||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||||
|
@ -115,7 +116,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var entry:T = createEntry(entryId);
|
var entry:Null<T> = createEntry(entryId);
|
||||||
if (entry != null)
|
if (entry != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded entry data: ${entry}');
|
trace(' Loaded entry data: ${entry}');
|
||||||
|
@ -165,7 +166,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
* @param id The ID of the entry.
|
* @param id The ID of the entry.
|
||||||
* @return The class name, or `null` if it does not exist.
|
* @return The class name, or `null` if it does not exist.
|
||||||
*/
|
*/
|
||||||
public function getScriptedEntryClassName(id:String):String
|
public function getScriptedEntryClassName(id:String):Null<String>
|
||||||
{
|
{
|
||||||
return scriptedEntryIds.get(id);
|
return scriptedEntryIds.get(id);
|
||||||
}
|
}
|
||||||
|
@ -216,7 +217,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
||||||
{
|
{
|
||||||
var entryStr:String = loadEntryFile(id).contents;
|
var entryStr:String = loadEntryFile(id).contents;
|
||||||
var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr);
|
var entryVersion:Null<thx.semver.Version> = VersionUtil.getVersionFromJSON(entryStr);
|
||||||
return entryVersion;
|
return entryVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import json2object.Position;
|
||||||
import json2object.Position.Line;
|
import json2object.Position.Line;
|
||||||
import json2object.Error;
|
import json2object.Error;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class DataError
|
class DataError
|
||||||
{
|
{
|
||||||
public static function printError(error:Error):Void
|
public static function printError(error:Error):Void
|
||||||
|
|
|
@ -19,6 +19,7 @@ import thx.semver.VersionRule;
|
||||||
*
|
*
|
||||||
* Functions must be of the signature `(hxjsonast.Json, String) -> T`, where the String is the property name and `T` is the type of the property.
|
* Functions must be of the signature `(hxjsonast.Json, String) -> T`, where the String is the property name and `T` is the type of the property.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class DataParse
|
class DataParse
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -146,7 +147,6 @@ class DataParse
|
||||||
throw 'Expected Backdrop property $name to be specify a valid "type", but it was "${backdropType}".';
|
throw 'Expected Backdrop property $name to be specify a valid "type", but it was "${backdropType}".';
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
default:
|
default:
|
||||||
throw 'Expected property $name to be an object, but it was ${json.value}.';
|
throw 'Expected property $name to be an object, but it was ${json.value}.';
|
||||||
}
|
}
|
||||||
|
@ -310,6 +310,7 @@ class DataParse
|
||||||
var length:Null<Float> = values[2] == null ? null : Tools.getValue(values[2]);
|
var length:Null<Float> = values[2] == null ? null : Tools.getValue(values[2]);
|
||||||
var alt:Null<Bool> = values[3] == null ? null : Tools.getValue(values[3]);
|
var alt:Null<Bool> = values[3] == null ? null : Tools.getValue(values[3]);
|
||||||
|
|
||||||
|
if (time == null || data == null) throw 'Property $name note is missing time and/or data values.';
|
||||||
return new LegacyNote(time, data, length, alt);
|
return new LegacyNote(time, data, length, alt);
|
||||||
// return null;
|
// return null;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -12,6 +12,7 @@ import haxe.ds.Either;
|
||||||
*
|
*
|
||||||
* NOTE: Result must include quotation marks if the value is a string! json2object will not add them for you!
|
* NOTE: Result must include quotation marks if the value is a string! json2object will not add them for you!
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class DataWrite
|
class DataWrite
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package funkin.data.animation;
|
package funkin.data.animation;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class AnimationDataUtil
|
class AnimationDataUtil
|
||||||
{
|
{
|
||||||
public static function toNamed(data:UnnamedAnimationData, ?name:String = ""):AnimationData
|
public static function toNamed(data:UnnamedAnimationData, name:String = ""):AnimationData
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -22,7 +23,7 @@ class AnimationDataUtil
|
||||||
* @param name (adds index to name)
|
* @param name (adds index to name)
|
||||||
* @return Array<AnimationData>
|
* @return Array<AnimationData>
|
||||||
*/
|
*/
|
||||||
public static function toNamedArray(data:Array<UnnamedAnimationData>, ?name:String = ""):Array<AnimationData>
|
public static function toNamedArray(data:Array<UnnamedAnimationData>, name:String = ""):Array<AnimationData>
|
||||||
{
|
{
|
||||||
return data.mapi(function(animItem, ind) return toNamed(animItem, '$name$ind'));
|
return data.mapi(function(animItem, ind) return toNamed(animItem, '$name$ind'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.cutscene.dialogue.ScriptedConversation;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
|
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
|
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
|
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,6 +8,7 @@ import funkin.play.event.ScriptedSongEvent;
|
||||||
/**
|
/**
|
||||||
* This class statically handles the parsing of internal and scripted song event handlers.
|
* This class statically handles the parsing of internal and scripted song event handlers.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class SongEventRegistry
|
class SongEventRegistry
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -87,14 +88,14 @@ class SongEventRegistry
|
||||||
return eventCache.values();
|
return eventCache.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getEvent(id:String):SongEvent
|
public static function getEvent(id:String):Null<SongEvent>
|
||||||
{
|
{
|
||||||
return eventCache.get(id);
|
return eventCache.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getEventSchema(id:String):SongEventSchema
|
public static function getEventSchema(id:String):Null<SongEventSchema>
|
||||||
{
|
{
|
||||||
var event:SongEvent = getEvent(id);
|
var event:Null<SongEvent> = getEvent(id);
|
||||||
if (event == null) return null;
|
if (event == null) return null;
|
||||||
|
|
||||||
return event.getEventSchema();
|
return event.getEventSchema();
|
||||||
|
@ -108,7 +109,7 @@ class SongEventRegistry
|
||||||
public static function handleEvent(data:SongEventData):Void
|
public static function handleEvent(data:SongEventData):Void
|
||||||
{
|
{
|
||||||
var eventKind:String = data.eventKind;
|
var eventKind:String = data.eventKind;
|
||||||
var eventHandler:SongEvent = eventCache.get(eventKind);
|
var eventHandler:Null<SongEvent> = eventCache.get(eventKind);
|
||||||
|
|
||||||
if (eventHandler != null)
|
if (eventHandler != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.ui.freeplay.ScriptedAlbum;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
|
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,7 @@ import funkin.save.Save;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
|
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
|
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.data.notestyle.NoteStyleData;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
|
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +25,8 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implement
|
||||||
|
|
||||||
public function fetchDefault():NoteStyle
|
public function fetchDefault():NoteStyle
|
||||||
{
|
{
|
||||||
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
var notestyle:Null<NoteStyle> = fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
||||||
|
if (notestyle == null) throw 'Default notestyle was null! This should not happen!';
|
||||||
|
return notestyle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,11 +68,12 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||||
@:jignored
|
@:jignored
|
||||||
public var variation:String;
|
public var variation:String;
|
||||||
|
|
||||||
public function new(songName:String, artist:String, ?variation:String)
|
public function new(songName:String, artist:String, ?charter:String, ?variation:String)
|
||||||
{
|
{
|
||||||
this.version = SongRegistry.SONG_METADATA_VERSION;
|
this.version = SongRegistry.SONG_METADATA_VERSION;
|
||||||
this.songName = songName;
|
this.songName = songName;
|
||||||
this.artist = artist;
|
this.artist = artist;
|
||||||
|
this.charter = (charter == null) ? null : charter;
|
||||||
this.timeFormat = 'ms';
|
this.timeFormat = 'ms';
|
||||||
this.divisions = null;
|
this.divisions = null;
|
||||||
this.offsets = new SongOffsets();
|
this.offsets = new SongOffsets();
|
||||||
|
@ -96,7 +97,7 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||||
*/
|
*/
|
||||||
public function clone():SongMetadata
|
public function clone():SongMetadata
|
||||||
{
|
{
|
||||||
var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.variation);
|
var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.charter, this.variation);
|
||||||
result.version = this.version;
|
result.version = this.version;
|
||||||
result.timeFormat = this.timeFormat;
|
result.timeFormat = this.timeFormat;
|
||||||
result.divisions = this.divisions;
|
result.divisions = this.divisions;
|
||||||
|
@ -139,7 +140,7 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||||
*/
|
*/
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'SongMetadata(${this.songName} by ${this.artist}, variation ${this.variation})';
|
return 'SongMetadata(${this.songName} by ${this.artist} and ${this.charter}, variation ${this.variation})';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
import funkin.data.song.SongData.SongTimeChange;
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
import funkin.util.ClipboardUtil;
|
import funkin.util.ClipboardUtil;
|
||||||
import funkin.util.SerializerUtil;
|
|
||||||
|
|
||||||
using Lambda;
|
using Lambda;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility functions for working with song data, including note data, event data, metadata, etc.
|
* Utility functions for working with song data, including note data, event data, metadata, etc.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class SongDataUtils
|
class SongDataUtils
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +28,7 @@ class SongDataUtils
|
||||||
var time:Float = note.time + offset;
|
var time:Float = note.time + offset;
|
||||||
var data:Int = note.data;
|
var data:Int = note.data;
|
||||||
var length:Float = note.length;
|
var length:Float = note.length;
|
||||||
var kind:String = note.kind;
|
var kind:Null<String> = note.kind;
|
||||||
return new SongNoteData(time, data, length, kind);
|
return new SongNoteData(time, data, length, kind);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ class SongDataUtils
|
||||||
* Create an array of notes whose note data is flipped (player becomes opponent and vice versa)
|
* Create an array of notes whose note data is flipped (player becomes opponent and vice versa)
|
||||||
* Does not mutate the original array.
|
* Does not mutate the original array.
|
||||||
*/
|
*/
|
||||||
public static function flipNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4):Array<SongNoteData>
|
public static function flipNotes(notes:Array<SongNoteData>, strumlineSize:Int = 4):Array<SongNoteData>
|
||||||
{
|
{
|
||||||
return notes.map(function(note:SongNoteData):SongNoteData {
|
return notes.map(function(note:SongNoteData):SongNoteData {
|
||||||
var newData = note.data;
|
var newData = note.data;
|
||||||
|
@ -150,7 +150,7 @@ class SongDataUtils
|
||||||
*
|
*
|
||||||
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
||||||
*/
|
*/
|
||||||
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int = null):Array<SongNoteData>
|
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int):Array<SongNoteData>
|
||||||
{
|
{
|
||||||
if (notes.length == 0) return notes;
|
if (notes.length == 0) return notes;
|
||||||
if (timeOffset == null) timeOffset = Std.int(notes[0].time);
|
if (timeOffset == null) timeOffset = Std.int(notes[0].time);
|
||||||
|
@ -162,7 +162,7 @@ class SongDataUtils
|
||||||
*
|
*
|
||||||
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
||||||
*/
|
*/
|
||||||
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int = null):Array<SongEventData>
|
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int):Array<SongEventData>
|
||||||
{
|
{
|
||||||
if (events.length == 0) return events;
|
if (events.length == 0) return events;
|
||||||
if (timeOffset == null) timeOffset = Std.int(events[0].time);
|
if (timeOffset == null) timeOffset = Std.int(events[0].time);
|
||||||
|
|
138
source/funkin/data/song/SongNoteDataUtils.hx
Normal file
138
source/funkin/data/song/SongNoteDataUtils.hx
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package funkin.data.song;
|
||||||
|
|
||||||
|
using SongData.SongNoteData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for extra handling of song notes
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
class SongNoteDataUtils
|
||||||
|
{
|
||||||
|
static final CHUNK_INTERVAL_MS:Float = 2500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all stacked notes. It does this by cycling through "chunks" of notes within a certain interval.
|
||||||
|
*
|
||||||
|
* @param notes Sorted notes by time.
|
||||||
|
* @param threshold The note stack threshold. Refer to `doNotesStack` for more details.
|
||||||
|
* @param includeOverlapped (Optional) If overlapped notes should be included.
|
||||||
|
* @param overlapped (Optional) An array that gets populated with overlapped notes.
|
||||||
|
* Note that it's only guaranteed to work properly if the provided notes are sorted.
|
||||||
|
* @return Stacked notes.
|
||||||
|
*/
|
||||||
|
public static function listStackedNotes(notes:Array<SongNoteData>, threshold:Float, includeOverlapped:Bool = true,
|
||||||
|
?overlapped:Array<SongNoteData>):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
var stackedNotes:Array<SongNoteData> = [];
|
||||||
|
|
||||||
|
var chunkTime:Float = 0;
|
||||||
|
var chunks:Array<Array<SongNoteData>> = [[]];
|
||||||
|
|
||||||
|
for (note in notes)
|
||||||
|
{
|
||||||
|
if (note == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (note.time >= chunkTime + CHUNK_INTERVAL_MS)
|
||||||
|
{
|
||||||
|
chunkTime += CHUNK_INTERVAL_MS;
|
||||||
|
chunks.push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks[chunks.length - 1].push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (chunk in chunks)
|
||||||
|
{
|
||||||
|
for (i in 0...(chunk.length - 1))
|
||||||
|
{
|
||||||
|
for (j in (i + 1)...chunk.length)
|
||||||
|
{
|
||||||
|
var noteI:SongNoteData = chunk[i];
|
||||||
|
var noteJ:SongNoteData = chunk[j];
|
||||||
|
|
||||||
|
if (doNotesStack(noteI, noteJ, threshold))
|
||||||
|
{
|
||||||
|
if (!stackedNotes.fastContains(noteI))
|
||||||
|
{
|
||||||
|
if (includeOverlapped) stackedNotes.push(noteI);
|
||||||
|
|
||||||
|
if (overlapped != null && !overlapped.contains(noteI)) overlapped.push(noteI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stackedNotes.fastContains(noteJ))
|
||||||
|
{
|
||||||
|
stackedNotes.push(noteJ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stackedNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates two arrays of notes but overwrites notes in `lhs` that are overlapped by notes in `rhs`.
|
||||||
|
* Hold notes are only overwritten by longer hold notes.
|
||||||
|
* This operation only modifies the second array and `overwrittenNotes`.
|
||||||
|
*
|
||||||
|
* @param lhs An array of notes
|
||||||
|
* @param rhs An array of notes to concatenate into `lhs`
|
||||||
|
* @param overwrittenNotes An optional array that is modified in-place with the notes in `lhs` that were overwritten.
|
||||||
|
* @param threshold The note stack threshold. Refer to `doNotesStack` for more details.
|
||||||
|
* @return The unsorted resulting array.
|
||||||
|
*/
|
||||||
|
public static function concatOverwrite(lhs:Array<SongNoteData>, rhs:Array<SongNoteData>, ?overwrittenNotes:Array<SongNoteData>,
|
||||||
|
threshold:Float = 0):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
if (lhs == null || rhs == null || rhs.length == 0) return lhs;
|
||||||
|
if (lhs.length == 0) return rhs;
|
||||||
|
|
||||||
|
var result = lhs.copy();
|
||||||
|
for (i in 0...rhs.length)
|
||||||
|
{
|
||||||
|
var noteB:SongNoteData = rhs[i];
|
||||||
|
var hasOverlap:Bool = false;
|
||||||
|
|
||||||
|
for (j in 0...lhs.length)
|
||||||
|
{
|
||||||
|
var noteA:SongNoteData = lhs[j];
|
||||||
|
if (doNotesStack(noteA, noteB, threshold))
|
||||||
|
{
|
||||||
|
// Long hold notes should have priority over shorter hold notes
|
||||||
|
if (noteA.length <= noteB.length)
|
||||||
|
{
|
||||||
|
overwrittenNotes?.push(result[j].clone());
|
||||||
|
result[j] = noteB;
|
||||||
|
}
|
||||||
|
hasOverlap = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasOverlap) result.push(noteB);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param noteA First note.
|
||||||
|
* @param noteB Second note.
|
||||||
|
* @param threshold The note stack threshold, in steps.
|
||||||
|
* @return Returns `true` if both notes are on the same strumline, have the same direction
|
||||||
|
* and their time difference in steps is less than the step-based threshold.
|
||||||
|
* A threshold of 0 will return `true` if notes are nearly perfectly aligned.
|
||||||
|
*/
|
||||||
|
public static function doNotesStack(noteA:SongNoteData, noteB:SongNoteData, threshold:Float = 0):Bool
|
||||||
|
{
|
||||||
|
if (noteA.data != noteB.data) return false;
|
||||||
|
else if (threshold == 0) return Math.ffloor(Math.abs(noteA.time - noteB.time)) < 1;
|
||||||
|
|
||||||
|
final stepDiff:Float = Math.abs(noteA.getStepTime() - noteB.getStepTime());
|
||||||
|
return stepDiff <= threshold + 0.001;
|
||||||
|
}
|
||||||
|
}
|
|
@ -324,7 +324,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleto
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.';
|
throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_MUSIC_DATA_VERSION_RULE}.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleto
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.';
|
throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_MUSIC_DATA_VERSION_RULE}.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ import funkin.data.song.SongData.SongTimeChange;
|
||||||
import funkin.data.song.importer.FNFLegacyData;
|
import funkin.data.song.importer.FNFLegacyData;
|
||||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class FNFLegacyImporter
|
class FNFLegacyImporter
|
||||||
{
|
{
|
||||||
public static function parseLegacyDataRaw(input:String, fileName:String = 'raw'):FNFLegacyData
|
public static function parseLegacyDataRaw(input:String, fileName:String = 'raw'):Null<FNFLegacyData>
|
||||||
{
|
{
|
||||||
var parser = new json2object.JsonParser<FNFLegacyData>();
|
var parser = new json2object.JsonParser<FNFLegacyData>();
|
||||||
parser.ignoreUnknownVariables = true; // Set to true to ignore extra variables that might be included in the JSON.
|
parser.ignoreUnknownVariables = true; // Set to true to ignore extra variables that might be included in the JSON.
|
||||||
|
@ -38,16 +39,14 @@ class FNFLegacyImporter
|
||||||
|
|
||||||
var songMetadata:SongMetadata = new SongMetadata('Import', Constants.DEFAULT_ARTIST, 'default');
|
var songMetadata:SongMetadata = new SongMetadata('Import', Constants.DEFAULT_ARTIST, 'default');
|
||||||
|
|
||||||
var hadError:Bool = false;
|
|
||||||
|
|
||||||
// Set generatedBy string for debugging.
|
// Set generatedBy string for debugging.
|
||||||
songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)';
|
songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)';
|
||||||
|
|
||||||
songMetadata.playData.stage = songData?.song?.stageDefault ?? 'mainStage';
|
songMetadata.playData.stage = songData.song?.stageDefault ?? 'mainStage';
|
||||||
songMetadata.songName = songData?.song?.song ?? 'Import';
|
songMetadata.songName = songData.song?.song ?? 'Import';
|
||||||
songMetadata.playData.difficulties = [];
|
songMetadata.playData.difficulties = [];
|
||||||
|
|
||||||
if (songData?.song?.notes != null)
|
if (songData.song?.notes != null)
|
||||||
{
|
{
|
||||||
switch (songData.song.notes)
|
switch (songData.song.notes)
|
||||||
{
|
{
|
||||||
|
@ -65,7 +64,7 @@ class FNFLegacyImporter
|
||||||
|
|
||||||
songMetadata.timeChanges = rebuildTimeChanges(songData);
|
songMetadata.timeChanges = rebuildTimeChanges(songData);
|
||||||
|
|
||||||
songMetadata.playData.characters = new SongCharacterData(songData?.song?.player1 ?? 'bf', 'gf', songData?.song?.player2 ?? 'dad');
|
songMetadata.playData.characters = new SongCharacterData(songData.song?.player1 ?? 'bf', 'gf', songData.song?.player2 ?? 'dad');
|
||||||
|
|
||||||
return songMetadata;
|
return songMetadata;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +75,7 @@ class FNFLegacyImporter
|
||||||
|
|
||||||
var songChartData:SongChartData = new SongChartData([difficulty => 1.0], [], [difficulty => []]);
|
var songChartData:SongChartData = new SongChartData([difficulty => 1.0], [], [difficulty => []]);
|
||||||
|
|
||||||
if (songData?.song?.notes != null)
|
if (songData.song?.notes != null)
|
||||||
{
|
{
|
||||||
switch (songData.song.notes)
|
switch (songData.song.notes)
|
||||||
{
|
{
|
||||||
|
@ -84,7 +83,6 @@ class FNFLegacyImporter
|
||||||
// One difficulty of notes.
|
// One difficulty of notes.
|
||||||
songChartData.notes.set(difficulty, migrateNoteSections(notes));
|
songChartData.notes.set(difficulty, migrateNoteSections(notes));
|
||||||
case Right(difficulties):
|
case Right(difficulties):
|
||||||
var baseDifficulty = null;
|
|
||||||
if (difficulties.easy != null) songChartData.notes.set('easy', migrateNoteSections(difficulties.easy));
|
if (difficulties.easy != null) songChartData.notes.set('easy', migrateNoteSections(difficulties.easy));
|
||||||
if (difficulties.normal != null) songChartData.notes.set('normal', migrateNoteSections(difficulties.normal));
|
if (difficulties.normal != null) songChartData.notes.set('normal', migrateNoteSections(difficulties.normal));
|
||||||
if (difficulties.hard != null) songChartData.notes.set('hard', migrateNoteSections(difficulties.hard));
|
if (difficulties.hard != null) songChartData.notes.set('hard', migrateNoteSections(difficulties.hard));
|
||||||
|
@ -124,8 +122,8 @@ class FNFLegacyImporter
|
||||||
noteSections = notes;
|
noteSections = notes;
|
||||||
case Right(difficulties):
|
case Right(difficulties):
|
||||||
if (difficulties.normal != null) noteSections = difficulties.normal;
|
if (difficulties.normal != null) noteSections = difficulties.normal;
|
||||||
if (difficulties.hard != null) noteSections = difficulties.normal;
|
if (difficulties.hard != null) noteSections = difficulties.hard;
|
||||||
if (difficulties.easy != null) noteSections = difficulties.normal;
|
if (difficulties.easy != null) noteSections = difficulties.easy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteSections == null || noteSections.length == 0) return result;
|
if (noteSections == null || noteSections.length == 0) return result;
|
||||||
|
@ -158,7 +156,7 @@ class FNFLegacyImporter
|
||||||
{
|
{
|
||||||
var result:Array<SongTimeChange> = [];
|
var result:Array<SongTimeChange> = [];
|
||||||
|
|
||||||
result.push(new SongTimeChange(0, songData?.song?.bpm ?? Constants.DEFAULT_BPM));
|
result.push(new SongTimeChange(0, songData.song?.bpm ?? Constants.DEFAULT_BPM));
|
||||||
|
|
||||||
var noteSections = [];
|
var noteSections = [];
|
||||||
switch (songData.song.notes)
|
switch (songData.song.notes)
|
||||||
|
@ -168,8 +166,8 @@ class FNFLegacyImporter
|
||||||
noteSections = notes;
|
noteSections = notes;
|
||||||
case Right(difficulties):
|
case Right(difficulties):
|
||||||
if (difficulties.normal != null) noteSections = difficulties.normal;
|
if (difficulties.normal != null) noteSections = difficulties.normal;
|
||||||
if (difficulties.hard != null) noteSections = difficulties.normal;
|
if (difficulties.hard != null) noteSections = difficulties.hard;
|
||||||
if (difficulties.easy != null) noteSections = difficulties.normal;
|
if (difficulties.easy != null) noteSections = difficulties.easy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteSections == null || noteSections.length == 0) return result;
|
if (noteSections == null || noteSections.length == 0) return result;
|
||||||
|
@ -179,7 +177,7 @@ class FNFLegacyImporter
|
||||||
if (noteSection.changeBPM ?? false)
|
if (noteSection.changeBPM ?? false)
|
||||||
{
|
{
|
||||||
var firstNote:LegacyNote = noteSection.sectionNotes[0];
|
var firstNote:LegacyNote = noteSection.sectionNotes[0];
|
||||||
if (firstNote != null) result.push(new SongTimeChange(firstNote.time, noteSection.bpm));
|
if (firstNote != null) result.push(new SongTimeChange(firstNote.time, noteSection.bpm ?? Constants.DEFAULT_BPM));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,7 @@ typedef StageDataProp =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The angle of the prop, as a float.
|
* The angle of the prop, as a float.
|
||||||
* @default 1.0
|
* @default 0.0
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
@:default(0.0)
|
@:default(0.0)
|
||||||
|
@ -284,7 +284,7 @@ typedef StageDataCharacter =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The angle of the character, as a float.
|
* The angle of the character, as a float.
|
||||||
* @default 1.0
|
* @default 0.0
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
@:default(0.0)
|
@:default(0.0)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.stage.ScriptedStage;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
|
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,6 +4,7 @@ import funkin.data.stickers.StickerData;
|
||||||
import funkin.ui.transition.stickers.StickerPack;
|
import funkin.ui.transition.stickers.StickerPack;
|
||||||
import funkin.ui.transition.stickers.ScriptedStickerPack;
|
import funkin.ui.transition.stickers.ScriptedStickerPack;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +25,9 @@ class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
||||||
|
|
||||||
public function fetchDefault():StickerPack
|
public function fetchDefault():StickerPack
|
||||||
{
|
{
|
||||||
return fetchEntry(Constants.DEFAULT_STICKER_PACK);
|
var stickerPack:Null<StickerPack> = fetchEntry(Constants.DEFAULT_STICKER_PACK);
|
||||||
|
if (stickerPack == null) throw 'Default sticker pack was null! This should not happen!';
|
||||||
|
return stickerPack;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.ui.story.ScriptedLevel;
|
||||||
import funkin.util.tools.ISingleton;
|
import funkin.util.tools.ISingleton;
|
||||||
import funkin.data.DefaultRegistryImpl;
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
|
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,7 @@ import openfl.display._internal.CairoGraphics as GfxRenderer;
|
||||||
* A modified `FlxSprite` that supports filters.
|
* A modified `FlxSprite` that supports filters.
|
||||||
* The name's pretty much self-explanatory.
|
* The name's pretty much self-explanatory.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
@:access(openfl.geom.Rectangle)
|
@:access(openfl.geom.Rectangle)
|
||||||
@:access(openfl.filters.BitmapFilter)
|
@:access(openfl.filters.BitmapFilter)
|
||||||
@:access(flixel.graphics.frames.FlxFrame)
|
@:access(flixel.graphics.frames.FlxFrame)
|
||||||
|
@ -39,12 +40,12 @@ class FlxFilteredSprite extends FlxSprite
|
||||||
{
|
{
|
||||||
@:noCompletion var _renderer:FlxAnimateFilterRenderer = new FlxAnimateFilterRenderer();
|
@:noCompletion var _renderer:FlxAnimateFilterRenderer = new FlxAnimateFilterRenderer();
|
||||||
|
|
||||||
@:noCompletion var _filterMatrix:FlxMatrix;
|
@:noCompletion var _filterMatrix:FlxMatrix = new FlxMatrix();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Array` of shader filters (aka `BitmapFilter`).
|
* An `Array` of shader filters (aka `BitmapFilter`).
|
||||||
*/
|
*/
|
||||||
public var filters(default, set):Array<BitmapFilter>;
|
public var filters(default, set):Null<Array<BitmapFilter>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a flag to update the image with the filters.
|
* a flag to update the image with the filters.
|
||||||
|
@ -52,11 +53,15 @@ class FlxFilteredSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public var filterDirty:Bool = false;
|
public var filterDirty:Bool = false;
|
||||||
|
|
||||||
@:noCompletion var filtered:Bool;
|
@:noCompletion var filtered:Bool = false;
|
||||||
|
|
||||||
|
// These appear to be a little troublesome to null safe.
|
||||||
|
@:nullSafety(Off)
|
||||||
@:noCompletion var _blankFrame:FlxFrame;
|
@:noCompletion var _blankFrame:FlxFrame;
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
var _filterBmp1:BitmapData;
|
var _filterBmp1:BitmapData;
|
||||||
|
@:nullSafety(Off)
|
||||||
var _filterBmp2:BitmapData;
|
var _filterBmp2:BitmapData;
|
||||||
|
|
||||||
override public function update(elapsed:Float)
|
override public function update(elapsed:Float)
|
||||||
|
@ -162,6 +167,7 @@ class FlxFilteredSprite extends FlxSprite
|
||||||
}
|
}
|
||||||
_flashRect.width += frameWidth;
|
_flashRect.width += frameWidth;
|
||||||
_flashRect.height += frameHeight;
|
_flashRect.height += frameHeight;
|
||||||
|
@:nullSafety(Off)
|
||||||
if (_blankFrame == null) _blankFrame = new FlxFrame(null);
|
if (_blankFrame == null) _blankFrame = new FlxFrame(null);
|
||||||
|
|
||||||
if (_blankFrame.parent == null || _flashRect.width > _blankFrame.parent.width || _flashRect.height > _blankFrame.parent.height)
|
if (_blankFrame.parent == null || _flashRect.width > _blankFrame.parent.width || _flashRect.height > _blankFrame.parent.height)
|
||||||
|
@ -178,6 +184,7 @@ class FlxFilteredSprite extends FlxSprite
|
||||||
_filterBmp2 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
_filterBmp2 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
||||||
}
|
}
|
||||||
_blankFrame.offset.copyFrom(_frame.offset);
|
_blankFrame.offset.copyFrom(_frame.offset);
|
||||||
|
@:nullSafety(Off)
|
||||||
_blankFrame.parent.bitmap = _renderer.applyFilter(_blankFrame.parent.bitmap, _filterBmp1, _filterBmp2, frame.parent.bitmap, filters, _flashRect,
|
_blankFrame.parent.bitmap = _renderer.applyFilter(_blankFrame.parent.bitmap, _filterBmp1, _filterBmp2, frame.parent.bitmap, filters, _flashRect,
|
||||||
frame.frame.copyToFlash());
|
frame.frame.copyToFlash());
|
||||||
_blankFrame.frame = FlxRect.get(0, 0, _blankFrame.parent.bitmap.width, _blankFrame.parent.bitmap.height);
|
_blankFrame.frame = FlxRect.get(0, 0, _blankFrame.parent.bitmap.width, _blankFrame.parent.bitmap.height);
|
||||||
|
@ -193,7 +200,7 @@ class FlxFilteredSprite extends FlxSprite
|
||||||
}
|
}
|
||||||
|
|
||||||
@:noCompletion
|
@:noCompletion
|
||||||
function set_filters(value:Array<BitmapFilter>)
|
function set_filters(value:Null<Array<BitmapFilter>>)
|
||||||
{
|
{
|
||||||
if (filters != value) filterDirty = true;
|
if (filters != value) filterDirty = true;
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import openfl.filters.ShaderFilter;
|
||||||
* - NOTE: Several other blend modes work without FunkinCamera. Some still do not work.
|
* - NOTE: Several other blend modes work without FunkinCamera. Some still do not work.
|
||||||
* - NOTE: Framerate-independent camera tweening is fixed in Flixel 6.x. Rest in peace, SwagCamera.
|
* - NOTE: Framerate-independent camera tweening is fixed in Flixel 6.x. Rest in peace, SwagCamera.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
@:access(openfl.display.DisplayObject)
|
@:access(openfl.display.DisplayObject)
|
||||||
@:access(openfl.display.BitmapData)
|
@:access(openfl.display.BitmapData)
|
||||||
@:access(openfl.display3D.Context3D)
|
@:access(openfl.display3D.Context3D)
|
||||||
|
@ -50,11 +51,12 @@ class FunkinCamera extends FlxCamera
|
||||||
// Used to identify the camera during debugging.
|
// Used to identify the camera during debugging.
|
||||||
final id:String = 'unknown';
|
final id:String = 'unknown';
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
public function new(id:String = 'unknown', x:Int = 0, y:Int = 0, width:Int = 0, height:Int = 0, zoom:Float = 0)
|
public function new(id:String = 'unknown', x:Int = 0, y:Int = 0, width:Int = 0, height:Int = 0, zoom:Float = 0)
|
||||||
{
|
{
|
||||||
super(x, y, width, height, zoom);
|
super(x, y, width, height, zoom);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
bgTexture = pickTexture(width, height);
|
bgTexture = @:nullSafety(Off) pickTexture(width, height);
|
||||||
bgBitmap = FixedBitmapData.fromTexture(bgTexture);
|
bgBitmap = FixedBitmapData.fromTexture(bgTexture);
|
||||||
bgFrame = new FlxFrame(new FlxGraphic('', null));
|
bgFrame = new FlxFrame(new FlxGraphic('', null));
|
||||||
bgFrame.parent.bitmap = bgBitmap;
|
bgFrame.parent.bitmap = bgBitmap;
|
||||||
|
@ -74,12 +76,15 @@ class FunkinCamera extends FlxCamera
|
||||||
* and the grabbed bitmap will not include any previously rendered sprites
|
* and the grabbed bitmap will not include any previously rendered sprites
|
||||||
* @return the grabbed bitmap data
|
* @return the grabbed bitmap data
|
||||||
*/
|
*/
|
||||||
public function grabScreen(applyFilters:Bool, isolate:Bool = false):BitmapData
|
public function grabScreen(applyFilters:Bool, isolate:Bool = false):Null<BitmapData>
|
||||||
{
|
{
|
||||||
final texture = pickTexture(width, height);
|
final texture = pickTexture(width, height);
|
||||||
final bitmap = FixedBitmapData.fromTexture(texture);
|
final bitmap = FixedBitmapData.fromTexture(texture);
|
||||||
squashTo(bitmap, applyFilters, isolate);
|
if (bitmap != null)
|
||||||
grabbed.push(bitmap);
|
{
|
||||||
|
squashTo(bitmap, applyFilters, isolate);
|
||||||
|
grabbed.push(bitmap);
|
||||||
|
}
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,12 +131,14 @@ class FunkinCamera extends FlxCamera
|
||||||
if (applyFilters)
|
if (applyFilters)
|
||||||
{
|
{
|
||||||
bitmap.draw(flashSprite, matrix);
|
bitmap.draw(flashSprite, matrix);
|
||||||
|
@:nullSafety(Off) // TODO: Remove this once openfl.display.Sprite has been null safed.
|
||||||
flashSprite.filters = null;
|
flashSprite.filters = null;
|
||||||
filtersApplied = true;
|
filtersApplied = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
final tmp = flashSprite.filters;
|
final tmp = flashSprite.filters;
|
||||||
|
@:nullSafety(Off)
|
||||||
flashSprite.filters = null;
|
flashSprite.filters = null;
|
||||||
bitmap.draw(flashSprite, matrix);
|
bitmap.draw(flashSprite, matrix);
|
||||||
flashSprite.filters = tmp;
|
flashSprite.filters = tmp;
|
||||||
|
@ -197,6 +204,7 @@ class FunkinCamera extends FlxCamera
|
||||||
final isolated = grabScreen(false, true);
|
final isolated = grabScreen(false, true);
|
||||||
// apply fullscreen blend
|
// apply fullscreen blend
|
||||||
customBlendShader.blendSwag = blend;
|
customBlendShader.blendSwag = blend;
|
||||||
|
@:nullSafety(Off) // I hope this doesn't cause issues
|
||||||
customBlendShader.sourceSwag = isolated;
|
customBlendShader.sourceSwag = isolated;
|
||||||
customBlendShader.updateViewInfo(FlxG.width, FlxG.height, this);
|
customBlendShader.updateViewInfo(FlxG.width, FlxG.height, this);
|
||||||
applyFilter(customBlendFilter);
|
applyFilter(customBlendFilter);
|
||||||
|
@ -228,7 +236,7 @@ class FunkinCamera extends FlxCamera
|
||||||
bgItemCount = 0;
|
bgItemCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickTexture(width:Int, height:Int):TextureBase
|
function pickTexture(width:Int, height:Int):Null<TextureBase>
|
||||||
{
|
{
|
||||||
// zero-sized textures will be problematic
|
// zero-sized textures will be problematic
|
||||||
width = width < 1 ? 1 : width;
|
width = width < 1 ? 1 : width;
|
||||||
|
@ -236,7 +244,9 @@ class FunkinCamera extends FlxCamera
|
||||||
if (texturePool.length > 0)
|
if (texturePool.length > 0)
|
||||||
{
|
{
|
||||||
final res = texturePool.pop();
|
final res = texturePool.pop();
|
||||||
BitmapDataUtil.resizeTexture(res, width, height);
|
if (res != null) BitmapDataUtil.resizeTexture(res, width, height);
|
||||||
|
else
|
||||||
|
trace('huh? why is this null? $texturePool');
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
return Lib.current.stage.context3D.createTexture(width, height, BGRA, true);
|
return Lib.current.stage.context3D.createTexture(width, height, BGRA, true);
|
||||||
|
|
|
@ -17,6 +17,7 @@ import flixel.FlxCamera;
|
||||||
* - A more efficient method for creating solid color sprites.
|
* - A more efficient method for creating solid color sprites.
|
||||||
* - TODO: Better cache handling for textures.
|
* - TODO: Better cache handling for textures.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FunkinSprite extends FlxSprite
|
class FunkinSprite extends FlxSprite
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -158,9 +159,14 @@ class FunkinSprite extends FlxSprite
|
||||||
* @param input The OpenFL `TextureBase` to apply
|
* @param input The OpenFL `TextureBase` to apply
|
||||||
* @return This sprite, for chaining
|
* @return This sprite, for chaining
|
||||||
*/
|
*/
|
||||||
public function loadTextureBase(input:TextureBase):FunkinSprite
|
public function loadTextureBase(input:TextureBase):Null<FunkinSprite>
|
||||||
{
|
{
|
||||||
var inputBitmap:FixedBitmapData = FixedBitmapData.fromTexture(input);
|
var inputBitmap:Null<FixedBitmapData> = FixedBitmapData.fromTexture(input);
|
||||||
|
if (inputBitmap == null)
|
||||||
|
{
|
||||||
|
FlxG.log.warn('loadTextureBase - input resulted in null bitmap! $input');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return loadBitmapData(inputBitmap);
|
return loadBitmapData(inputBitmap);
|
||||||
}
|
}
|
||||||
|
@ -219,7 +225,7 @@ class FunkinSprite extends FlxSprite
|
||||||
// Move the graphic from the previous cache to the current cache.
|
// Move the graphic from the previous cache to the current cache.
|
||||||
var graphic = previousCachedTextures.get(key);
|
var graphic = previousCachedTextures.get(key);
|
||||||
previousCachedTextures.remove(key);
|
previousCachedTextures.remove(key);
|
||||||
currentCachedTextures.set(key, graphic);
|
if (graphic != null) currentCachedTextures.set(key, graphic);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,8 +277,9 @@ class FunkinSprite extends FlxSprite
|
||||||
|
|
||||||
static function isGraphicCached(graphic:FlxGraphic):Bool
|
static function isGraphicCached(graphic:FlxGraphic):Bool
|
||||||
{
|
{
|
||||||
|
var result = null;
|
||||||
if (graphic == null) return false;
|
if (graphic == null) return false;
|
||||||
var result = FlxG.bitmap.get(graphic.key);
|
result = FlxG.bitmap.get(graphic.key);
|
||||||
if (result == null) return false;
|
if (result == null) return false;
|
||||||
if (result != graphic)
|
if (result != graphic)
|
||||||
{
|
{
|
||||||
|
@ -288,8 +295,9 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function isAnimationDynamic(id:String):Bool
|
public function isAnimationDynamic(id:String):Bool
|
||||||
{
|
{
|
||||||
|
var animData = null;
|
||||||
if (this.animation == null) return false;
|
if (this.animation == null) return false;
|
||||||
var animData = this.animation.getByName(id);
|
animData = this.animation.getByName(id);
|
||||||
if (animData == null) return false;
|
if (animData == null) return false;
|
||||||
return animData.numFrames > 1;
|
return animData.numFrames > 1;
|
||||||
}
|
}
|
||||||
|
@ -428,6 +436,7 @@ class FunkinSprite extends FlxSprite
|
||||||
|
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
|
@:nullSafety(Off) // TODO: Remove when flixel.FlxSprite is null safed.
|
||||||
frames = null;
|
frames = null;
|
||||||
// Cancel all tweens so they don't continue to run on a destroyed sprite.
|
// Cancel all tweens so they don't continue to run on a destroyed sprite.
|
||||||
// This prevents crashes.
|
// This prevents crashes.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package funkin.modding.base;
|
package funkin.graphics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A script that can be tied to a FunkinSprite.
|
* A script that can be tied to a FunkinSprite.
|
||||||
* Create a scripted class that extends FunkinSprite to use this.
|
* Create a scripted class that extends FunkinSprite to use this.
|
||||||
*/
|
*/
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedFunkinSprite extends funkin.graphics.FunkinSprite implements HScriptedClass {}
|
class ScriptedFunkinSprite extends funkin.graphics.FunkinSprite implements polymod.hscript.HScriptedClass {}
|
|
@ -11,6 +11,7 @@ import flxanimate.animate.FlxKeyFrame;
|
||||||
/**
|
/**
|
||||||
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FlxAtlasSprite extends FlxAnimate
|
class FlxAtlasSprite extends FlxAnimate
|
||||||
{
|
{
|
||||||
static final SETTINGS:Settings =
|
static final SETTINGS:Settings =
|
||||||
|
@ -40,10 +41,11 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
*/
|
*/
|
||||||
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
var currentAnimation:String;
|
var currentAnimation:String = '';
|
||||||
|
|
||||||
var canPlayOtherAnims:Bool = true;
|
var canPlayOtherAnims:Bool = true;
|
||||||
|
|
||||||
|
@:nullSafety(Off) // null safety HATES new classes atm, it'll be fixed in haxe 4.0.0?
|
||||||
public function new(x:Float, y:Float, ?path:String, ?settings:Settings)
|
public function new(x:Float, y:Float, ?path:String, ?settings:Settings)
|
||||||
{
|
{
|
||||||
if (settings == null) settings = SETTINGS;
|
if (settings == null) settings = SETTINGS;
|
||||||
|
@ -110,7 +112,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
|
|
||||||
var _completeAnim:Bool = false;
|
var _completeAnim:Bool = false;
|
||||||
|
|
||||||
var fr:FlxKeyFrame = null;
|
var fr:Null<FlxKeyFrame> = null;
|
||||||
|
|
||||||
var looping:Bool = false;
|
var looping:Bool = false;
|
||||||
|
|
||||||
|
@ -195,8 +197,9 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
|
|
||||||
fr = null;
|
fr = null;
|
||||||
}
|
}
|
||||||
|
var frameLabelNames = getFrameLabelNames();
|
||||||
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
|
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
|
||||||
if (getFrameLabelNames().indexOf(id) != -1)
|
if (frameLabelNames != null && frameLabelNames.indexOf(id) != -1)
|
||||||
{
|
{
|
||||||
goToFrameLabel(id);
|
goToFrameLabel(id);
|
||||||
fr = anim.getFrameLabel(id);
|
fr = anim.getFrameLabel(id);
|
||||||
|
@ -266,7 +269,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
this.anim.goToFrameLabel(label);
|
this.anim.goToFrameLabel(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String>):Array<String>
|
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String>):Null<Array<String>>
|
||||||
{
|
{
|
||||||
var labels = this.anim.getFrameLabels(layer);
|
var labels = this.anim.getFrameLabels(layer);
|
||||||
var array = [];
|
var array = [];
|
||||||
|
@ -374,6 +377,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
|
var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
|
||||||
prevFrames.set(index, prevFrame);
|
prevFrames.set(index, prevFrame);
|
||||||
|
|
||||||
|
@:nullSafety(Off) // TODO: Remove this once flixel.system.frontEnds.BitmapFrontEnd has been null safed
|
||||||
var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
|
var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
|
||||||
frame.copyTo(frames.getByIndex(index));
|
frame.copyTo(frames.getByIndex(index));
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import openfl.filters.BitmapFilter;
|
||||||
/**
|
/**
|
||||||
* Provides cool stuff for `BitmapData`s that have a hardware texture internally.
|
* Provides cool stuff for `BitmapData`s that have a hardware texture internally.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
@:access(openfl.display.BitmapData)
|
@:access(openfl.display.BitmapData)
|
||||||
@:access(openfl.display3D.textures.TextureBase)
|
@:access(openfl.display3D.textures.TextureBase)
|
||||||
@:access(openfl.display3D.Context3D)
|
@:access(openfl.display3D.Context3D)
|
||||||
|
@ -19,7 +20,7 @@ class BitmapDataUtil
|
||||||
{
|
{
|
||||||
static function getCache():{sprite:Sprite, bitmap:Bitmap}
|
static function getCache():{sprite:Sprite, bitmap:Bitmap}
|
||||||
{
|
{
|
||||||
static var cache:{sprite:Sprite, bitmap:Bitmap} = null;
|
static var cache:Null<{sprite:Sprite, bitmap:Bitmap}> = null;
|
||||||
if (cache == null)
|
if (cache == null)
|
||||||
{
|
{
|
||||||
final sprite = new Sprite();
|
final sprite = new Sprite();
|
||||||
|
@ -56,7 +57,7 @@ class BitmapDataUtil
|
||||||
* @param format the format if the internal texture
|
* @param format the format if the internal texture
|
||||||
* @return the bitmap
|
* @return the bitmap
|
||||||
*/
|
*/
|
||||||
public static function create(width:Int, height:Int, format:Context3DTextureFormat = BGRA):FixedBitmapData
|
public static function create(width:Int, height:Int, format:Context3DTextureFormat = BGRA):Null<FixedBitmapData>
|
||||||
{
|
{
|
||||||
final texture = Lib.current.stage.context3D.createTexture(width, height, format, true);
|
final texture = Lib.current.stage.context3D.createTexture(width, height, format, true);
|
||||||
return FixedBitmapData.fromTexture(texture);
|
return FixedBitmapData.fromTexture(texture);
|
||||||
|
@ -83,6 +84,7 @@ class BitmapDataUtil
|
||||||
* @param width the width
|
* @param width the width
|
||||||
* @param height the height
|
* @param height the height
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety(Off) // the final context there is causing an error, idk how to fix it
|
||||||
public static function resizeTexture(texture:TextureBase, width:Int, height:Int):Void
|
public static function resizeTexture(texture:TextureBase, width:Int, height:Int):Void
|
||||||
{
|
{
|
||||||
if (texture.__width == width && texture.__height == height) return;
|
if (texture.__width == width && texture.__height == height) return;
|
||||||
|
@ -101,6 +103,7 @@ class BitmapDataUtil
|
||||||
* @param dst the destination bitmap
|
* @param dst the destination bitmap
|
||||||
* @param src the source bitmap
|
* @param src the source bitmap
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety(Off) // TODO: Remove this once openfl.display.Sprite has been null safed.
|
||||||
public static function copy(dst:BitmapData, src:BitmapData):Void
|
public static function copy(dst:BitmapData, src:BitmapData):Void
|
||||||
{
|
{
|
||||||
hardwareCheck(dst);
|
hardwareCheck(dst);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import openfl.display3D.textures.TextureBase;
|
||||||
/**
|
/**
|
||||||
* `BitmapData` is kinda broken so I fixed it.
|
* `BitmapData` is kinda broken so I fixed it.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
@:access(openfl.display3D.textures.TextureBase)
|
@:access(openfl.display3D.textures.TextureBase)
|
||||||
@:access(openfl.display.OpenGLRenderer)
|
@:access(openfl.display.OpenGLRenderer)
|
||||||
class FixedBitmapData extends BitmapData
|
class FixedBitmapData extends BitmapData
|
||||||
|
@ -29,7 +30,7 @@ class FixedBitmapData extends BitmapData
|
||||||
* @param texture the texture
|
* @param texture the texture
|
||||||
* @return the bitmap data
|
* @return the bitmap data
|
||||||
*/
|
*/
|
||||||
public static function fromTexture(texture:TextureBase):FixedBitmapData
|
public static function fromTexture(texture:Null<TextureBase>):Null<FixedBitmapData>
|
||||||
{
|
{
|
||||||
if (texture == null) return null;
|
if (texture == null) return null;
|
||||||
final bitmapData:FixedBitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0);
|
final bitmapData:FixedBitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import flixel.util.FlxColor;
|
||||||
* Yoinked from AustinEast, thanks hopefully u dont mind me using some of ur good code
|
* Yoinked from AustinEast, thanks hopefully u dont mind me using some of ur good code
|
||||||
* instead of my dumbass ugly code bro
|
* instead of my dumbass ugly code bro
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class MeshRender extends FlxStrip
|
class MeshRender extends FlxStrip
|
||||||
{
|
{
|
||||||
public var vertex_count(default, null):Int = 0;
|
public var vertex_count(default, null):Int = 0;
|
||||||
|
|
|
@ -2,12 +2,13 @@ package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class AdjustColorShader extends FlxRuntimeShader
|
class AdjustColorShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var hue(default, set):Float;
|
public var hue(default, set):Float = 0;
|
||||||
public var saturation(default, set):Float;
|
public var saturation(default, set):Float = 0;
|
||||||
public var brightness(default, set):Float;
|
public var brightness(default, set):Float = 0;
|
||||||
public var contrast(default, set):Float;
|
public var contrast(default, set):Float = 0;
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,12 +8,13 @@ typedef BlendModeShader =
|
||||||
var uBlendColor:ShaderParameter<Float>;
|
var uBlendColor:ShaderParameter<Float>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class BlendModeEffect
|
class BlendModeEffect
|
||||||
{
|
{
|
||||||
public var shader(default, null):BlendModeShader;
|
public var shader(default, null):BlendModeShader;
|
||||||
|
|
||||||
@:isVar
|
@:isVar
|
||||||
public var color(default, set):FlxColor;
|
public var color(default, set):FlxColor = new FlxColor();
|
||||||
|
|
||||||
public function new(shader:BlendModeShader, color:FlxColor):Void
|
public function new(shader:BlendModeShader, color:FlxColor):Void
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,10 +4,11 @@ import flixel.addons.display.FlxRuntimeShader;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.display.ShaderInput;
|
import openfl.display.ShaderInput;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class BlendModesShader extends FlxRuntimeShader
|
class BlendModesShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var camera:ShaderInput<BitmapData>;
|
public var camera:Null<ShaderInput<BitmapData>>;
|
||||||
public var cameraData:BitmapData;
|
public var cameraData:Null<BitmapData>;
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@ import flixel.tweens.FlxTween;
|
||||||
|
|
||||||
class BlueFade extends FlxShader
|
class BlueFade extends FlxShader
|
||||||
{
|
{
|
||||||
public var fadeVal(default, set):Float;
|
public var fadeVal(default, set):Float = 1;
|
||||||
|
|
||||||
function set_fadeVal(val:Float):Float
|
function set_fadeVal(val:Float):Float
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,9 +5,10 @@ import flixel.addons.display.FlxRuntimeShader;
|
||||||
/**
|
/**
|
||||||
* Note... not actually gaussian!
|
* Note... not actually gaussian!
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class GaussianBlurShader extends FlxRuntimeShader
|
class GaussianBlurShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var amount:Float;
|
public var amount:Float = 1;
|
||||||
|
|
||||||
public function new(amount:Float = 1.0)
|
public function new(amount:Float = 1.0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@ package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class Grayscale extends FlxRuntimeShader
|
class Grayscale extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var amount:Float = 1;
|
public var amount:Float = 1;
|
||||||
|
|
|
@ -2,11 +2,12 @@ package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class HSVShader extends FlxRuntimeShader
|
class HSVShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var hue(default, set):Float;
|
public var hue(default, set):Float = 1;
|
||||||
public var saturation(default, set):Float;
|
public var saturation(default, set):Float = 1;
|
||||||
public var value(default, set):Float;
|
public var value(default, set):Float = 1;
|
||||||
|
|
||||||
public function new(h:Float = 1, s:Float = 1, v:Float = 1)
|
public function new(h:Float = 1, s:Float = 1, v:Float = 1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,9 +5,10 @@ import flixel.addons.display.FlxRuntimeShader;
|
||||||
/**
|
/**
|
||||||
* Create a little dotting effect.
|
* Create a little dotting effect.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class InverseDotsShader extends FlxRuntimeShader
|
class InverseDotsShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var amount:Float;
|
public var amount:Float = 0;
|
||||||
|
|
||||||
public function new(amount:Float = 1.0)
|
public function new(amount:Float = 1.0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@ package funkin.graphics.shaders;
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class MosaicEffect extends FlxRuntimeShader
|
class MosaicEffect extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public var blockSize:FlxPoint = FlxPoint.get(1.0, 1.0);
|
public var blockSize:FlxPoint = FlxPoint.get(1.0, 1.0);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package funkin.graphics.shaders;
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class PuddleShader extends FlxRuntimeShader
|
class PuddleShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public function new()
|
public function new()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class WaveShader extends FlxShader
|
class WaveShader extends FlxShader
|
||||||
{
|
{
|
||||||
@:glFragmentSource('
|
@:glFragmentSource('
|
||||||
|
|
|
@ -18,16 +18,17 @@ enum WiggleEffectType
|
||||||
* 2. Call `sprite.shader = wiggleEffect` on the target sprite.
|
* 2. Call `sprite.shader = wiggleEffect` on the target sprite.
|
||||||
* 3. Call the update() method on the instance every frame.
|
* 3. Call the update() method on the instance every frame.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class WiggleEffectRuntime extends FlxRuntimeShader
|
class WiggleEffectRuntime extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
public static function getEffectTypeId(v:WiggleEffectType):Int
|
public static function getEffectTypeId(v:Null<WiggleEffectType>):Int
|
||||||
{
|
{
|
||||||
return WiggleEffectType.getConstructors().indexOf(Std.string(v));
|
return WiggleEffectType.getConstructors().indexOf(Std.string(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
public var effectType(default, set):WiggleEffectType = DREAMY;
|
public var effectType(default, set):Null<WiggleEffectType> = DREAMY;
|
||||||
|
|
||||||
function set_effectType(v:WiggleEffectType):WiggleEffectType
|
function set_effectType(v:Null<WiggleEffectType>):Null<WiggleEffectType>
|
||||||
{
|
{
|
||||||
this.setInt('effectType', getEffectTypeId(v));
|
this.setInt('effectType', getEffectTypeId(v));
|
||||||
return effectType = v;
|
return effectType = v;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import openfl.net.NetStream;
|
||||||
* This does NOT replace hxvlc, nor does hxvlc replace this.
|
* This does NOT replace hxvlc, nor does hxvlc replace this.
|
||||||
* hxvlc only works on desktop and does not work on HTML5!
|
* hxvlc only works on desktop and does not work on HTML5!
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FlxVideo extends FunkinSprite
|
class FlxVideo extends FunkinSprite
|
||||||
{
|
{
|
||||||
var video:Video;
|
var video:Video;
|
||||||
|
@ -22,14 +23,16 @@ class FlxVideo extends FunkinSprite
|
||||||
/**
|
/**
|
||||||
* A callback to execute when the video finishes.
|
* A callback to execute when the video finishes.
|
||||||
*/
|
*/
|
||||||
public var finishCallback:Void->Void;
|
public var finishCallback:Null<Void->Void> = null;
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
public function new(videoPath:String)
|
public function new(videoPath:String)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.videoPath = videoPath;
|
this.videoPath = videoPath;
|
||||||
|
|
||||||
|
@:nullSafety(Off) // Why do I to do this here as well for this to build?
|
||||||
makeGraphic(2, 2, FlxColor.TRANSPARENT);
|
makeGraphic(2, 2, FlxColor.TRANSPARENT);
|
||||||
|
|
||||||
video = new Video();
|
video = new Video();
|
||||||
|
@ -72,7 +75,7 @@ class FlxVideo extends FunkinSprite
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoAvailable:Bool = false;
|
var videoAvailable:Bool = false;
|
||||||
var frameTimer:Float;
|
var frameTimer:Float = 0;
|
||||||
|
|
||||||
static final FRAME_RATE:Float = 60;
|
static final FRAME_RATE:Float = 60;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import hxvlc.flixel.FlxVideoSprite;
|
||||||
* Not to be confused with FlxVideo, this is a hxvlc based video class
|
* Not to be confused with FlxVideo, this is a hxvlc based video class
|
||||||
* We override it simply to correct/control our volume easier.
|
* We override it simply to correct/control our volume easier.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FunkinVideoSprite extends FlxVideoSprite
|
class FunkinVideoSprite extends FlxVideoSprite
|
||||||
{
|
{
|
||||||
public function new(x:Float = 0, y:Float = 0)
|
public function new(x:Float = 0, y:Float = 0)
|
||||||
|
|
|
@ -1067,9 +1067,9 @@ class Controls extends FlxActionSet
|
||||||
case Control.FREEPLAY_CHAR_SELECT:
|
case Control.FREEPLAY_CHAR_SELECT:
|
||||||
return [X];
|
return [X];
|
||||||
case Control.FREEPLAY_JUMP_TO_TOP:
|
case Control.FREEPLAY_JUMP_TO_TOP:
|
||||||
return [];
|
return [RIGHT_STICK_DIGITAL_UP];
|
||||||
case Control.FREEPLAY_JUMP_TO_BOTTOM:
|
case Control.FREEPLAY_JUMP_TO_BOTTOM:
|
||||||
return [];
|
return [RIGHT_STICK_DIGITAL_DOWN];
|
||||||
case Control.VOLUME_UP:
|
case Control.VOLUME_UP:
|
||||||
[];
|
[];
|
||||||
case Control.VOLUME_DOWN:
|
case Control.VOLUME_DOWN:
|
||||||
|
|
|
@ -10,6 +10,7 @@ import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
import funkin.play.notes.notekind.NoteKindManager;
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import funkin.data.freeplay.player.PlayerRegistry;
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
|
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||||
import funkin.data.stage.StageRegistry;
|
import funkin.data.stage.StageRegistry;
|
||||||
import funkin.data.stickers.StickerRegistry;
|
import funkin.data.stickers.StickerRegistry;
|
||||||
import funkin.data.freeplay.album.AlbumRegistry;
|
import funkin.data.freeplay.album.AlbumRegistry;
|
||||||
|
@ -255,9 +256,29 @@ class PolymodHandler
|
||||||
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||||
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||||
|
|
||||||
|
// Backward compatibility for certain scripted classes outside `funkin.modding.base`.
|
||||||
|
Polymod.addImportAlias('funkin.modding.base.ScriptedFunkinSprite', funkin.graphics.ScriptedFunkinSprite);
|
||||||
|
Polymod.addImportAlias('funkin.modding.base.ScriptedMusicBeatState', funkin.ui.ScriptedMusicBeatState);
|
||||||
|
Polymod.addImportAlias('funkin.modding.base.ScriptedMusicBeatSubState', funkin.ui.ScriptedMusicBeatSubState);
|
||||||
|
|
||||||
// `funkin.util.FileUtil` has unrestricted access to the file system.
|
// `funkin.util.FileUtil` has unrestricted access to the file system.
|
||||||
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
|
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
// `funkin.api.newgrounds.Leaderboards` allows for submitting cheated scores.
|
||||||
|
Polymod.addImportAlias('funkin.api.newgrounds.Leaderboards', funkin.api.newgrounds.Leaderboards.LeaderboardsSandboxed);
|
||||||
|
|
||||||
|
// `funkin.api.newgrounds.Medals` allows for unfair granting of medals.
|
||||||
|
Polymod.addImportAlias('funkin.api.newgrounds.Medals', funkin.api.newgrounds.Medals.MedalsSandboxed);
|
||||||
|
|
||||||
|
// `funkin.api.newgrounds.NewgroundsClientSandboxed` allows for submitting cheated data.
|
||||||
|
Polymod.addImportAlias('funkin.api.newgrounds.NewgroundsClient', funkin.api.newgrounds.NewgroundsClient.NewgroundsClientSandboxed);
|
||||||
|
#end
|
||||||
|
|
||||||
|
#if FEATURE_DISCORD_RPC
|
||||||
|
Polymod.addImportAlias('funkin.api.discord.DiscordClient', funkin.api.discord.DiscordClient.DiscordClientSandboxed);
|
||||||
|
#end
|
||||||
|
|
||||||
// Add blacklisting for prohibited classes and packages.
|
// Add blacklisting for prohibited classes and packages.
|
||||||
|
|
||||||
// `Sys`
|
// `Sys`
|
||||||
|
@ -276,9 +297,9 @@ class PolymodHandler
|
||||||
// Lib.load() can load malicious DLLs
|
// Lib.load() can load malicious DLLs
|
||||||
Polymod.blacklistImport('cpp.Lib');
|
Polymod.blacklistImport('cpp.Lib');
|
||||||
|
|
||||||
// `Unserializer`
|
// `haxe.Unserializer`
|
||||||
// Unserializer.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
// Unserializer.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||||
Polymod.blacklistImport('Unserializer');
|
Polymod.blacklistImport('haxe.Unserializer');
|
||||||
|
|
||||||
// `lime.system.CFFI`
|
// `lime.system.CFFI`
|
||||||
// Can load and execute compiled binaries.
|
// Can load and execute compiled binaries.
|
||||||
|
@ -310,6 +331,7 @@ class PolymodHandler
|
||||||
{
|
{
|
||||||
if (cls == null) continue;
|
if (cls == null) continue;
|
||||||
var className:String = Type.getClassName(cls);
|
var className:String = Type.getClassName(cls);
|
||||||
|
if (polymod.hscript._internal.PolymodScriptClass.importOverrides.exists(className)) continue;
|
||||||
Polymod.blacklistImport(className);
|
Polymod.blacklistImport(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,15 +344,6 @@ class PolymodHandler
|
||||||
Polymod.blacklistImport(className);
|
Polymod.blacklistImport(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `funkin.api.newgrounds.*`
|
|
||||||
// Contains functions which allow for cheating medals and leaderboards.
|
|
||||||
for (cls in ClassMacro.listClassesInPackage('funkin.api.newgrounds'))
|
|
||||||
{
|
|
||||||
if (cls == null) continue;
|
|
||||||
var className:String = Type.getClassName(cls);
|
|
||||||
Polymod.blacklistImport(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `io.newgrounds.*`
|
// `io.newgrounds.*`
|
||||||
// Contains functions which allow for cheating medals and leaderboards.
|
// Contains functions which allow for cheating medals and leaderboards.
|
||||||
for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))
|
for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))
|
||||||
|
@ -348,6 +361,16 @@ class PolymodHandler
|
||||||
var className:String = Type.getClassName(cls);
|
var className:String = Type.getClassName(cls);
|
||||||
Polymod.blacklistImport(className);
|
Polymod.blacklistImport(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `funkin.util.macro.*`
|
||||||
|
// CompiledClassList's get function allows access to sys and Newgrounds classes
|
||||||
|
// None of the classes are suitable for mods anyway
|
||||||
|
for (cls in ClassMacro.listClassesInPackage('funkin.util.macro'))
|
||||||
|
{
|
||||||
|
if (cls == null) continue;
|
||||||
|
var className:String = Type.getClassName(cls);
|
||||||
|
Polymod.blacklistImport(className);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -489,6 +512,7 @@ class PolymodHandler
|
||||||
AlbumRegistry.instance.loadEntries();
|
AlbumRegistry.instance.loadEntries();
|
||||||
StageRegistry.instance.loadEntries();
|
StageRegistry.instance.loadEntries();
|
||||||
StickerRegistry.instance.loadEntries();
|
StickerRegistry.instance.loadEntries();
|
||||||
|
FreeplayStyleRegistry.instance.loadEntries();
|
||||||
|
|
||||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||||
NoteKindManager.loadScripts();
|
NoteKindManager.loadScripts();
|
||||||
|
|
13
source/funkin/modding/base/Object.hx
Normal file
13
source/funkin/modding/base/Object.hx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty base class meant to be extended by scripts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Object {
|
||||||
|
public function new() {}
|
||||||
|
|
||||||
|
public function toString():String {
|
||||||
|
return "(Object)";
|
||||||
|
}
|
||||||
|
}
|
8
source/funkin/modding/base/ScriptedFlxBasic.hx
Normal file
8
source/funkin/modding/base/ScriptedFlxBasic.hx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A script that can be tied to an FlxBasic.
|
||||||
|
* Create a scripted class that extends FlxBasic to use this.
|
||||||
|
*/
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxBasic extends flixel.FlxBasic implements HScriptedClass {}
|
8
source/funkin/modding/base/ScriptedFlxObject.hx
Normal file
8
source/funkin/modding/base/ScriptedFlxObject.hx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A script that can be tied to an FlxObject.
|
||||||
|
* Create a scripted class that extends FlxObject to use this.
|
||||||
|
*/
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedFlxObject extends flixel.FlxObject implements HScriptedClass {}
|
8
source/funkin/modding/base/ScriptedObject.hx
Normal file
8
source/funkin/modding/base/ScriptedObject.hx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package funkin.modding.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A script that can be tied to an Object (empty base class).
|
||||||
|
* Create a scripted class that extends Object to use this.
|
||||||
|
*/
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedObject extends funkin.modding.base.Object implements HScriptedClass {}
|
|
@ -255,7 +255,7 @@ class HoldNoteScriptEvent extends NoteScriptEvent
|
||||||
*/
|
*/
|
||||||
public var doesNotesplash:Bool = false;
|
public var doesNotesplash:Bool = false;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, holdNote:SustainTrail, healthChange:Float, score:Int, isComboBreak:Bool, cancelable:Bool = false):Void
|
public function new(type:ScriptEventType, holdNote:SustainTrail, healthChange:Float, score:Int, isComboBreak:Bool, comboCount:Int = 0, cancelable:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(type, null, healthChange, comboCount, true);
|
super(type, null, healthChange, comboCount, true);
|
||||||
this.holdNote = holdNote;
|
this.holdNote = holdNote;
|
||||||
|
|
|
@ -176,13 +176,13 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game regains focus.
|
* Called when the game regains focus.
|
||||||
* This does not get called if "Auto Pause" is disabled.
|
* This does not get called if "Pause on Unfocus" is disabled.
|
||||||
*/
|
*/
|
||||||
public function onFocusGained(event:FocusScriptEvent) {}
|
public function onFocusGained(event:FocusScriptEvent) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game loses focus.
|
* Called when the game loses focus.
|
||||||
* This does not get called if "Auto Pause" is disabled.
|
* This does not get called if "Pause on Unfocus" is disabled.
|
||||||
*/
|
*/
|
||||||
public function onFocusLost(event:FocusScriptEvent) {}
|
public function onFocusLost(event:FocusScriptEvent) {}
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smoothly lerp the camera
|
// Smoothly lerp the camera
|
||||||
FlxG.camera.zoom = MathUtil.smoothLerp(FlxG.camera.zoom, targetCameraZoom, elapsed, CAMERA_ZOOM_DURATION);
|
FlxG.camera.zoom = MathUtil.smoothLerpPrecision(FlxG.camera.zoom, targetCameraZoom, elapsed, CAMERA_ZOOM_DURATION);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Handle user inputs.
|
// Handle user inputs.
|
||||||
|
@ -391,7 +391,6 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
if (!isEnding)
|
if (!isEnding)
|
||||||
{
|
{
|
||||||
isEnding = true;
|
isEnding = true;
|
||||||
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
|
||||||
|
|
||||||
// Stop death quotes immediately.
|
// Stop death quotes immediately.
|
||||||
hasPlayedDeathQuote = true;
|
hasPlayedDeathQuote = true;
|
||||||
|
@ -401,6 +400,8 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
deathQuoteSound = null;
|
deathQuoteSound = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||||
|
|
||||||
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -68,22 +68,13 @@ class PauseSubState extends MusicBeatSubState
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause menu entries for when the game is paused during a video cutscene.
|
* Pause menu entries for when the game is paused during a cutscene.
|
||||||
|
* `[CUTSCENE]` is replaced with the name of the cutscene type.
|
||||||
*/
|
*/
|
||||||
static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array<PauseMenuEntry> = [
|
static final PAUSE_MENU_ENTRIES_CUTSCENE:Array<PauseMenuEntry> = [
|
||||||
{text: 'Resume', callback: resume},
|
{text: 'Resume', callback: resume},
|
||||||
{text: 'Skip Cutscene', callback: skipVideoCutscene},
|
{text: 'Skip [CUTSCENE]', callback: skipCutscene},
|
||||||
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
{text: 'Restart [CUTSCENE]', callback: restartCutscene},
|
||||||
{text: 'Exit to Menu', callback: quitToMenu},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause menu entries for when the game is paused during a conversation.
|
|
||||||
*/
|
|
||||||
static final PAUSE_MENU_ENTRIES_CONVERSATION:Array<PauseMenuEntry> = [
|
|
||||||
{text: 'Resume', callback: resume},
|
|
||||||
{text: 'Skip Dialogue', callback: skipConversation},
|
|
||||||
{text: 'Restart Dialogue', callback: restartConversation},
|
|
||||||
{text: 'Exit to Menu', callback: quitToMenu},
|
{text: 'Exit to Menu', callback: quitToMenu},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -533,10 +524,15 @@ class PauseSubState extends MusicBeatSubState
|
||||||
|
|
||||||
// Add the back button.
|
// Add the back button.
|
||||||
currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone());
|
currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone());
|
||||||
case PauseMode.Conversation:
|
case Cutscene(entryName, pauseName, onResume, onSkip, onRestart):
|
||||||
currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone();
|
var entries:Array<PauseMenuEntry> = [];
|
||||||
case PauseMode.Cutscene:
|
|
||||||
currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone();
|
for (entry in PAUSE_MENU_ENTRIES_CUTSCENE)
|
||||||
|
{
|
||||||
|
entries.push({text: StringTools.replace(entry.text, '[CUTSCENE]', entryName), callback: entry.callback});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMenuEntries = entries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,10 +597,8 @@ class PauseSubState extends MusicBeatSubState
|
||||||
metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls';
|
metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls';
|
||||||
case Charting:
|
case Charting:
|
||||||
metadataDeaths.text = 'Chart Editor Preview';
|
metadataDeaths.text = 'Chart Editor Preview';
|
||||||
case Conversation:
|
case Cutscene(entryName, pauseName, onResume, onSkip, onRestart):
|
||||||
metadataDeaths.text = 'Dialogue Paused';
|
metadataDeaths.text = '$pauseName Paused';
|
||||||
case Cutscene:
|
|
||||||
metadataDeaths.text = 'Video Paused';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,8 +612,15 @@ class PauseSubState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
static function resume(state:PauseSubState):Void
|
static function resume(state:PauseSubState):Void
|
||||||
{
|
{
|
||||||
// Resume a paused video if it exists.
|
switch (state.currentMode)
|
||||||
VideoCutscene.resumeVideo();
|
{
|
||||||
|
case Cutscene(entryName, pauseName, onResume, onSkip, onRestart):
|
||||||
|
if (onResume != null)
|
||||||
|
{
|
||||||
|
onResume();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
state.close();
|
state.close();
|
||||||
}
|
}
|
||||||
|
@ -681,46 +682,40 @@ class PauseSubState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restart the paused video cutscene, then resume the game.
|
* Restart the paused cutscene, then resume the game.
|
||||||
* @param state The current PauseSubState.
|
* @param state The current PauseSubState.
|
||||||
*/
|
*/
|
||||||
static function restartVideoCutscene(state:PauseSubState):Void
|
static function restartCutscene(state:PauseSubState):Void
|
||||||
{
|
{
|
||||||
VideoCutscene.restartVideo();
|
switch (state.currentMode)
|
||||||
|
{
|
||||||
|
case Cutscene(entryName, pauseName, onResume, onSkip, onRestart):
|
||||||
|
if (onRestart != null)
|
||||||
|
{
|
||||||
|
onRestart(); // VideoCutscene.restartVideo(); on videos
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
state.close();
|
state.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip the paused video cutscene, then resume the game.
|
* Skip the paused cutscene, then resume the game.
|
||||||
* @param state The current PauseSubState.
|
* @param state The current PauseSubState.
|
||||||
*/
|
*/
|
||||||
static function skipVideoCutscene(state:PauseSubState):Void
|
static function skipCutscene(state:PauseSubState):Void
|
||||||
{
|
{
|
||||||
VideoCutscene.finishVideo();
|
switch (state.currentMode)
|
||||||
state.close();
|
{
|
||||||
}
|
case Cutscene(entryName, pauseName, onResume, onSkip, onRestart):
|
||||||
|
if (onSkip != null)
|
||||||
|
{
|
||||||
|
onSkip(); // VideoCutscene.finishVideo(); on videos
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Restart the paused conversation, then resume the game.
|
|
||||||
* @param state The current PauseSubState.
|
|
||||||
*/
|
|
||||||
static function restartConversation(state:PauseSubState):Void
|
|
||||||
{
|
|
||||||
if (PlayState.instance?.currentConversation == null) return;
|
|
||||||
|
|
||||||
PlayState.instance.currentConversation.resetConversation();
|
|
||||||
state.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip the paused conversation, then resume the game.
|
|
||||||
* @param state The current PauseSubState.
|
|
||||||
*/
|
|
||||||
static function skipConversation(state:PauseSubState):Void
|
|
||||||
{
|
|
||||||
if (PlayState.instance?.currentConversation == null) return;
|
|
||||||
|
|
||||||
PlayState.instance.currentConversation.skipConversation();
|
|
||||||
state.close();
|
state.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -766,11 +761,15 @@ class PauseSubState extends MusicBeatSubState
|
||||||
* Quit the game and return to the chart editor.
|
* Quit the game and return to the chart editor.
|
||||||
* @param state The current PauseSubState.
|
* @param state The current PauseSubState.
|
||||||
*/
|
*/
|
||||||
|
@:access(funkin.play.PlayState)
|
||||||
static function quitToChartEditor(state:PauseSubState):Void
|
static function quitToChartEditor(state:PauseSubState):Void
|
||||||
{
|
{
|
||||||
|
// This should come first because the sounds list gets cleared!
|
||||||
|
PlayState.instance?.forEachPausedSound(s -> s.destroy());
|
||||||
state.close();
|
state.close();
|
||||||
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
FlxG.sound.music?.pause(); // Don't reset song position!
|
||||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
PlayState.instance?.vocals?.pause();
|
||||||
|
PlayState.instance?.close(); // This only works because PlayState is a substate!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -795,14 +794,14 @@ enum PauseMode
|
||||||
Difficulty;
|
Difficulty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The menu displayed when the player pauses the game during a conversation.
|
* The menu displayed when the player pauses the game during a cutscene.
|
||||||
|
* @param entryName The name to show in entries.
|
||||||
|
* @param pauseName The name to show on the "Cutscene Paused" text.
|
||||||
|
* @param onResume Gets called when `Resume` is selected.
|
||||||
|
* @param onSkip Gets called when `Skip [entryName]` is selected.
|
||||||
|
* @param onRestart Gets called when `Restart [entryName]` is selected.
|
||||||
*/
|
*/
|
||||||
Conversation;
|
Cutscene(entryName:String, pauseName:String, onResume:Void->Void, onSkip:Void->Void, onRestart:Void->Void);
|
||||||
|
|
||||||
/**
|
|
||||||
* The menu displayed when the player pauses the game during a video cutscene.
|
|
||||||
*/
|
|
||||||
Cutscene;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,7 @@ import flixel.FlxObject;
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
|
import flixel.sound.FlxSound;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import flixel.ui.FlxBar;
|
import flixel.ui.FlxBar;
|
||||||
|
@ -185,6 +186,11 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var needsReset:Bool = false;
|
public var needsReset:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A timer that gets active once resetting happens. Used to vwoosh in notes.
|
||||||
|
*/
|
||||||
|
public var vwooshTimer:FlxTimer = new FlxTimer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current 'Blueball Counter' to display in the pause menu.
|
* The current 'Blueball Counter' to display in the pause menu.
|
||||||
* Resets when you beat a song or go back to the main menu.
|
* Resets when you beat a song or go back to the main menu.
|
||||||
|
@ -304,13 +310,13 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the game is currently in Practice Mode.
|
* Whether the game is currently in Practice Mode.
|
||||||
* If true, player will not lose gain or lose score from notes.
|
* If true, player will not gain or lose score from notes.
|
||||||
*/
|
*/
|
||||||
public var isPracticeMode:Bool = false;
|
public var isPracticeMode:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the game is currently in Bot Play Mode.
|
* Whether the game is currently in Bot Play Mode.
|
||||||
* If true, player will not lose gain or lose score from notes.
|
* If true, player will not gain or lose score from notes.
|
||||||
*/
|
*/
|
||||||
public var isBotPlayMode:Bool = false;
|
public var isBotPlayMode:Bool = false;
|
||||||
|
|
||||||
|
@ -432,6 +438,11 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
var cameraTweensPausedBySubState:List<FlxTween> = new List<FlxTween>();
|
var cameraTweensPausedBySubState:List<FlxTween> = new List<FlxTween>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track any sounds we've paused for a Pause substate, so we can unpause them when we return.
|
||||||
|
*/
|
||||||
|
var soundsPausedBySubState:List<FlxSound> = new List<FlxSound>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* False until `create()` has completed.
|
* False until `create()` has completed.
|
||||||
*/
|
*/
|
||||||
|
@ -807,7 +818,6 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
var list = FlxG.sound.list;
|
|
||||||
updateHealthBar();
|
updateHealthBar();
|
||||||
updateScoreText();
|
updateScoreText();
|
||||||
|
|
||||||
|
@ -837,9 +847,9 @@ class PlayState extends MusicBeatSubState
|
||||||
// Reset music properly.
|
// Reset music properly.
|
||||||
if (FlxG.sound.music != null)
|
if (FlxG.sound.music != null)
|
||||||
{
|
{
|
||||||
FlxG.sound.music.time = startTimestamp - Conductor.instance.combinedOffset;
|
|
||||||
FlxG.sound.music.pitch = playbackRate;
|
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
|
FlxG.sound.music.time = startTimestamp;
|
||||||
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overrideMusic)
|
if (!overrideMusic)
|
||||||
|
@ -854,7 +864,7 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
vocals.time = 0 - Conductor.instance.combinedOffset;
|
vocals.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||||
|
|
||||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 1;
|
if (FlxG.sound.music != null) FlxG.sound.music.volume = 1;
|
||||||
vocals.volume = 1;
|
vocals.volume = 1;
|
||||||
|
@ -875,9 +885,6 @@ class PlayState extends MusicBeatSubState
|
||||||
// Delete all notes and reset the arrays.
|
// Delete all notes and reset the arrays.
|
||||||
regenNoteData();
|
regenNoteData();
|
||||||
|
|
||||||
// so the song doesn't start too early :D
|
|
||||||
Conductor.instance.update(-5000, false);
|
|
||||||
|
|
||||||
// Reset camera zooming
|
// Reset camera zooming
|
||||||
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
|
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
|
||||||
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
|
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
|
||||||
|
@ -886,10 +893,13 @@ class PlayState extends MusicBeatSubState
|
||||||
health = Constants.HEALTH_STARTING;
|
health = Constants.HEALTH_STARTING;
|
||||||
songScore = 0;
|
songScore = 0;
|
||||||
Highscore.tallies.combo = 0;
|
Highscore.tallies.combo = 0;
|
||||||
|
|
||||||
|
// so the song doesn't start too early :D
|
||||||
|
var vwooshDelay:Float = 0.5;
|
||||||
|
Conductor.instance.update(-vwooshDelay * 1000 + startTimestamp + Conductor.instance.beatLengthMs * -5);
|
||||||
|
|
||||||
// timer for vwoosh
|
// timer for vwoosh
|
||||||
var vwooshTimer = new FlxTimer();
|
vwooshTimer.start(vwooshDelay, function(_) {
|
||||||
vwooshTimer.start(0.5, function(t:FlxTimer) {
|
|
||||||
Conductor.instance.update(startTimestamp - Conductor.instance.combinedOffset, false);
|
|
||||||
if (playerStrumline.notes.length == 0) playerStrumline.updateNotes();
|
if (playerStrumline.notes.length == 0) playerStrumline.updateNotes();
|
||||||
if (opponentStrumline.notes.length == 0) opponentStrumline.updateNotes();
|
if (opponentStrumline.notes.length == 0) opponentStrumline.updateNotes();
|
||||||
playerStrumline.vwooshInNotes();
|
playerStrumline.vwooshInNotes();
|
||||||
|
@ -897,6 +907,9 @@ class PlayState extends MusicBeatSubState
|
||||||
Countdown.performCountdown();
|
Countdown.performCountdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Stops any existing countdown.
|
||||||
|
Countdown.stopCountdown();
|
||||||
|
|
||||||
// Reset the health icons.
|
// Reset the health icons.
|
||||||
currentStage?.getBoyfriend()?.initHealthIcon(false);
|
currentStage?.getBoyfriend()?.initHealthIcon(false);
|
||||||
currentStage?.getDad()?.initHealthIcon(true);
|
currentStage?.getDad()?.initHealthIcon(true);
|
||||||
|
@ -959,18 +972,16 @@ class PlayState extends MusicBeatSubState
|
||||||
// Enable drawing while the substate is open, allowing the game state to be shown behind the pause menu.
|
// Enable drawing while the substate is open, allowing the game state to be shown behind the pause menu.
|
||||||
persistentDraw = true;
|
persistentDraw = true;
|
||||||
|
|
||||||
// There is a 1/1000 change to use a special pause menu.
|
// Prevent vwoosh timer from starting countdown in pause menu
|
||||||
|
vwooshTimer.active = false;
|
||||||
|
|
||||||
|
// There is a 1/1000 chance to use a special pause menu.
|
||||||
// This prevents the player from resuming, but that's the point.
|
// This prevents the player from resuming, but that's the point.
|
||||||
// It's a reference to Gitaroo Man, which doesn't let you pause the game.
|
// It's a reference to Gitaroo Man, which doesn't let you pause the game.
|
||||||
if (!isSubState && event.gitaroo)
|
if (!isSubState && event.gitaroo)
|
||||||
{
|
{
|
||||||
this.remove(currentStage);
|
this.remove(currentStage);
|
||||||
FlxG.switchState(() -> new GitarooPause(
|
FlxG.switchState(() -> new GitarooPause(lastParams));
|
||||||
{
|
|
||||||
targetSong: currentSong,
|
|
||||||
targetDifficulty: currentDifficulty,
|
|
||||||
targetVariation: currentVariation,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1127,6 +1138,8 @@ class PlayState extends MusicBeatSubState
|
||||||
playerStrumline.clean();
|
playerStrumline.clean();
|
||||||
opponentStrumline.clean();
|
opponentStrumline.clean();
|
||||||
|
|
||||||
|
vwooshTimer.cancel();
|
||||||
|
|
||||||
songScore = 0;
|
songScore = 0;
|
||||||
updateScoreText();
|
updateScoreText();
|
||||||
|
|
||||||
|
@ -1230,9 +1243,29 @@ class PlayState extends MusicBeatSubState
|
||||||
musicPausedBySubState = true;
|
musicPausedBySubState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause vocals.
|
// Pause any sounds that are playing and keep track of them.
|
||||||
// Not tracking that we've done this via a bool because vocal re-syncing involves pausing the vocals anyway.
|
// Vocals are also paused here but are not included as they are handled separately.
|
||||||
if (vocals != null) vocals.pause();
|
if (Std.isOfType(subState, PauseSubState))
|
||||||
|
{
|
||||||
|
FlxG.sound.list.forEachAlive(function(sound:FlxSound) {
|
||||||
|
if (!sound.active || sound == FlxG.sound.music) return;
|
||||||
|
// In case it's a scheduled sound
|
||||||
|
var funkinSound:FunkinSound = cast sound;
|
||||||
|
if (funkinSound != null && !funkinSound.isPlaying) return;
|
||||||
|
if (!sound.playing && sound.time >= 0) return;
|
||||||
|
|
||||||
|
sound.pause();
|
||||||
|
soundsPausedBySubState.add(sound);
|
||||||
|
});
|
||||||
|
|
||||||
|
vocals?.forEach(function(voice:FunkinSound) {
|
||||||
|
soundsPausedBySubState.remove(voice);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vocals?.pause();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause camera tweening, and keep track of which tweens we pause.
|
// Pause camera tweening, and keep track of which tweens we pause.
|
||||||
|
@ -1281,6 +1314,9 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (event.eventCanceled) return;
|
if (event.eventCanceled) return;
|
||||||
|
|
||||||
|
// Resume vwooshTimer
|
||||||
|
if (!vwooshTimer.finished) vwooshTimer.active = true;
|
||||||
|
|
||||||
// Resume music if we paused it.
|
// Resume music if we paused it.
|
||||||
if (musicPausedBySubState)
|
if (musicPausedBySubState)
|
||||||
{
|
{
|
||||||
|
@ -1288,6 +1324,8 @@ class PlayState extends MusicBeatSubState
|
||||||
musicPausedBySubState = false;
|
musicPausedBySubState = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forEachPausedSound((s) -> needsReset ? s.destroy() : s.resume());
|
||||||
|
|
||||||
// Resume camera tweens if we paused any.
|
// Resume camera tweens if we paused any.
|
||||||
for (camTween in cameraTweensPausedBySubState)
|
for (camTween in cameraTweensPausedBySubState)
|
||||||
{
|
{
|
||||||
|
@ -1423,6 +1461,10 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
performCleanup();
|
performCleanup();
|
||||||
|
|
||||||
|
// `performCleanup()` clears the static reference to this state
|
||||||
|
// scripts might still need it, so we set it back to `this`
|
||||||
|
instance = this;
|
||||||
|
|
||||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||||
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
||||||
LoadingState.loadPlayState(lastParams);
|
LoadingState.loadPlayState(lastParams);
|
||||||
|
@ -1510,13 +1552,6 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
// trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.instance.currentBeat} % ${cameraZoomRate} == ${Conductor.instance.currentBeat % cameraZoomRate}}');
|
// trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.instance.currentBeat} % ${cameraZoomRate} == ${Conductor.instance.currentBeat % cameraZoomRate}}');
|
||||||
|
|
||||||
// That combo milestones that got spoiled that one time.
|
|
||||||
// Comes with NEAT visual and audio effects.
|
|
||||||
|
|
||||||
// bruh this var is bonkers i thot it was a function lmfaooo
|
|
||||||
|
|
||||||
// Break up into individual lines to aid debugging.
|
|
||||||
|
|
||||||
if (playerStrumline != null) playerStrumline.onBeatHit();
|
if (playerStrumline != null) playerStrumline.onBeatHit();
|
||||||
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
||||||
|
|
||||||
|
@ -2056,9 +2091,9 @@ class PlayState extends MusicBeatSubState
|
||||||
FlxG.sound.music.onComplete = function() {
|
FlxG.sound.music.onComplete = function() {
|
||||||
if (mayPauseGame) endSong(skipEndingTransition);
|
if (mayPauseGame) endSong(skipEndingTransition);
|
||||||
};
|
};
|
||||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
|
||||||
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
FlxG.sound.music.pause();
|
||||||
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
FlxG.sound.music.time = startTimestamp;
|
||||||
FlxG.sound.music.pitch = playbackRate;
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
|
|
||||||
// Prevent the volume from being wrong.
|
// Prevent the volume from being wrong.
|
||||||
|
@ -2067,13 +2102,17 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
trace('Playing vocals...');
|
trace('Playing vocals...');
|
||||||
add(vocals);
|
add(vocals);
|
||||||
vocals.play();
|
|
||||||
vocals.volume = 1.0;
|
vocals.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||||
vocals.pitch = playbackRate;
|
vocals.pitch = playbackRate;
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.volume = 1.0;
|
||||||
|
|
||||||
|
// trace('STARTING SONG AT:');
|
||||||
// trace('${FlxG.sound.music.time}');
|
// trace('${FlxG.sound.music.time}');
|
||||||
// trace('${vocals.time}');
|
// trace('${vocals.time}');
|
||||||
resyncVocals();
|
|
||||||
|
FlxG.sound.music.play();
|
||||||
|
vocals.play();
|
||||||
|
|
||||||
#if FEATURE_DISCORD_RPC
|
#if FEATURE_DISCORD_RPC
|
||||||
// Updating Discord Rich Presence (with Time Left)
|
// Updating Discord Rich Presence (with Time Left)
|
||||||
|
@ -2102,7 +2141,7 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resyncronize the vocal tracks if they have become offset from the instrumental.
|
* Resynchronize the vocal tracks if they have become offset from the instrumental.
|
||||||
*/
|
*/
|
||||||
function resyncVocals():Void
|
function resyncVocals():Void
|
||||||
{
|
{
|
||||||
|
@ -2111,8 +2150,10 @@ class PlayState extends MusicBeatSubState
|
||||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||||
if (!(FlxG.sound.music?.playing ?? false)) return;
|
if (!(FlxG.sound.music?.playing ?? false)) return;
|
||||||
|
|
||||||
var timeToPlayAt:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset));
|
var timeToPlayAt:Float = Math.min(FlxG.sound.music.length,
|
||||||
|
Math.max(Math.min(Conductor.instance.combinedOffset, 0), Conductor.instance.songPosition) - Conductor.instance.combinedOffset);
|
||||||
trace('Resyncing vocals to ${timeToPlayAt}');
|
trace('Resyncing vocals to ${timeToPlayAt}');
|
||||||
|
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
|
|
||||||
|
@ -2347,7 +2388,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
// Call an event to allow canceling the note miss.
|
// Call an event to allow canceling the note miss.
|
||||||
// NOTE: This is what handles the character animations!
|
// NOTE: This is what handles the character animations!
|
||||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Constants.HEALTH_MISS_PENALTY, 0, true);
|
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Constants.HEALTH_MISS_PENALTY, Highscore.tallies.combo, true);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
|
@ -2414,7 +2455,7 @@ class PlayState extends MusicBeatSubState
|
||||||
var healthChange = healthChangeUncapped.clamp(healthChangeMax, 0);
|
var healthChange = healthChangeUncapped.clamp(healthChangeMax, 0);
|
||||||
var scoreChange = Std.int(Constants.SCORE_HOLD_DROP_PENALTY_PER_SECOND * remainingLengthSec);
|
var scoreChange = Std.int(Constants.SCORE_HOLD_DROP_PENALTY_PER_SECOND * remainingLengthSec);
|
||||||
|
|
||||||
var event:HoldNoteScriptEvent = new HoldNoteScriptEvent(NOTE_HOLD_DROP, holdNote, healthChange, scoreChange, true);
|
var event:HoldNoteScriptEvent = new HoldNoteScriptEvent(NOTE_HOLD_DROP, holdNote, healthChange, scoreChange, true, Highscore.tallies.combo);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
trace('Penalizing score by ${event.score} and health by ${event.healthChange} for dropping hold note (is combo break: ${event.isComboBreak})!');
|
trace('Penalizing score by ${event.score} and health by ${event.healthChange} for dropping hold note (is combo break: ${event.isComboBreak})!');
|
||||||
|
@ -2448,7 +2489,7 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respawns notes that were b
|
// Respawns notes that were between the previous time and the current time when skipping backward, or destroy notes between the previous time and the current time when skipping forward.
|
||||||
playerStrumline.handleSkippedNotes();
|
playerStrumline.handleSkippedNotes();
|
||||||
opponentStrumline.handleSkippedNotes();
|
opponentStrumline.handleSkippedNotes();
|
||||||
}
|
}
|
||||||
|
@ -2709,6 +2750,8 @@ class PlayState extends MusicBeatSubState
|
||||||
FlxG.switchState(() -> new ChartEditorState(
|
FlxG.switchState(() -> new ChartEditorState(
|
||||||
{
|
{
|
||||||
targetSongId: currentSong.id,
|
targetSongId: currentSong.id,
|
||||||
|
targetSongDifficulty: currentDifficulty,
|
||||||
|
targetSongVariation: currentVariation,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2833,7 +2876,10 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
currentConversation.pauseMusic();
|
currentConversation.pauseMusic();
|
||||||
|
|
||||||
var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation});
|
var pauseSubState:FlxSubState = new PauseSubState(
|
||||||
|
{
|
||||||
|
mode: Cutscene('Conversation', 'Conversation', null, currentConversation?.skipConversation, currentConversation?.resetConversation)
|
||||||
|
});
|
||||||
|
|
||||||
persistentUpdate = false;
|
persistentUpdate = false;
|
||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
|
@ -2849,7 +2895,10 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
VideoCutscene.pauseVideo();
|
VideoCutscene.pauseVideo();
|
||||||
|
|
||||||
var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene});
|
var pauseSubState:FlxSubState = new PauseSubState(
|
||||||
|
{
|
||||||
|
mode: Cutscene('Cutscene', 'Video', VideoCutscene.resumeVideo, VideoCutscene.finishVideo.bind(null), VideoCutscene.restartVideo.bind(true))
|
||||||
|
});
|
||||||
|
|
||||||
persistentUpdate = false;
|
persistentUpdate = false;
|
||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
|
@ -3154,6 +3203,9 @@ class PlayState extends MusicBeatSubState
|
||||||
// TODO: Uncache the song.
|
// TODO: Uncache the song.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent vwoosh timer from running outside PlayState (e.g Chart Editor)
|
||||||
|
vwooshTimer.cancel();
|
||||||
|
|
||||||
if (overrideMusic)
|
if (overrideMusic)
|
||||||
{
|
{
|
||||||
// Stop the music. Do NOT destroy it, something still references it!
|
// Stop the music. Do NOT destroy it, something still references it!
|
||||||
|
@ -3175,6 +3227,11 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forEachPausedSound((s) -> s.destroy());
|
||||||
|
|
||||||
|
FlxTween.globalManager.clear();
|
||||||
|
FlxTimer.globalManager.clear();
|
||||||
|
|
||||||
// Remove reference to stage and remove sprites from it to save memory.
|
// Remove reference to stage and remove sprites from it to save memory.
|
||||||
if (currentStage != null)
|
if (currentStage != null)
|
||||||
{
|
{
|
||||||
|
@ -3429,7 +3486,7 @@ class PlayState extends MusicBeatSubState
|
||||||
cancelCameraZoomTween();
|
cancelCameraZoomTween();
|
||||||
}
|
}
|
||||||
|
|
||||||
var prevScrollTargets:Array<Dynamic> = []; // used to snap scroll speed when things go unruely
|
var prevScrollTargets:Array<Dynamic> = []; // used to snap scroll speed when things go unruly
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The magical function that shall tween the scroll speed.
|
* The magical function that shall tween the scroll speed.
|
||||||
|
@ -3483,6 +3540,15 @@ class PlayState extends MusicBeatSubState
|
||||||
scrollSpeedTweens = [];
|
scrollSpeedTweens = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forEachPausedSound(f:FlxSound->Void):Void
|
||||||
|
{
|
||||||
|
for (sound in soundsPausedBySubState)
|
||||||
|
{
|
||||||
|
f(sound);
|
||||||
|
}
|
||||||
|
soundsPausedBySubState.clear();
|
||||||
|
}
|
||||||
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
/**
|
/**
|
||||||
* Jumps forward or backward a number of sections in the song.
|
* Jumps forward or backward a number of sections in the song.
|
||||||
|
|
|
@ -32,6 +32,7 @@ import funkin.ui.freeplay.FreeplayState;
|
||||||
import funkin.ui.MusicBeatSubState;
|
import funkin.ui.MusicBeatSubState;
|
||||||
import funkin.ui.story.StoryMenuState;
|
import funkin.ui.story.StoryMenuState;
|
||||||
import funkin.modding.base.ScriptedFlxAtlasSprite;
|
import funkin.modding.base.ScriptedFlxAtlasSprite;
|
||||||
|
import funkin.graphics.ScriptedFunkinSprite;
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
import funkin.api.newgrounds.Medals;
|
import funkin.api.newgrounds.Medals;
|
||||||
#end
|
#end
|
||||||
|
@ -258,7 +259,15 @@ class ResultState extends MusicBeatSubState
|
||||||
// Add to the scene.
|
// Add to the scene.
|
||||||
add(animation);
|
add(animation);
|
||||||
case 'sparrow':
|
case 'sparrow':
|
||||||
var animation:FunkinSprite = FunkinSprite.createSparrow(offsets[0], offsets[1], animPath);
|
@:nullSafety(Off)
|
||||||
|
var animation:FunkinSprite = null;
|
||||||
|
|
||||||
|
if (animData.scriptClass != null) animation = ScriptedFunkinSprite.init(animData.scriptClass, offsets[0], offsets[1]);
|
||||||
|
else
|
||||||
|
animation = FunkinSprite.createSparrow(offsets[0], offsets[1], animPath);
|
||||||
|
|
||||||
|
if (animation == null) continue;
|
||||||
|
|
||||||
animation.animation.addByPrefix('idle', '', 24, false, false, false);
|
animation.animation.addByPrefix('idle', '', 24, false, false, false);
|
||||||
|
|
||||||
if (animData.loopFrame != null)
|
if (animData.loopFrame != null)
|
||||||
|
@ -487,8 +496,7 @@ class ResultState extends MusicBeatSubState
|
||||||
bgFlash.visible = true;
|
bgFlash.visible = true;
|
||||||
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
||||||
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
||||||
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick +
|
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good
|
||||||
params.scoreData.tallies.good
|
|
||||||
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
|
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
|
||||||
clearPercentTarget = Math.floor(clearPercentFloat);
|
clearPercentTarget = Math.floor(clearPercentFloat);
|
||||||
// Prevent off-by-one errors.
|
// Prevent off-by-one errors.
|
||||||
|
@ -563,16 +571,6 @@ class ResultState extends MusicBeatSubState
|
||||||
// scorePopin.animation.play("score");
|
// scorePopin.animation.play("score");
|
||||||
|
|
||||||
// scorePopin.visible = true;
|
// scorePopin.visible = true;
|
||||||
|
|
||||||
if (params.isNewHighscore ?? false)
|
|
||||||
{
|
|
||||||
highscoreNew.visible = true;
|
|
||||||
highscoreNew.animation.play("new");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
highscoreNew.visible = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import funkin.util.VersionUtil;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
import flixel.graphics.frames.FlxFrame;
|
import flixel.graphics.frames.FlxFrame;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class CharacterDataParser
|
class CharacterDataParser
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +58,7 @@ class CharacterDataParser
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var charData:CharacterData = parseCharacterData(charId);
|
var charData:Null<CharacterData> = parseCharacterData(charId);
|
||||||
if (charData != null)
|
if (charData != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded character data: ${charId}');
|
trace(' Loaded character data: ${charId}');
|
||||||
|
@ -203,14 +204,14 @@ class CharacterDataParser
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var charData:CharacterData = characterCache.get(charId);
|
var charData:Null<CharacterData> = characterCache.get(charId);
|
||||||
var charScriptClass:String = characterScriptedClass.get(charId);
|
var charScriptClass:Null<String> = characterScriptedClass.get(charId);
|
||||||
|
|
||||||
var char:BaseCharacter;
|
var char:Null<BaseCharacter> = null;
|
||||||
|
|
||||||
if (charScriptClass != null)
|
if (charScriptClass != null)
|
||||||
{
|
{
|
||||||
switch (charData.renderType)
|
if (charData != null) switch (charData.renderType)
|
||||||
{
|
{
|
||||||
case CharacterRenderType.AnimateAtlas:
|
case CharacterRenderType.AnimateAtlas:
|
||||||
char = ScriptedAnimateAtlasCharacter.init(charScriptClass, charId);
|
char = ScriptedAnimateAtlasCharacter.init(charScriptClass, charId);
|
||||||
|
@ -227,7 +228,7 @@ class CharacterDataParser
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (charData.renderType)
|
if (charData != null) switch (charData.renderType)
|
||||||
{
|
{
|
||||||
case CharacterRenderType.AnimateAtlas:
|
case CharacterRenderType.AnimateAtlas:
|
||||||
char = new AnimateAtlasCharacter(charId);
|
char = new AnimateAtlasCharacter(charId);
|
||||||
|
@ -283,7 +284,7 @@ class CharacterDataParser
|
||||||
/**
|
/**
|
||||||
* Returns the idle frame of a character.
|
* Returns the idle frame of a character.
|
||||||
*/
|
*/
|
||||||
public static function getCharPixelIconAsset(char:String):FlxFrame
|
public static function getCharPixelIconAsset(char:String):Null<FlxFrame>
|
||||||
{
|
{
|
||||||
var charPath:String = "freeplay/icons/";
|
var charPath:String = "freeplay/icons/";
|
||||||
|
|
||||||
|
@ -323,13 +324,13 @@ class CharacterDataParser
|
||||||
}
|
}
|
||||||
|
|
||||||
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
||||||
var frame:FlxFrame = null;
|
var frame:Null<FlxFrame> = null;
|
||||||
|
|
||||||
if (isAnimated)
|
if (isAnimated)
|
||||||
{
|
{
|
||||||
var frames = Paths.getSparrowAtlas(charPath);
|
var frames = Paths.getSparrowAtlas(charPath);
|
||||||
|
|
||||||
var idleFrame:FlxFrame = frames.frames.find(function(frame:FlxFrame):Bool {
|
var idleFrame:Null<FlxFrame> = frames.frames.find(function(frame:FlxFrame):Bool {
|
||||||
return frame.name.startsWith('idle');
|
return frame.name.startsWith('idle');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -380,7 +381,7 @@ class CharacterDataParser
|
||||||
{
|
{
|
||||||
var rawJson:String = loadCharacterFile(charId);
|
var rawJson:String = loadCharacterFile(charId);
|
||||||
|
|
||||||
var charData:CharacterData = migrateCharacterData(rawJson, charId);
|
var charData:Null<CharacterData> = migrateCharacterData(rawJson, charId);
|
||||||
|
|
||||||
return validateCharacterData(charId, charData);
|
return validateCharacterData(charId, charData);
|
||||||
}
|
}
|
||||||
|
@ -445,7 +446,7 @@ class CharacterDataParser
|
||||||
* @param input
|
* @param input
|
||||||
* @return The validated character data
|
* @return The validated character data
|
||||||
*/
|
*/
|
||||||
static function validateCharacterData(id:String, input:CharacterData):Null<CharacterData>
|
static function validateCharacterData(id:String, input:Null<CharacterData>):Null<CharacterData>
|
||||||
{
|
{
|
||||||
if (input == null)
|
if (input == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -207,10 +207,10 @@ class HealthIcon extends FunkinSprite
|
||||||
|
|
||||||
if (bopEvery != 0)
|
if (bopEvery != 0)
|
||||||
{
|
{
|
||||||
lerpIconSize();
|
lerpIconSize(false, elapsed);
|
||||||
|
|
||||||
// Lerp the health icon back to its normal angle.
|
// Lerp the health icon back to its normal angle.
|
||||||
this.angle = MathUtil.coolLerp(this.angle, 0, 0.15);
|
this.angle = MathUtil.smoothLerpPrecision(this.angle, 0, elapsed, 0.511);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
|
@ -221,14 +221,14 @@ class HealthIcon extends FunkinSprite
|
||||||
* Mainly forced when changing to old icon to not have a weird lerp related to changing from pixel icon to non-pixel old icon
|
* Mainly forced when changing to old icon to not have a weird lerp related to changing from pixel icon to non-pixel old icon
|
||||||
* @param force Force the icon immedialtely to be the target size. Defaults to false.
|
* @param force Force the icon immedialtely to be the target size. Defaults to false.
|
||||||
*/
|
*/
|
||||||
function lerpIconSize(force:Bool = false):Void
|
function lerpIconSize(force:Bool = false, ?elapsed:Float):Void
|
||||||
{
|
{
|
||||||
// Lerp the health icon back to its normal size,
|
// Lerp the health icon back to its normal size,
|
||||||
// while maintaining aspect ratio.
|
// while maintaining aspect ratio.
|
||||||
if (this.width > this.height)
|
if (this.width > this.height)
|
||||||
{
|
{
|
||||||
// Apply linear interpolation while accounting for frame rate.
|
// Apply linear interpolation while accounting for frame rate.
|
||||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
|
var targetSize:Int = Std.int(MathUtil.smoothLerpPrecision(this.width, HEALTH_ICON_SIZE * this.size.x, elapsed ?? FlxG.elapsed, 0.511));
|
||||||
|
|
||||||
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.x);
|
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.x);
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ class HealthIcon extends FunkinSprite
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
|
var targetSize:Int = Std.int(MathUtil.smoothLerpPrecision(this.height, HEALTH_ICON_SIZE * this.size.y, elapsed ?? FlxG.elapsed, 0.511));
|
||||||
|
|
||||||
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.y);
|
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.y);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import funkin.data.IRegistryEntry;
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
import flixel.graphics.frames.FlxFramesCollection;
|
import flixel.graphics.frames.FlxFramesCollection;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import flixel.addons.text.FlxTypeText;
|
|
||||||
import funkin.util.assets.FlxAnimationUtil;
|
import funkin.util.assets.FlxAnimationUtil;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
|
@ -66,7 +65,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||||
}
|
}
|
||||||
|
|
||||||
var boxSprite:FlxSprite;
|
var boxSprite:FlxSprite;
|
||||||
var textDisplay:FlxTypeText;
|
var textDisplay:FunkinTypeText;
|
||||||
|
|
||||||
var text(default, set):String;
|
var text(default, set):String;
|
||||||
|
|
||||||
|
@ -273,7 +272,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||||
|
|
||||||
function loadText():Void
|
function loadText():Void
|
||||||
{
|
{
|
||||||
textDisplay = new FlxTypeText(0, 0, 300, '', 32);
|
textDisplay = new FunkinTypeText(0, 0, 300, '', 32);
|
||||||
textDisplay.fieldWidth = _data.text.width;
|
textDisplay.fieldWidth = _data.text.width;
|
||||||
textDisplay.setFormat(_data.text.fontFamily, _data.text.size, FlxColor.fromString(_data.text.color), LEFT, SHADOW,
|
textDisplay.setFormat(_data.text.fontFamily, _data.text.size, FlxColor.fromString(_data.text.color), LEFT, SHADOW,
|
||||||
FlxColor.fromString(_data.text.shadowColor ?? '#00000000'), false);
|
FlxColor.fromString(_data.text.shadowColor ?? '#00000000'), false);
|
||||||
|
|
104
source/funkin/play/cutscene/dialogue/FunkinTypeText.hx
Normal file
104
source/funkin/play/cutscene/dialogue/FunkinTypeText.hx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package funkin.play.cutscene.dialogue;
|
||||||
|
|
||||||
|
import flixel.addons.text.FlxTypeText;
|
||||||
|
import flixel.input.keyboard.FlxKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An FlxTypeText that better accounts for text-wrapping,
|
||||||
|
* by overriding the functions of insertBreakLines() to check the finished state.
|
||||||
|
* Also fixes a bug where empty strings would make the typing never 'finish'.
|
||||||
|
*/
|
||||||
|
class FunkinTypeText extends FlxTypeText
|
||||||
|
{
|
||||||
|
public var preWrapping:Bool = true;
|
||||||
|
|
||||||
|
public function new(X:Float, Y:Float, Width:Int, Text:String, Size:Int = 8, EmbeddedFont:Bool = true, CheckWrapping:Bool = true)
|
||||||
|
{
|
||||||
|
super(X, Y, Width, "", Size, EmbeddedFont);
|
||||||
|
_finalText = Text;
|
||||||
|
preWrapping = CheckWrapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function start(?Delay:Float, ForceRestart:Bool = false, AutoErase:Bool = false, ?SkipKeys:Array<FlxKey>, ?Callback:Void->Void):Void
|
||||||
|
{
|
||||||
|
if (Delay != null)
|
||||||
|
{
|
||||||
|
delay = Delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
_typing = true;
|
||||||
|
_erasing = false;
|
||||||
|
paused = false;
|
||||||
|
_waiting = false;
|
||||||
|
|
||||||
|
if (ForceRestart)
|
||||||
|
{
|
||||||
|
text = "";
|
||||||
|
_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoErase = AutoErase;
|
||||||
|
|
||||||
|
if (SkipKeys != null)
|
||||||
|
{
|
||||||
|
skipKeys = SkipKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Callback != null)
|
||||||
|
{
|
||||||
|
completeCallback = Callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useDefaultSound)
|
||||||
|
{
|
||||||
|
loadDefaultSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autocomplete if the text is empty anyway. Why bother?
|
||||||
|
if (_finalText.length == 0)
|
||||||
|
{
|
||||||
|
onComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preWrapping)
|
||||||
|
{
|
||||||
|
insertBreakLines();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function insertBreakLines()
|
||||||
|
{
|
||||||
|
var saveText = text;
|
||||||
|
|
||||||
|
// See what it looks like when it's finished typing.
|
||||||
|
text = prefix + _finalText;
|
||||||
|
var prefixLength:Null<Int> = prefix.length;
|
||||||
|
var split:String = '';
|
||||||
|
|
||||||
|
// trace('Breaking apart text lines...');
|
||||||
|
|
||||||
|
for (i in 0...textField.numLines)
|
||||||
|
{
|
||||||
|
var curLine = textField.getLineText(i);
|
||||||
|
// trace('now at line $i, curLine: $curLine');
|
||||||
|
if (prefixLength >= curLine.length)
|
||||||
|
{
|
||||||
|
prefixLength -= curLine.length;
|
||||||
|
}
|
||||||
|
else if (prefixLength != null)
|
||||||
|
{
|
||||||
|
split += curLine.substr(prefixLength);
|
||||||
|
prefixLength = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
split += '\n' + curLine;
|
||||||
|
}
|
||||||
|
// trace('now at line $i, split: $split');
|
||||||
|
}
|
||||||
|
|
||||||
|
_finalText = split;
|
||||||
|
text = saveText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -218,9 +218,21 @@ class FocusCameraSongEvent extends SongEvent
|
||||||
'Smooth Step In' => 'smoothStepIn',
|
'Smooth Step In' => 'smoothStepIn',
|
||||||
'Smooth Step Out' => 'smoothStepOut',
|
'Smooth Step Out' => 'smoothStepOut',
|
||||||
'Smooth Step In/Out' => 'smoothStepInOut',
|
'Smooth Step In/Out' => 'smoothStepInOut',
|
||||||
|
'Smoother Step In' => 'smootherStepIn',
|
||||||
|
'Smoother Step Out' => 'smootherStepOut',
|
||||||
|
'Smoother Step In/Out' => 'smootherStepInOut',
|
||||||
'Elastic In' => 'elasticIn',
|
'Elastic In' => 'elasticIn',
|
||||||
'Elastic Out' => 'elasticOut',
|
'Elastic Out' => 'elasticOut',
|
||||||
'Elastic In/Out' => 'elasticInOut',
|
'Elastic In/Out' => 'elasticInOut',
|
||||||
|
'Back In' => 'backIn',
|
||||||
|
'Back Out' => 'backOut',
|
||||||
|
'Back In/Out' => 'backInOut',
|
||||||
|
'Bounce In' => 'bounceIn',
|
||||||
|
'Bounce Out' => 'bounceOut',
|
||||||
|
'Bounce In/Out' => 'bounceInOut',
|
||||||
|
'Circ In' => 'circIn',
|
||||||
|
'Circ Out' => 'circOut',
|
||||||
|
'Circ In/Out' => 'circInOut',
|
||||||
'Instant (Ignores duration)' => 'INSTANT',
|
'Instant (Ignores duration)' => 'INSTANT',
|
||||||
'Classic (Ignores duration)' => 'CLASSIC'
|
'Classic (Ignores duration)' => 'CLASSIC'
|
||||||
]
|
]
|
||||||
|
|
|
@ -149,9 +149,21 @@ class ScrollSpeedEvent extends SongEvent
|
||||||
'Smooth Step In' => 'smoothStepIn',
|
'Smooth Step In' => 'smoothStepIn',
|
||||||
'Smooth Step Out' => 'smoothStepOut',
|
'Smooth Step Out' => 'smoothStepOut',
|
||||||
'Smooth Step In/Out' => 'smoothStepInOut',
|
'Smooth Step In/Out' => 'smoothStepInOut',
|
||||||
|
'Smoother Step In' => 'smootherStepIn',
|
||||||
|
'Smoother Step Out' => 'smootherStepOut',
|
||||||
|
'Smoother Step In/Out' => 'smootherStepInOut',
|
||||||
'Elastic In' => 'elasticIn',
|
'Elastic In' => 'elasticIn',
|
||||||
'Elastic Out' => 'elasticOut',
|
'Elastic Out' => 'elasticOut',
|
||||||
'Elastic In/Out' => 'elasticInOut'
|
'Elastic In/Out' => 'elasticInOut',
|
||||||
|
'Back In' => 'backIn',
|
||||||
|
'Back Out' => 'backOut',
|
||||||
|
'Back In/Out' => 'backInOut',
|
||||||
|
'Bounce In' => 'bounceIn',
|
||||||
|
'Bounce Out' => 'bounceOut',
|
||||||
|
'Bounce In/Out' => 'bounceInOut',
|
||||||
|
'Circ In' => 'circIn',
|
||||||
|
'Circ Out' => 'circOut',
|
||||||
|
'Circ In/Out' => 'circInOut'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -158,9 +158,21 @@ class ZoomCameraSongEvent extends SongEvent
|
||||||
'Smooth Step In' => 'smoothStepIn',
|
'Smooth Step In' => 'smoothStepIn',
|
||||||
'Smooth Step Out' => 'smoothStepOut',
|
'Smooth Step Out' => 'smoothStepOut',
|
||||||
'Smooth Step In/Out' => 'smoothStepInOut',
|
'Smooth Step In/Out' => 'smoothStepInOut',
|
||||||
|
'Smoother Step In' => 'smootherStepIn',
|
||||||
|
'Smoother Step Out' => 'smootherStepOut',
|
||||||
|
'Smoother Step In/Out' => 'smootherStepInOut',
|
||||||
'Elastic In' => 'elasticIn',
|
'Elastic In' => 'elasticIn',
|
||||||
'Elastic Out' => 'elasticOut',
|
'Elastic Out' => 'elasticOut',
|
||||||
'Elastic In/Out' => 'elasticInOut'
|
'Elastic In/Out' => 'elasticInOut',
|
||||||
|
'Back In' => 'backIn',
|
||||||
|
'Back Out' => 'backOut',
|
||||||
|
'Back In/Out' => 'backInOut',
|
||||||
|
'Bounce In' => 'bounceIn',
|
||||||
|
'Bounce Out' => 'bounceOut',
|
||||||
|
'Bounce In/Out' => 'bounceInOut',
|
||||||
|
'Circ In' => 'circIn',
|
||||||
|
'Circ Out' => 'circOut',
|
||||||
|
'Circ In/Out' => 'circInOut'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -71,6 +71,8 @@ class NoteHoldCover extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
|
||||||
|
holdNote.cover = null;
|
||||||
|
|
||||||
if (glow != null) glow.visible = false;
|
if (glow != null) glow.visible = false;
|
||||||
if (sparks != null) sparks.visible = false;
|
if (sparks != null) sparks.visible = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -448,7 +448,6 @@ class Strumline extends FlxSpriteGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For a note's strumTime, calculate its Y position relative to the strumline.
|
* For a note's strumTime, calculate its Y position relative to the strumline.
|
||||||
* NOTE: Assumes Conductor and PlayState are both initialized.
|
* NOTE: Assumes Conductor and PlayState are both initialized.
|
||||||
|
@ -458,7 +457,7 @@ class Strumline extends FlxSpriteGroup
|
||||||
public function calculateNoteYPos(strumTime:Float):Float
|
public function calculateNoteYPos(strumTime:Float):Float
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1);
|
Constants.PIXELS_PER_MS * (conductorInUse.getTimeWithDelta() - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateNotes():Void
|
public function updateNotes():Void
|
||||||
|
|
|
@ -194,14 +194,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
includeScript:Bool = true, validScore:Bool = false):Song
|
includeScript:Bool = true, validScore:Bool = false):Song
|
||||||
{
|
{
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var result:Null<Song>;
|
var result:Null<Song> = null;
|
||||||
|
|
||||||
if (includeScript && SongRegistry.instance.isScriptedEntry(songId))
|
if (includeScript && SongRegistry.instance.isScriptedEntry(songId))
|
||||||
{
|
{
|
||||||
var songClassName:String = SongRegistry.instance.getScriptedEntryClassName(songId);
|
var songClassName:Null<String> = SongRegistry.instance.getScriptedEntryClassName(songId);
|
||||||
|
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
result = SongRegistry.instance.createScriptedEntry(songClassName);
|
if (songClassName != null) result = SongRegistry.instance.createScriptedEntry(songClassName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
package funkin.play.song;
|
|
||||||
|
|
||||||
import funkin.data.song.SongData.SongChartData;
|
|
||||||
import funkin.data.song.SongData.SongMetadata;
|
|
||||||
import funkin.util.FileUtil;
|
|
||||||
import openfl.net.FileReference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Refactor and remove this.
|
|
||||||
*/
|
|
||||||
class SongSerializer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Access a SongChartData JSON file from a specific path, then load it.
|
|
||||||
* @param path The file path to read from.
|
|
||||||
*/
|
|
||||||
public static function importSongChartDataSync(path:String):SongChartData
|
|
||||||
{
|
|
||||||
var fileData = FileUtil.readStringFromPath(path);
|
|
||||||
|
|
||||||
if (fileData == null) return null;
|
|
||||||
|
|
||||||
var songChartData:SongChartData = fileData.parseJSON();
|
|
||||||
|
|
||||||
return songChartData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access a SongMetadata JSON file from a specific path, then load it.
|
|
||||||
* @param path The file path to read from.
|
|
||||||
*/
|
|
||||||
public static function importSongMetadataSync(path:String):SongMetadata
|
|
||||||
{
|
|
||||||
var fileData = FileUtil.readStringFromPath(path);
|
|
||||||
|
|
||||||
if (fileData == null) return null;
|
|
||||||
|
|
||||||
var songMetadata:SongMetadata = fileData.parseJSON();
|
|
||||||
|
|
||||||
return songMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt the user to browse for a SongChartData JSON file path, then load it.
|
|
||||||
* @param callback The function to call when the file is loaded.
|
|
||||||
*/
|
|
||||||
public static function importSongChartDataAsync(callback:SongChartData->Void):Void
|
|
||||||
{
|
|
||||||
FileUtil.browseFileReference(function(fileReference:FileReference) {
|
|
||||||
var data = fileReference.data.toString();
|
|
||||||
|
|
||||||
if (data == null) return;
|
|
||||||
|
|
||||||
var songChartData:SongChartData = data.parseJSON();
|
|
||||||
|
|
||||||
if (songChartData != null) callback(songChartData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt the user to browse for a SongMetadata JSON file path, then load it.
|
|
||||||
* @param callback The function to call when the file is loaded.
|
|
||||||
*/
|
|
||||||
public static function importSongMetadataAsync(callback:SongMetadata->Void):Void
|
|
||||||
{
|
|
||||||
FileUtil.browseFileReference(function(fileReference:FileReference) {
|
|
||||||
var data = fileReference.data.toString();
|
|
||||||
|
|
||||||
if (data == null) return;
|
|
||||||
|
|
||||||
var songMetadata:SongMetadata = data.parseJSON();
|
|
||||||
|
|
||||||
if (songMetadata != null) callback(songMetadata);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -248,8 +248,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||||
propSprite.scrollFactor.y = dataProp.scroll[1];
|
propSprite.scrollFactor.y = dataProp.scroll[1];
|
||||||
|
|
||||||
propSprite.angle = dataProp.angle;
|
propSprite.angle = dataProp.angle;
|
||||||
propSprite.color = FlxColor.fromString(dataProp.color);
|
if (!isSolidColor) propSprite.color = FlxColor.fromString(dataProp.color);
|
||||||
@:privateAccess if (!isSolidColor) propSprite.blend = BlendMode.fromString(dataProp.blend);
|
@:privateAccess propSprite.blend = BlendMode.fromString(dataProp.blend);
|
||||||
|
|
||||||
propSprite.zIndex = dataProp.zIndex;
|
propSprite.zIndex = dataProp.zIndex;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package funkin.save;
|
||||||
|
|
||||||
import flixel.util.FlxSave;
|
import flixel.util.FlxSave;
|
||||||
import funkin.input.Controls.Device;
|
import funkin.input.Controls.Device;
|
||||||
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.scoring.Scoring;
|
import funkin.play.scoring.Scoring;
|
||||||
import funkin.play.scoring.Scoring.ScoringRank;
|
import funkin.play.scoring.Scoring.ScoringRank;
|
||||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||||
|
@ -127,8 +128,6 @@ class Save
|
||||||
shouldHideMouse: true,
|
shouldHideMouse: true,
|
||||||
fancyPreview: true,
|
fancyPreview: true,
|
||||||
previewOnSave: true,
|
previewOnSave: true,
|
||||||
saveFormat: 'PNG',
|
|
||||||
jpegQuality: 80,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
controls:
|
controls:
|
||||||
|
@ -173,6 +172,10 @@ class Save
|
||||||
metronomeVolume: 1.0,
|
metronomeVolume: 1.0,
|
||||||
hitsoundVolumePlayer: 1.0,
|
hitsoundVolumePlayer: 1.0,
|
||||||
hitsoundVolumeOpponent: 1.0,
|
hitsoundVolumeOpponent: 1.0,
|
||||||
|
instVolume: 1.0,
|
||||||
|
playerVoiceVolume: 1.0,
|
||||||
|
opponentVoiceVolume: 1.0,
|
||||||
|
playbackSpeed: 0.5,
|
||||||
themeMusic: true
|
themeMusic: true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -181,7 +184,10 @@ class Save
|
||||||
previousFiles: [],
|
previousFiles: [],
|
||||||
moveStep: "1px",
|
moveStep: "1px",
|
||||||
angleStep: 5,
|
angleStep: 5,
|
||||||
theme: StageEditorTheme.Light
|
theme: StageEditorTheme.Light,
|
||||||
|
bfChar: "bf",
|
||||||
|
gfChar: "gf",
|
||||||
|
dadChar: "dad"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -407,6 +413,57 @@ class Save
|
||||||
return data.optionsChartEditor.hitsoundVolumeOpponent;
|
return data.optionsChartEditor.hitsoundVolumeOpponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var chartEditorInstVolume(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorInstVolume():Float
|
||||||
|
{
|
||||||
|
if (data.optionsChartEditor.instVolume == null) data.optionsChartEditor.instVolume = 1.0;
|
||||||
|
|
||||||
|
return data.optionsChartEditor.instVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorInstVolume(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
data.optionsChartEditor.instVolume = value;
|
||||||
|
flush();
|
||||||
|
return data.optionsChartEditor.instVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorPlayerVoiceVolume(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorPlayerVoiceVolume():Float
|
||||||
|
{
|
||||||
|
if (data.optionsChartEditor.playerVoiceVolume == null) data.optionsChartEditor.playerVoiceVolume = 1.0;
|
||||||
|
|
||||||
|
return data.optionsChartEditor.playerVoiceVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorPlayerVoiceVolume(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
data.optionsChartEditor.playerVoiceVolume = value;
|
||||||
|
flush();
|
||||||
|
return data.optionsChartEditor.playerVoiceVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorOpponentVoiceVolume(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorOpponentVoiceVolume():Float
|
||||||
|
{
|
||||||
|
if (data.optionsChartEditor.opponentVoiceVolume == null) data.optionsChartEditor.opponentVoiceVolume = 1.0;
|
||||||
|
|
||||||
|
return data.optionsChartEditor.opponentVoiceVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorOpponentVoiceVolume(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
data.optionsChartEditor.opponentVoiceVolume = value;
|
||||||
|
flush();
|
||||||
|
return data.optionsChartEditor.opponentVoiceVolume;
|
||||||
|
}
|
||||||
|
|
||||||
public var chartEditorThemeMusic(get, set):Bool;
|
public var chartEditorThemeMusic(get, set):Bool;
|
||||||
|
|
||||||
function get_chartEditorThemeMusic():Bool
|
function get_chartEditorThemeMusic():Bool
|
||||||
|
@ -428,7 +485,7 @@ class Save
|
||||||
|
|
||||||
function get_chartEditorPlaybackSpeed():Float
|
function get_chartEditorPlaybackSpeed():Float
|
||||||
{
|
{
|
||||||
if (data.optionsChartEditor.playbackSpeed == null) data.optionsChartEditor.playbackSpeed = 1.0;
|
if (data.optionsChartEditor.playbackSpeed == null) data.optionsChartEditor.playbackSpeed = 0.5;
|
||||||
|
|
||||||
return data.optionsChartEditor.playbackSpeed;
|
return data.optionsChartEditor.playbackSpeed;
|
||||||
}
|
}
|
||||||
|
@ -550,6 +607,60 @@ class Save
|
||||||
return data.optionsStageEditor.theme;
|
return data.optionsStageEditor.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var stageBoyfriendChar(get, set):String;
|
||||||
|
|
||||||
|
function get_stageBoyfriendChar():String
|
||||||
|
{
|
||||||
|
if (data.optionsStageEditor.bfChar == null
|
||||||
|
|| CharacterDataParser.fetchCharacterData(data.optionsStageEditor.bfChar) == null) data.optionsStageEditor.bfChar = "bf";
|
||||||
|
|
||||||
|
return data.optionsStageEditor.bfChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_stageBoyfriendChar(value:String):String
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
data.optionsStageEditor.bfChar = value;
|
||||||
|
flush();
|
||||||
|
return data.optionsStageEditor.bfChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var stageGirlfriendChar(get, set):String;
|
||||||
|
|
||||||
|
function get_stageGirlfriendChar():String
|
||||||
|
{
|
||||||
|
if (data.optionsStageEditor.gfChar == null
|
||||||
|
|| CharacterDataParser.fetchCharacterData(data.optionsStageEditor.gfChar ?? "") == null) data.optionsStageEditor.gfChar = "gf";
|
||||||
|
|
||||||
|
return data.optionsStageEditor.gfChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_stageGirlfriendChar(value:String):String
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
data.optionsStageEditor.gfChar = value;
|
||||||
|
flush();
|
||||||
|
return data.optionsStageEditor.gfChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var stageDadChar(get, set):String;
|
||||||
|
|
||||||
|
function get_stageDadChar():String
|
||||||
|
{
|
||||||
|
if (data.optionsStageEditor.dadChar == null
|
||||||
|
|| CharacterDataParser.fetchCharacterData(data.optionsStageEditor.dadChar ?? "") == null) data.optionsStageEditor.dadChar = "dad";
|
||||||
|
|
||||||
|
return data.optionsStageEditor.dadChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_stageDadChar(value:String):String
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
data.optionsStageEditor.dadChar = value;
|
||||||
|
flush();
|
||||||
|
return data.optionsStageEditor.dadChar;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When we've seen a character unlock, add it to the list of characters seen.
|
* When we've seen a character unlock, add it to the list of characters seen.
|
||||||
* @param character
|
* @param character
|
||||||
|
@ -1441,16 +1552,12 @@ typedef SaveDataOptions =
|
||||||
* @param shouldHideMouse Should the mouse be hidden when taking a screenshot? Default: `true`
|
* @param shouldHideMouse Should the mouse be hidden when taking a screenshot? Default: `true`
|
||||||
* @param fancyPreview Show a fancy preview? Default: `true`
|
* @param fancyPreview Show a fancy preview? Default: `true`
|
||||||
* @param previewOnSave Only show the fancy preview after a screenshot is saved? Default: `true`
|
* @param previewOnSave Only show the fancy preview after a screenshot is saved? Default: `true`
|
||||||
* @param saveFormat The save format of the screenshot, PNG or JPEG. Default: `PNG`
|
|
||||||
* @param jpegQuality The JPEG Quality, if we're saving to the format. Default: `80`
|
|
||||||
*/
|
*/
|
||||||
var screenshot:
|
var screenshot:
|
||||||
{
|
{
|
||||||
var shouldHideMouse:Bool;
|
var shouldHideMouse:Bool;
|
||||||
var fancyPreview:Bool;
|
var fancyPreview:Bool;
|
||||||
var previewOnSave:Bool;
|
var previewOnSave:Bool;
|
||||||
var saveFormat:String;
|
|
||||||
var jpegQuality:Int;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var controls:
|
var controls:
|
||||||
|
@ -1653,10 +1760,16 @@ typedef SaveDataChartEditorOptions =
|
||||||
var ?instVolume:Float;
|
var ?instVolume:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Voices volume in the Chart Editor.
|
* Player voice volume in the Chart Editor.
|
||||||
* @default `1.0`
|
* @default `1.0`
|
||||||
*/
|
*/
|
||||||
var ?voicesVolume:Float;
|
var ?playerVoiceVolume:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opponent voice volume in the Chart Editor.
|
||||||
|
* @default `1.0`
|
||||||
|
*/
|
||||||
|
var ?opponentVoiceVolume:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playback speed in the Chart Editor.
|
* Playback speed in the Chart Editor.
|
||||||
|
@ -1699,4 +1812,22 @@ typedef SaveDataStageEditorOptions =
|
||||||
* @default `StageEditorTheme.Light`
|
* @default `StageEditorTheme.Light`
|
||||||
*/
|
*/
|
||||||
var ?theme:StageEditorTheme;
|
var ?theme:StageEditorTheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BF character ID used in testing stages.
|
||||||
|
* @default bf
|
||||||
|
*/
|
||||||
|
var ?bfChar:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GF character ID used in testing stages.
|
||||||
|
* @default gf
|
||||||
|
*/
|
||||||
|
var ?gfChar:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dad character ID used in testing stages.
|
||||||
|
* @default dad
|
||||||
|
*/
|
||||||
|
var ?dadChar:String;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,9 +8,10 @@ typedef AtlasAsset = flixel.util.typeLimit.OneOfTwo<String, FlxAtlasFrames>;
|
||||||
/**
|
/**
|
||||||
* A menulist whose items share a single texture atlas.
|
* A menulist whose items share a single texture atlas.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
|
class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
|
||||||
{
|
{
|
||||||
public var atlas:FlxAtlasFrames;
|
public var atlas:Null<FlxAtlasFrames>;
|
||||||
|
|
||||||
public function new(atlas, navControls:NavControls = Vertical, ?wrapMode)
|
public function new(atlas, navControls:NavControls = Vertical, ?wrapMode)
|
||||||
{
|
{
|
||||||
|
@ -38,9 +39,10 @@ class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
|
||||||
/**
|
/**
|
||||||
* A menu list item which uses single texture atlas.
|
* A menu list item which uses single texture atlas.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class AtlasMenuItem extends MenuListItem
|
class AtlasMenuItem extends MenuListItem
|
||||||
{
|
{
|
||||||
var atlas:FlxAtlasFrames;
|
var atlas:Null<FlxAtlasFrames>;
|
||||||
|
|
||||||
public var centered:Bool = false;
|
public var centered:Bool = false;
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ class AtlasMenuItem extends MenuListItem
|
||||||
|
|
||||||
override function setData(name:String, ?callback:Void->Void)
|
override function setData(name:String, ?callback:Void->Void)
|
||||||
{
|
{
|
||||||
frames = atlas;
|
if (atlas != null) frames = atlas;
|
||||||
animation.addByPrefix('idle', '$name idle', 24);
|
animation.addByPrefix('idle', '$name idle', 24);
|
||||||
animation.addByPrefix('selected', '$name selected', 24);
|
animation.addByPrefix('selected', '$name selected', 24);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import flixel.util.FlxStringUtil;
|
||||||
* AtlasText is an improved version of Alphabet and FlxBitmapText.
|
* AtlasText is an improved version of Alphabet and FlxBitmapText.
|
||||||
* It supports animations on the letters, and is less buggy than Alphabet.
|
* It supports animations on the letters, and is less buggy than Alphabet.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
||||||
{
|
{
|
||||||
static var fonts = new Map<AtlasFont, AtlasFontData>();
|
static var fonts = new Map<AtlasFont, AtlasFontData>();
|
||||||
|
@ -16,7 +17,7 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
||||||
|
|
||||||
public var text(default, set):String = "";
|
public var text(default, set):String = "";
|
||||||
|
|
||||||
var font:AtlasFontData;
|
var font:AtlasFontData = new AtlasFontData(AtlasFont.DEFAULT);
|
||||||
|
|
||||||
public var atlas(get, never):FlxAtlasFrames;
|
public var atlas(get, never):FlxAtlasFrames;
|
||||||
|
|
||||||
|
@ -33,10 +34,10 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
||||||
inline function get_maxHeight()
|
inline function get_maxHeight()
|
||||||
return font.maxHeight;
|
return font.maxHeight;
|
||||||
|
|
||||||
public function new(x = 0.0, y = 0.0, text:String, fontName:AtlasFont = AtlasFont.DEFAULT)
|
public function new(x = 0.0, y = 0.0, text:String = "", fontName:AtlasFont = AtlasFont.DEFAULT)
|
||||||
{
|
{
|
||||||
if (!fonts.exists(fontName)) fonts[fontName] = new AtlasFontData(fontName);
|
if (!fonts.exists(fontName)) fonts[fontName] = new AtlasFontData(fontName);
|
||||||
font = fonts[fontName];
|
font = fonts[fontName] ?? new AtlasFontData(fontName);
|
||||||
|
|
||||||
super(x, y);
|
super(x, y);
|
||||||
|
|
||||||
|
@ -251,6 +252,7 @@ class AtlasChar extends FlxSprite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
private class AtlasFontData
|
private class AtlasFontData
|
||||||
{
|
{
|
||||||
static public var upperChar = ~/^[A-Z]\d+$/;
|
static public var upperChar = ~/^[A-Z]\d+$/;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue