mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-19 21:33:06 +00:00
index on rewrite/bugfix/pause-and-results-fixes: 9b8fc872
song diff menu sort
This commit is contained in:
parent
9b8fc87261
commit
afcb677fac
2
.github/actions/setup-haxeshit/action.yml
vendored
2
.github/actions/setup-haxeshit/action.yml
vendored
|
@ -23,8 +23,6 @@ runs:
|
|||
with:
|
||||
path: .haxelib
|
||||
key: ${{ runner.os }}-hmm-${{ hashFiles('**/hmm.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-hmm-
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
name: hmm install
|
||||
run: |
|
||||
|
|
9
.github/workflows/build-shit.yml
vendored
9
.github/workflows/build-shit.yml
vendored
|
@ -53,9 +53,8 @@ jobs:
|
|||
token: ${{ secrets.GH_RO_PAT }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Make HXCPP cache dir
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}\\hxcpp_cache
|
||||
mkdir -p ${{ runner.temp }}\hxcpp_cache
|
||||
- name: Restore build cache
|
||||
id: cache-build-win
|
||||
uses: actions/cache@v3
|
||||
|
@ -63,10 +62,8 @@ jobs:
|
|||
path: |
|
||||
.haxelib
|
||||
export
|
||||
${{ runner.temp }}\\hxcpp_cache
|
||||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-win-
|
||||
${{ runner.temp }}\hxcpp_cache
|
||||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER
|
||||
|
|
17
Project.xml
17
Project.xml
|
@ -156,7 +156,6 @@
|
|||
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||
<haxedef name="HXCPP_STACK_LINE" />
|
||||
<haxedef name="HXCPP_STACK_TRACE" />
|
||||
<haxedef name="openfl-enable-handle-error" />
|
||||
<!-- This macro allows addition of new functionality to existing Flixel. -->
|
||||
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
||||
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
|
||||
|
@ -196,6 +195,22 @@
|
|||
<haxedef name="REDIRECT_ASSETS_FOLDER" />
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<!--
|
||||
This flag enables the popup/crashlog error handler.
|
||||
However, it also messes with breakpoints on some platforms.
|
||||
-->
|
||||
<haxedef name="openfl-enable-handle-error" />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<!-- TODO: Add a flag to Github Actions to turn this on or something. -->
|
||||
|
||||
<!-- Forces the version string to include the Git hash even on release builds (which are used for performance reasons). -->
|
||||
<haxedef name="FORCE_DEBUG_VERSION" />
|
||||
</section>
|
||||
|
||||
<!-- Run a script before and after building. -->
|
||||
<postbuild haxe="source/Prebuild.hx"/> -->
|
||||
<postbuild haxe="source/Postbuild.hx"/> -->
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 6e5ed46026a2eb1e575c5accf9192b90c13ff857
|
||||
Subproject commit 8104d43e584a1f25e574438d7b21a7e671358969
|
4
hmm.json
4
hmm.json
|
@ -104,7 +104,7 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f195121ebec688b417e38ab115185c8d93c349d3",
|
||||
"ref": "737b86f121cdc90358d59e2e527934f267c94a2c",
|
||||
"url": "https://github.com/EliteMasterEric/lime"
|
||||
},
|
||||
{
|
||||
|
@ -139,7 +139,7 @@
|
|||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "de9395d2f367a80f93f082e1b639b9cde2258bf1",
|
||||
"ref": "f229d76361c7e31025a048fe7909847f75bb5d5e",
|
||||
"url": "https://github.com/EliteMasterEric/openfl"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ import flixel.FlxGame;
|
|||
import flixel.FlxState;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.MemoryCounter;
|
||||
import funkin.save.Save;
|
||||
import haxe.ui.Toolkit;
|
||||
import openfl.display.FPS;
|
||||
import openfl.display.Sprite;
|
||||
|
@ -84,20 +85,21 @@ class Main extends Sprite
|
|||
|
||||
initHaxeUI();
|
||||
|
||||
fpsCounter = new FPS(10, 3, 0xFFFFFF);
|
||||
// addChild(fpsCounter); // Handled by Preferences.init
|
||||
#if !html5
|
||||
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
|
||||
// addChild(memoryCounter);
|
||||
#end
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
|
||||
addChild(new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen));
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#end
|
||||
|
||||
#if debug
|
||||
fpsCounter = new FPS(10, 3, 0xFFFFFF);
|
||||
addChild(fpsCounter);
|
||||
#if !html5
|
||||
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
|
||||
addChild(memoryCounter);
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
function initHaxeUI():Void
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
|
||||
package funkin;
|
||||
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.FlxObject;
|
||||
import flixel.input.FlxInput;
|
||||
|
@ -832,6 +834,14 @@ class Controls extends FlxActionSet
|
|||
fromSaveData(padData, Gamepad(id));
|
||||
}
|
||||
|
||||
public function getGamepadIds():Array<Int> {
|
||||
return gamepadsAdded;
|
||||
}
|
||||
|
||||
public function getGamepads():Array<FlxGamepad> {
|
||||
return [for (id in gamepadsAdded) FlxG.gamepads.getByID(id)];
|
||||
}
|
||||
|
||||
inline function addGamepadLiteral(id:Int, ?buttonMap:Map<Control, Array<FlxGamepadInputID>>):Void
|
||||
{
|
||||
gamepadsAdded.push(id);
|
||||
|
|
|
@ -1,54 +1,55 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.shaderslmfao.HSVShader;
|
||||
import funkin.ui.StickerSubState;
|
||||
import flash.text.TextField;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.ui.FlxInputText;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.ui.FlxInputText;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.system.debug.watch.Tracker.TrackerProfile;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.debug.watch.Tracker.TrackerProfile;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.Controls.Control;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.freeplayStuff.BGScrollingText;
|
||||
import funkin.freeplayStuff.DifficultyStars;
|
||||
import funkin.freeplayStuff.DJBoyfriend;
|
||||
import funkin.freeplayStuff.FreeplayScore;
|
||||
import funkin.freeplayStuff.LetterSort;
|
||||
import funkin.freeplayStuff.SongMenuItem;
|
||||
import funkin.freeplayStuff.DifficultyStars;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
import funkin.shaderslmfao.StrokeShader;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.save.Save;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.HSVShader;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
import funkin.shaderslmfao.StrokeShader;
|
||||
import funkin.ui.StickerSubState;
|
||||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
|
||||
class FreeplayState extends MusicBeatSubState
|
||||
{
|
||||
var songs:Array<FreeplaySongData> = [];
|
||||
|
||||
// var selector:FlxText;
|
||||
var curSelected:Int = 0;
|
||||
var curDifficulty:Int = 1;
|
||||
|
||||
|
@ -107,8 +108,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
openSubState(stickerSubState);
|
||||
stickerSubState.degenStickers();
|
||||
|
||||
// resetSubState();
|
||||
}
|
||||
|
||||
#if discord_rpc
|
||||
|
@ -120,31 +119,25 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
#if debug
|
||||
isDebug = true;
|
||||
// addSong('Test', 'tutorial', 'bf-pixel');
|
||||
// addSong('Pyro', 'weekend1', 'darnell');
|
||||
#end
|
||||
|
||||
// var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
|
||||
|
||||
// for (i in 0...initSonglist.length)
|
||||
// {
|
||||
// songs.push(new FreeplaySongData(initSonglist[i], 'tutorial', 'gf'));
|
||||
// }
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
}
|
||||
|
||||
// Add a null entry that represents the RANDOM option
|
||||
songs.push(null);
|
||||
|
||||
// programmatically adds the songs via LevelRegistry and SongRegistry
|
||||
for (coolWeek in LevelRegistry.instance.listBaseGameLevelIds())
|
||||
{
|
||||
for (coolSong in LevelRegistry.instance.parseEntryData(coolWeek).songs)
|
||||
for (songId in LevelRegistry.instance.parseEntryData(coolWeek).songs)
|
||||
{
|
||||
var metadata = SongRegistry.instance.parseEntryMetadata(coolSong);
|
||||
var metadata = SongRegistry.instance.parseEntryMetadata(songId);
|
||||
var char = metadata.playData.characters.opponent;
|
||||
var songName = metadata.songName;
|
||||
addSong(songName, coolWeek, char);
|
||||
addSong(songId, songName, coolWeek, char);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,7 +247,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
speed: 0.3
|
||||
});
|
||||
|
||||
// dj = new DJBoyfriend(0, -100);
|
||||
dj = new DJBoyfriend(640, 366);
|
||||
exitMovers.set([dj],
|
||||
{
|
||||
|
@ -427,7 +419,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
dj.onIntroDone.add(function() {
|
||||
// when boyfriend hits dat shiii
|
||||
//
|
||||
|
||||
albumArt.visible = true;
|
||||
albumArt.anim.play("");
|
||||
|
@ -485,40 +476,15 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
generateSongList(null, false);
|
||||
|
||||
// FlxG.sound.playMusic(Paths.music('title'), 0);
|
||||
// FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
// selector = new FlxText();
|
||||
|
||||
// selector.size = 40;
|
||||
// selector.text = ">";
|
||||
// add(selector);
|
||||
|
||||
var swag:Alphabet = new Alphabet(1, 0, "swag");
|
||||
|
||||
// JUST DOIN THIS SHIT FOR TESTING!!!
|
||||
/*
|
||||
var md:String = Markdown.markdownToHtml(Assets.getText('CHANGELOG.md'));
|
||||
|
||||
var texFel:TextField = new TextField();
|
||||
texFel.width = FlxG.width;
|
||||
texFel.height = FlxG.height;
|
||||
// texFel.
|
||||
texFel.htmlText = md;
|
||||
|
||||
FlxG.stage.addChild(texFel);
|
||||
|
||||
trace(md);
|
||||
*/
|
||||
|
||||
var funnyCam = new FlxCamera(0, 0, FlxG.width, FlxG.height);
|
||||
funnyCam.bgColor = FlxColor.TRANSPARENT;
|
||||
FlxG.cameras.add(funnyCam);
|
||||
|
||||
typing = new FlxInputText(100, 100);
|
||||
// add(typing);
|
||||
|
||||
typing.callback = function(txt, action) {
|
||||
// generateSongList(new EReg(txt.trim(), "ig"));
|
||||
trace(action);
|
||||
};
|
||||
|
||||
|
@ -534,9 +500,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
for (cap in grpCapsules.members)
|
||||
cap.kill();
|
||||
|
||||
// grpCapsules.clear();
|
||||
|
||||
// var regexp:EReg = regexp;
|
||||
var tempSongs:Array<FreeplaySongData> = songs;
|
||||
|
||||
if (filterStuff != null)
|
||||
|
@ -570,7 +533,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||
randomCapsule.init(FlxG.width, 0, "Random");
|
||||
randomCapsule.onConfirm = function() {
|
||||
trace("RANDOM SELECTED");
|
||||
capsuleOnConfirmRandom(randomCapsule);
|
||||
};
|
||||
randomCapsule.y = randomCapsule.intendedY(0) + 10;
|
||||
randomCapsule.targetPos.x = randomCapsule.x;
|
||||
|
@ -583,7 +546,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
for (i in 0...tempSongs.length)
|
||||
{
|
||||
if (tempSongs[i] == null) continue;
|
||||
|
||||
var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||
|
||||
funnyMenu.init(FlxG.width, 0, tempSongs[i].songName);
|
||||
if (tempSongs[i].songCharacter != null) funnyMenu.setCharacter(tempSongs[i].songCharacter);
|
||||
funnyMenu.onConfirm = function() {
|
||||
|
@ -596,7 +562,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
funnyMenu.songText.visible = false;
|
||||
funnyMenu.favIcon.visible = tempSongs[i].isFav;
|
||||
funnyMenu.hsvShader = hsvShader;
|
||||
// fp.updateScore(0);
|
||||
|
||||
if (i < 8) funnyMenu.initJumpIn(Math.min(i, 4), force);
|
||||
else
|
||||
|
@ -611,22 +576,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
changeDiff();
|
||||
}
|
||||
|
||||
public function addSong(songName:String, levelId:String, songCharacter:String)
|
||||
public function addSong(songId:String, songName:String, levelId:String, songCharacter:String)
|
||||
{
|
||||
songs.push(new FreeplaySongData(songName, levelId, songCharacter));
|
||||
}
|
||||
|
||||
public function addWeek(songs:Array<String>, levelId:String, ?songCharacters:Array<String>)
|
||||
{
|
||||
if (songCharacters == null) songCharacters = ['bf'];
|
||||
|
||||
var num:Int = 0;
|
||||
for (song in songs)
|
||||
{
|
||||
addSong(song, levelId, songCharacters[num]);
|
||||
|
||||
if (songCharacters.length != 1) num++;
|
||||
}
|
||||
songs.push(new FreeplaySongData(songId, songName, levelId, songCharacter));
|
||||
}
|
||||
|
||||
var touchY:Float = 0;
|
||||
|
@ -643,34 +595,39 @@ class FreeplayState extends MusicBeatSubState
|
|||
var spamTimer:Float = 0;
|
||||
var spamming:Bool = false;
|
||||
|
||||
var busy:Bool = false; // Set to true once the user has pressed enter to select a song.
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.keys.justPressed.F)
|
||||
{
|
||||
var realShit = curSelected;
|
||||
songs[curSelected].isFav = !songs[curSelected].isFav;
|
||||
if (songs[curSelected].isFav)
|
||||
if (songs[curSelected] != null)
|
||||
{
|
||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
|
||||
{
|
||||
ease: FlxEase.elasticOut,
|
||||
onComplete: _ -> {
|
||||
grpCapsules.members[realShit].favIcon.visible = true;
|
||||
grpCapsules.members[realShit].favIcon.animation.play("fav");
|
||||
}
|
||||
var realShit = curSelected;
|
||||
songs[curSelected].isFav = !songs[curSelected].isFav;
|
||||
if (songs[curSelected].isFav)
|
||||
{
|
||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
|
||||
{
|
||||
ease: FlxEase.elasticOut,
|
||||
onComplete: _ -> {
|
||||
grpCapsules.members[realShit].favIcon.visible = true;
|
||||
grpCapsules.members[realShit].favIcon.animation.play("fav");
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
grpCapsules.members[realShit].favIcon.animation.play('fav', false, true);
|
||||
new FlxTimer().start((1 / 24) * 14, _ -> {
|
||||
grpCapsules.members[realShit].favIcon.visible = false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
grpCapsules.members[realShit].favIcon.animation.play('fav', false, true);
|
||||
new FlxTimer().start((1 / 24) * 14, _ -> {
|
||||
grpCapsules.members[realShit].favIcon.visible = false;
|
||||
});
|
||||
new FlxTimer().start((1 / 24) * 24, _ -> {
|
||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 0}, 0.4, {ease: FlxEase.elasticOut});
|
||||
});
|
||||
new FlxTimer().start((1 / 24) * 24, _ -> {
|
||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 0}, 0.4, {ease: FlxEase.elasticOut});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,11 +647,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
fp.updateScore(Std.int(lerpScore));
|
||||
|
||||
txtCompletion.text = Math.floor(lerpCompletion * 100) + "%";
|
||||
// trace(Highscore.getCompletion(songs[curSelected].songName, curDifficulty));
|
||||
|
||||
// trace(intendedScore);
|
||||
// trace(lerpScore);
|
||||
// Highscore.getAllScores();
|
||||
handleInputs(elapsed);
|
||||
}
|
||||
|
||||
function handleInputs(elapsed:Float):Void
|
||||
{
|
||||
if (busy) return;
|
||||
|
||||
var upP = controls.UI_UP_P;
|
||||
var downP = controls.UI_DOWN_P;
|
||||
|
@ -718,16 +677,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
FlxG.watch.addQuick("LENGTH", length);
|
||||
FlxG.watch.addQuick("ANGLE", Math.round(FlxAngle.asDegrees(angle)));
|
||||
// trace("ANGLE", Math.round(FlxAngle.asDegrees(angle)));
|
||||
}
|
||||
|
||||
/* switch (inputID)
|
||||
{
|
||||
case FlxObject.UP:
|
||||
return
|
||||
case FlxObject.DOWN:
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (FlxG.touches.getFirst() != null)
|
||||
|
@ -763,7 +713,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
touchY = touch.screenY;
|
||||
|
||||
if (dyTouch != 0) dyTouch < 0 ? changeSelection(1) : changeSelection(-1);
|
||||
// changeSelection(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -841,8 +790,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||
|
||||
// FlxTween.tween(dj, {x: -dj.width}, 0.2, {ease: FlxEase.quartOut});
|
||||
|
||||
var longestTimer:Float = 0;
|
||||
|
||||
for (grpSpr in exitMovers.keys())
|
||||
|
@ -888,15 +835,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
//
|
||||
// close();
|
||||
});
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
// if (Assets.exists())
|
||||
|
||||
grpCapsules.members[curSelected].onConfirm();
|
||||
}
|
||||
}
|
||||
|
@ -904,7 +847,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
@:haxe.warning("-WDeprecated")
|
||||
override function switchTo(nextState:FlxState):Bool
|
||||
{
|
||||
clearDaCache(songs[curSelected].songName);
|
||||
var daSong = songs[curSelected];
|
||||
if (daSong != null)
|
||||
{
|
||||
clearDaCache(daSong.songName);
|
||||
}
|
||||
return super.switchTo(nextState);
|
||||
}
|
||||
|
||||
|
@ -917,9 +864,29 @@ class FreeplayState extends MusicBeatSubState
|
|||
if (curDifficulty < 0) curDifficulty = 2;
|
||||
if (curDifficulty > 2) curDifficulty = 0;
|
||||
|
||||
// intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
|
||||
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
|
||||
intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty);
|
||||
var targetDifficulty:String = switch (curDifficulty)
|
||||
{
|
||||
case 0:
|
||||
'easy';
|
||||
case 1:
|
||||
'normal';
|
||||
case 2:
|
||||
'hard';
|
||||
default: 'normal';
|
||||
};
|
||||
|
||||
var daSong = songs[curSelected];
|
||||
if (daSong != null)
|
||||
{
|
||||
var songScore:SaveScoreData = Save.get().getSongScore(songs[curSelected].songId, targetDifficulty);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore?.accuracy ?? 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
}
|
||||
|
||||
grpDifficulties.group.forEach(function(spr) {
|
||||
spr.visible = false;
|
||||
|
@ -941,6 +908,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
for (song in songs)
|
||||
{
|
||||
if (song == null) continue;
|
||||
if (song.songName != actualSongTho)
|
||||
{
|
||||
trace('trying to remove: ' + song.songName);
|
||||
|
@ -949,19 +917,16 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
function capsuleOnConfirmRandom(cap:SongMenuItem):Void
|
||||
{
|
||||
trace("RANDOM SELECTED");
|
||||
|
||||
busy = true;
|
||||
}
|
||||
|
||||
function capsuleOnConfirmDefault(cap:SongMenuItem):Void
|
||||
{
|
||||
// var poop:String = songs[curSelected].songName.toLowerCase();
|
||||
|
||||
// does not work properly, always just accidentally sets it to normal anyways!
|
||||
/* if (!Assets.exists(Paths.json(songs[curSelected].songName + '/' + poop)))
|
||||
{
|
||||
// defaults to normal if HARD / EASY doesn't exist
|
||||
// does not account if NORMAL doesn't exist!
|
||||
FlxG.log.warn("CURRENT DIFFICULTY IS NOT CHARTED, DEFAULTING TO NORMAL!");
|
||||
poop = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), 1);
|
||||
curDifficulty = 1;
|
||||
}*/
|
||||
busy = true;
|
||||
|
||||
PlayStatePlaylist.isStoryMode = false;
|
||||
|
||||
|
@ -1002,6 +967,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
targetSong.cacheCharts(true);
|
||||
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
Paths.setCurrentLevel(songs[curSelected].levelId);
|
||||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
|
@ -1013,8 +979,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
function changeSelection(change:Int = 0)
|
||||
{
|
||||
// fp.updateScore(12345);
|
||||
|
||||
// NGio.logEvent('Fresh');
|
||||
FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
|
||||
// FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName));
|
||||
|
@ -1024,27 +988,30 @@ class FreeplayState extends MusicBeatSubState
|
|||
if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1;
|
||||
if (curSelected >= grpCapsules.countLiving()) curSelected = 0;
|
||||
|
||||
// selector.y = (70 * curSelected) + 30;
|
||||
|
||||
// intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
|
||||
|
||||
if (songs[curSelected] != null)
|
||||
var targetDifficulty:String = switch (curDifficulty)
|
||||
{
|
||||
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
|
||||
intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty);
|
||||
case 0:
|
||||
'easy';
|
||||
case 1:
|
||||
'normal';
|
||||
case 2:
|
||||
'hard';
|
||||
default: 'normal';
|
||||
};
|
||||
|
||||
var daSong = songs[curSelected];
|
||||
if (daSong != null)
|
||||
{
|
||||
var songScore:SaveScoreData = Save.get().getSongScore(daSong.songId, targetDifficulty);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore?.accuracy ?? 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0;
|
||||
intendedCompletion = 0.0;
|
||||
}
|
||||
|
||||
// lerpScore = 0;
|
||||
|
||||
#if PRELOAD_ALL
|
||||
// FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName), 0);
|
||||
#end
|
||||
|
||||
for (index => capsule in grpCapsules.members)
|
||||
{
|
||||
index += 1;
|
||||
|
@ -1053,7 +1020,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
capsule.targetPos.y = capsule.intendedY(index - curSelected);
|
||||
capsule.targetPos.x = 270 + (60 * (Math.sin(index - curSelected)));
|
||||
// capsule.targetPos.x = 320 + (40 * (index - curSelected));
|
||||
|
||||
if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure
|
||||
}
|
||||
|
@ -1132,14 +1098,16 @@ enum abstract FilterType(String)
|
|||
|
||||
class FreeplaySongData
|
||||
{
|
||||
public var songId:String = "";
|
||||
public var songName:String = "";
|
||||
public var levelId:String = "";
|
||||
public var songCharacter:String = "";
|
||||
public var isFav:Bool = false;
|
||||
|
||||
public function new(song:String, levelId:String, songCharacter:String, isFav:Bool = false)
|
||||
public function new(songId:String, songName:String, levelId:String, songCharacter:String, isFav:Bool = false)
|
||||
{
|
||||
this.songName = song;
|
||||
this.songId = songId;
|
||||
this.songName = songName;
|
||||
this.levelId = levelId;
|
||||
this.songCharacter = songCharacter;
|
||||
this.isFav = isFav;
|
||||
|
|
|
@ -2,183 +2,9 @@ package funkin;
|
|||
|
||||
class Highscore
|
||||
{
|
||||
#if (haxe >= "4.0.0")
|
||||
public static var songScores:Map<String, Int> = new Map();
|
||||
#else
|
||||
public static var songScores:Map<String, Int> = new Map<String, Int>();
|
||||
#end
|
||||
|
||||
#if (haxe >= "4.0.0")
|
||||
public static var songCompletion:Map<String, Float> = new Map();
|
||||
#else
|
||||
public static var songCompletion:Map<String, Float> = new Map<String, Float>();
|
||||
#end
|
||||
|
||||
public static var tallies:Tallies = new Tallies();
|
||||
|
||||
public static function saveScore(song:String, score:Int = 0, ?diff:Int = 0):Bool
|
||||
{
|
||||
var formattedSong:String = formatSong(song, diff);
|
||||
|
||||
#if newgrounds
|
||||
NGio.postScore(score, song);
|
||||
#end
|
||||
|
||||
if (songScores.exists(formattedSong))
|
||||
{
|
||||
if (songScores.get(formattedSong) < score)
|
||||
{
|
||||
setScore(formattedSong, score);
|
||||
return true;
|
||||
// new highscore
|
||||
}
|
||||
}
|
||||
else
|
||||
setScore(formattedSong, score);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function saveScoreForDifficulty(song:String, score:Int = 0, diff:String = 'normal'):Bool
|
||||
{
|
||||
var diffInt:Int = 1;
|
||||
|
||||
if (diff == 'easy') diffInt = 0;
|
||||
else if (diff == 'hard') diffInt = 2;
|
||||
|
||||
return saveScore(song, score, diffInt);
|
||||
}
|
||||
|
||||
public static function saveCompletion(song:String, completion:Float, diff:Int = 0):Bool
|
||||
{
|
||||
var formattedSong:String = formatSong(song, diff);
|
||||
|
||||
if (songCompletion.exists(formattedSong))
|
||||
{
|
||||
if (songCompletion.get(formattedSong) < completion)
|
||||
{
|
||||
setCompletion(formattedSong, completion);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
setCompletion(formattedSong, completion);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function saveCompletionForDifficulty(song:String, completion:Float, diff:String = 'normal'):Bool
|
||||
{
|
||||
var diffInt:Int = 1;
|
||||
|
||||
if (diff == 'easy') diffInt = 0;
|
||||
else if (diff == 'hard') diffInt = 2;
|
||||
|
||||
return saveCompletion(song, completion, diffInt);
|
||||
}
|
||||
|
||||
public static function saveWeekScore(week:String, score:Int = 0, diff:Int = 0):Void
|
||||
{
|
||||
#if newgrounds
|
||||
NGio.postScore(score, 'Campaign ID $week');
|
||||
#end
|
||||
|
||||
var formattedSong:String = formatSong(week, diff);
|
||||
|
||||
if (songScores.exists(formattedSong))
|
||||
{
|
||||
if (songScores.get(formattedSong) < score) setScore(formattedSong, score);
|
||||
}
|
||||
else
|
||||
{
|
||||
setScore(formattedSong, score);
|
||||
}
|
||||
}
|
||||
|
||||
public static function saveWeekScoreForDifficulty(week:String, score:Int = 0, diff:String = 'normal'):Void
|
||||
{
|
||||
var diffInt:Int = 1;
|
||||
|
||||
if (diff == 'easy') diffInt = 0;
|
||||
else if (diff == 'hard') diffInt = 2;
|
||||
|
||||
saveWeekScore(week, score, diffInt);
|
||||
}
|
||||
|
||||
static function setCompletion(formattedSong:String, completion:Float):Void
|
||||
{
|
||||
songCompletion.set(formattedSong, completion);
|
||||
FlxG.save.data.songCompletion = songCompletion;
|
||||
FlxG.save.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* YOU SHOULD FORMAT SONG WITH formatSong() BEFORE TOSSING IN SONG VARIABLE
|
||||
*/
|
||||
static function setScore(formattedSong:String, score:Int):Void
|
||||
{
|
||||
/** GeoKureli
|
||||
* References to Highscore were wrapped in `#if !switch` blocks. I wasn't sure if this
|
||||
* is because switch doesn't use NGio, or because switch has a different saving method.
|
||||
* I moved the compiler flag here, rather than using it everywhere else.
|
||||
*/
|
||||
#if ! switch
|
||||
// Reminder that I don't need to format this song, it should come formatted!
|
||||
songScores.set(formattedSong, score);
|
||||
FlxG.save.data.songScores = songScores;
|
||||
FlxG.save.flush();
|
||||
#end
|
||||
}
|
||||
|
||||
public static function formatSong(song:String, diff:Int):String
|
||||
{
|
||||
var daSong:String = song;
|
||||
|
||||
if (diff == 0) daSong += '-easy';
|
||||
else if (diff == 2) daSong += '-hard';
|
||||
|
||||
return daSong;
|
||||
}
|
||||
|
||||
public static function getScore(song:String, diff:Int):Int
|
||||
{
|
||||
if (!songScores.exists(formatSong(song, diff))) setScore(formatSong(song, diff), 0);
|
||||
|
||||
return songScores.get(formatSong(song, diff));
|
||||
}
|
||||
|
||||
public static function getCompletion(song, diff):Float
|
||||
{
|
||||
if (!songCompletion.exists(formatSong(song, diff))) setCompletion(formatSong(song, diff), 0);
|
||||
|
||||
return songCompletion.get(formatSong(song, diff));
|
||||
}
|
||||
|
||||
public static function getAllScores():Void
|
||||
{
|
||||
trace(songScores.toString());
|
||||
}
|
||||
|
||||
public static function getWeekScore(week:Int, diff:Int):Int
|
||||
{
|
||||
if (!songScores.exists(formatSong('week' + week, diff))) setScore(formatSong('week' + week, diff), 0);
|
||||
|
||||
return songScores.get(formatSong('week' + week, diff));
|
||||
}
|
||||
|
||||
public static function load():Void
|
||||
{
|
||||
if (FlxG.save.data.songScores != null)
|
||||
{
|
||||
songScores = FlxG.save.data.songScores;
|
||||
}
|
||||
|
||||
if (FlxG.save.data.songCompletion != null) songCompletion = FlxG.save.data.songCompletion;
|
||||
}
|
||||
}
|
||||
|
||||
// i only do forward metadata cuz george did!
|
||||
|
||||
@:forward
|
||||
abstract Tallies(RawTallies)
|
||||
{
|
||||
|
|
|
@ -46,7 +46,11 @@ class InitState extends FlxState
|
|||
{
|
||||
setupShit();
|
||||
|
||||
loadSaveData();
|
||||
// loadSaveData(); // Moved to Main.hx
|
||||
// Load player options from save data.
|
||||
Preferences.init();
|
||||
// Load controls from save data.
|
||||
PlayerSettings.init();
|
||||
|
||||
startGame();
|
||||
}
|
||||
|
@ -73,10 +77,6 @@ class InitState extends FlxState
|
|||
FlxG.sound.volumeDownKeys = [];
|
||||
FlxG.sound.muteKeys = [];
|
||||
|
||||
// TODO: Make sure volume still saves/loads properly.
|
||||
// if (FlxG.save.data.volume != null) FlxG.sound.volume = FlxG.save.data.volume;
|
||||
// if (FlxG.save.data.mute != null) FlxG.sound.muted = FlxG.save.data.mute;
|
||||
|
||||
// Set the game to a lower frame rate while it is in the background.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
|
@ -212,24 +212,6 @@ class InitState extends FlxState
|
|||
ModuleHandler.callOnCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive and parse data from the user's save.
|
||||
*/
|
||||
function loadSaveData()
|
||||
{
|
||||
// Bind save data.
|
||||
// TODO: Migrate save data to a better format.
|
||||
FlxG.save.bind('funkin', 'ninjamuffin99');
|
||||
|
||||
// Load player options from save data.
|
||||
PreferencesMenu.initPrefs();
|
||||
// Load controls from save data.
|
||||
PlayerSettings.init();
|
||||
// Load highscores from save data.
|
||||
Highscore.load();
|
||||
// TODO: Load level/character/cosmetic unlocks from save data.
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the game.
|
||||
*
|
||||
|
|
|
@ -13,23 +13,13 @@ import flixel.input.touch.FlxTouch;
|
|||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.NGio;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
import funkin.ui.AtlasMenuList;
|
||||
import funkin.ui.MenuList.MenuItem;
|
||||
import funkin.ui.MenuList;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.OptionsState;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.WindowUtil;
|
||||
import lime.app.Application;
|
||||
import openfl.filters.ShaderFilter;
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
|
@ -82,8 +72,10 @@ class MainMenuState extends MusicBeatState
|
|||
magenta.y = bg.y;
|
||||
magenta.visible = false;
|
||||
magenta.color = 0xFFfd719b;
|
||||
if (PreferencesMenu.preferences.get('flashing-menu')) add(magenta);
|
||||
// magenta.scrollFactor.set();
|
||||
|
||||
// TODO: Why doesn't this line compile I'm going fucking feral
|
||||
|
||||
if (Preferences.flashingLights) add(magenta);
|
||||
|
||||
menuItems = new MenuTypedList<AtlasMenuItem>();
|
||||
add(menuItems);
|
||||
|
@ -116,7 +108,7 @@ class MainMenuState extends MusicBeatState
|
|||
#end
|
||||
|
||||
createMenuItem('options', 'mainmenu/options', function() {
|
||||
startExitState(new OptionsState());
|
||||
startExitState(new funkin.ui.OptionsState());
|
||||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
|
|
|
@ -86,10 +86,10 @@ class NGio
|
|||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && FlxG.save.data.sessionId != null)
|
||||
if (sessionId == null && Save.get().ngSessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = FlxG.save.data.sessionId;
|
||||
sessionId = Save.get().ngSessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
|
@ -159,8 +159,8 @@ class NGio
|
|||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
FlxG.save.data.sessionId = NG.core.sessionId;
|
||||
FlxG.save.flush();
|
||||
Save.get().ngSessionId = NG.core.sessionId;
|
||||
Save.get().flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
|
@ -174,8 +174,8 @@ class NGio
|
|||
{
|
||||
NG.core.logOut();
|
||||
|
||||
FlxG.save.data.sessionId = null;
|
||||
FlxG.save.flush();
|
||||
Save.get().ngSessionId = null;
|
||||
Save.get().flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
|
|
|
@ -157,6 +157,11 @@ class PauseSubState extends MusicBeatSubState
|
|||
|
||||
super.update(elapsed);
|
||||
|
||||
handleInputs();
|
||||
}
|
||||
|
||||
function handleInputs():Void
|
||||
{
|
||||
var upP = controls.UI_UP_P;
|
||||
var downP = controls.UI_DOWN_P;
|
||||
var accepted = controls.ACCEPT;
|
||||
|
@ -224,9 +229,14 @@ class PauseSubState extends MusicBeatSubState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY));
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
openSubState(new funkin.ui.StickerSubState(null, STORY));
|
||||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.StickerSubState(null, FREEPLAY));
|
||||
}
|
||||
|
||||
case 'Exit to Chart Editor':
|
||||
this.close();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.save.Save;
|
||||
import funkin.Controls;
|
||||
import flixel.FlxCamera;
|
||||
import funkin.input.PreciseInputManager;
|
||||
|
@ -11,121 +12,36 @@ import flixel.util.FlxSignal;
|
|||
// import props.Player;
|
||||
class PlayerSettings
|
||||
{
|
||||
static public var numPlayers(default, null) = 0;
|
||||
static public var numAvatars(default, null) = 0;
|
||||
static public var player1(default, null):PlayerSettings;
|
||||
static public var player2(default, null):PlayerSettings;
|
||||
public static var numPlayers(default, null) = 0;
|
||||
public static var numAvatars(default, null) = 0;
|
||||
public static var player1(default, null):PlayerSettings;
|
||||
public static var player2(default, null):PlayerSettings;
|
||||
|
||||
static public var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
static public var onAvatarRemove(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
public static var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
public static var onAvatarRemove(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
|
||||
public var id(default, null):Int;
|
||||
|
||||
public var controls(default, null):Controls;
|
||||
|
||||
// public var avatar:Player;
|
||||
// public var camera(get, never):PlayCamera;
|
||||
|
||||
function new(id:Int)
|
||||
/**
|
||||
* Return the PlayerSettings for the given player number, or `null` if that player isn't active.
|
||||
*/
|
||||
public static function get(id:Int):Null<PlayerSettings>
|
||||
{
|
||||
trace('loading player settings for id: $id');
|
||||
|
||||
this.id = id;
|
||||
this.controls = new Controls('player$id', None);
|
||||
|
||||
#if CLEAR_INPUT_SAVE
|
||||
FlxG.save.data.controls = null;
|
||||
FlxG.save.flush();
|
||||
#end
|
||||
|
||||
var useDefault = true;
|
||||
var controlData = FlxG.save.data.controls;
|
||||
if (controlData != null)
|
||||
return switch (id)
|
||||
{
|
||||
var keyData:Dynamic = null;
|
||||
if (id == 0 && controlData.p1 != null && controlData.p1.keys != null) keyData = controlData.p1.keys;
|
||||
else if (id == 1 && controlData.p2 != null && controlData.p2.keys != null) keyData = controlData.p2.keys;
|
||||
|
||||
if (keyData != null)
|
||||
{
|
||||
useDefault = false;
|
||||
trace("loaded key data: " + haxe.Json.stringify(keyData));
|
||||
controls.fromSaveData(keyData, Keys);
|
||||
}
|
||||
}
|
||||
|
||||
if (useDefault)
|
||||
{
|
||||
trace("falling back to default control scheme");
|
||||
controls.setKeyboardScheme(Solo);
|
||||
}
|
||||
|
||||
// Apply loaded settings.
|
||||
PreciseInputManager.instance.initializeKeys(controls);
|
||||
case 1: player1;
|
||||
case 2: player2;
|
||||
default: null;
|
||||
};
|
||||
}
|
||||
|
||||
function addGamepad(gamepad:FlxGamepad)
|
||||
{
|
||||
var useDefault = true;
|
||||
var controlData = FlxG.save.data.controls;
|
||||
if (controlData != null)
|
||||
{
|
||||
var padData:Dynamic = null;
|
||||
if (id == 0 && controlData.p1 != null && controlData.p1.pad != null) padData = controlData.p1.pad;
|
||||
else if (id == 1 && controlData.p2 != null && controlData.p2.pad != null) padData = controlData.p2.pad;
|
||||
|
||||
if (padData != null)
|
||||
{
|
||||
useDefault = false;
|
||||
trace("loaded pad data: " + haxe.Json.stringify(padData));
|
||||
controls.addGamepadWithSaveData(gamepad.id, padData);
|
||||
}
|
||||
}
|
||||
|
||||
if (useDefault) controls.addDefaultGamepad(gamepad.id);
|
||||
}
|
||||
|
||||
public function saveControls()
|
||||
{
|
||||
if (FlxG.save.data.controls == null) FlxG.save.data.controls = {};
|
||||
|
||||
var playerData:{?keys:Dynamic, ?pad:Dynamic}
|
||||
if (id == 0)
|
||||
{
|
||||
if (FlxG.save.data.controls.p1 == null) FlxG.save.data.controls.p1 = {};
|
||||
playerData = FlxG.save.data.controls.p1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FlxG.save.data.controls.p2 == null) FlxG.save.data.controls.p2 = {};
|
||||
playerData = FlxG.save.data.controls.p2;
|
||||
}
|
||||
|
||||
var keyData = controls.createSaveData(Keys);
|
||||
if (keyData != null)
|
||||
{
|
||||
playerData.keys = keyData;
|
||||
trace("saving key data: " + haxe.Json.stringify(keyData));
|
||||
}
|
||||
|
||||
if (controls.gamepadsAdded.length > 0)
|
||||
{
|
||||
var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0]));
|
||||
if (padData != null)
|
||||
{
|
||||
trace("saving pad data: " + haxe.Json.stringify(padData));
|
||||
playerData.pad = padData;
|
||||
}
|
||||
}
|
||||
|
||||
FlxG.save.flush();
|
||||
}
|
||||
|
||||
static public function init():Void
|
||||
public static function init():Void
|
||||
{
|
||||
if (player1 == null)
|
||||
{
|
||||
player1 = new PlayerSettings(0);
|
||||
player1 = new PlayerSettings(1);
|
||||
++numPlayers;
|
||||
}
|
||||
|
||||
|
@ -137,26 +53,13 @@ class PlayerSettings
|
|||
var gamepad = FlxG.gamepads.getByID(i);
|
||||
if (gamepad != null) onGamepadAdded(gamepad);
|
||||
}
|
||||
}
|
||||
|
||||
// player1.controls.addDefaultGamepad(0);
|
||||
// }
|
||||
|
||||
// if (numGamepads > 1)
|
||||
// {
|
||||
// if (player2 == null)
|
||||
// {
|
||||
// player2 = new PlayerSettings(1, None);
|
||||
// ++numPlayers;
|
||||
// }
|
||||
|
||||
// var gamepad = FlxG.gamepads.getByID(1);
|
||||
// if (gamepad == null)
|
||||
// throw 'Unexpected null gamepad. id:0';
|
||||
|
||||
// player2.controls.addDefaultGamepad(1);
|
||||
// }
|
||||
|
||||
// DeviceManager.init();
|
||||
public static function reset()
|
||||
{
|
||||
player1 = null;
|
||||
player2 = null;
|
||||
numPlayers = 0;
|
||||
}
|
||||
|
||||
static function onGamepadAdded(gamepad:FlxGamepad)
|
||||
|
@ -164,86 +67,90 @@ class PlayerSettings
|
|||
player1.addGamepad(gamepad);
|
||||
}
|
||||
|
||||
/*
|
||||
public function setKeyboardScheme(scheme)
|
||||
{
|
||||
controls.setKeyboardScheme(scheme);
|
||||
}
|
||||
|
||||
static public function addAvatar(avatar:Player):PlayerSettings
|
||||
{
|
||||
var settings:PlayerSettings;
|
||||
|
||||
if (player1 == null)
|
||||
{
|
||||
player1 = new PlayerSettings(0, Solo);
|
||||
++numPlayers;
|
||||
}
|
||||
|
||||
if (player1.avatar == null)
|
||||
settings = player1;
|
||||
else
|
||||
{
|
||||
if (player2 == null)
|
||||
{
|
||||
if (player1.controls.keyboardScheme.match(Duo(true)))
|
||||
player2 = new PlayerSettings(1, Duo(false));
|
||||
else
|
||||
player2 = new PlayerSettings(1, None);
|
||||
++numPlayers;
|
||||
}
|
||||
|
||||
if (player2.avatar == null)
|
||||
settings = player2;
|
||||
else
|
||||
throw throw 'Invalid number of players: ${numPlayers + 1}';
|
||||
}
|
||||
++numAvatars;
|
||||
settings.avatar = avatar;
|
||||
avatar.settings = settings;
|
||||
|
||||
splitCameras();
|
||||
|
||||
onAvatarAdd.dispatch(settings);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
static public function removeAvatar(avatar:Player):Void
|
||||
{
|
||||
var settings:PlayerSettings;
|
||||
|
||||
if (player1 != null && player1.avatar == avatar)
|
||||
settings = player1;
|
||||
else if (player2 != null && player2.avatar == avatar)
|
||||
{
|
||||
settings = player2;
|
||||
if (player1.controls.keyboardScheme.match(Duo(_)))
|
||||
player1.setKeyboardScheme(Solo);
|
||||
}
|
||||
else
|
||||
throw "Cannot remove avatar that is not for a player";
|
||||
|
||||
settings.avatar = null;
|
||||
while (settings.controls.gamepadsAdded.length > 0)
|
||||
{
|
||||
final id = settings.controls.gamepadsAdded.shift();
|
||||
settings.controls.removeGamepad(id);
|
||||
DeviceManager.releaseGamepad(FlxG.gamepads.getByID(id));
|
||||
}
|
||||
|
||||
--numAvatars;
|
||||
|
||||
splitCameras();
|
||||
|
||||
onAvatarRemove.dispatch(avatar.settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The player number this represents. This was refactored to START AT `1`.
|
||||
*/
|
||||
static public function reset()
|
||||
private function new(id:Int)
|
||||
{
|
||||
player1 = null;
|
||||
player2 = null;
|
||||
numPlayers = 0;
|
||||
trace('loading player settings for id: $id');
|
||||
|
||||
this.id = id;
|
||||
this.controls = new Controls('player$id', None);
|
||||
|
||||
addKeyboard();
|
||||
}
|
||||
|
||||
function addKeyboard():Void
|
||||
{
|
||||
var useDefault = true;
|
||||
if (Save.get().hasControls(id, Keys))
|
||||
{
|
||||
var keyControlData = Save.get().getControls(id, Keys);
|
||||
trace("keyControlData: " + haxe.Json.stringify(keyControlData));
|
||||
useDefault = false;
|
||||
controls.fromSaveData(keyControlData, Keys);
|
||||
}
|
||||
else
|
||||
{
|
||||
useDefault = true;
|
||||
}
|
||||
|
||||
if (useDefault)
|
||||
{
|
||||
trace("Loading default keyboard control scheme");
|
||||
controls.setKeyboardScheme(Solo);
|
||||
}
|
||||
|
||||
PreciseInputManager.instance.initializeKeys(controls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after an FlxGamepad has been detected.
|
||||
* @param gamepad The gamepad that was detected.
|
||||
*/
|
||||
function addGamepad(gamepad:FlxGamepad)
|
||||
{
|
||||
var useDefault = true;
|
||||
if (Save.get().hasControls(id, Gamepad(gamepad.id)))
|
||||
{
|
||||
var padControlData = Save.get().getControls(id, Gamepad(gamepad.id));
|
||||
trace("padControlData: " + haxe.Json.stringify(padControlData));
|
||||
useDefault = false;
|
||||
controls.addGamepadWithSaveData(gamepad.id, padControlData);
|
||||
}
|
||||
else
|
||||
{
|
||||
useDefault = true;
|
||||
}
|
||||
|
||||
if (useDefault)
|
||||
{
|
||||
trace("Loading gamepad control scheme");
|
||||
controls.addDefaultGamepad(gamepad.id);
|
||||
}
|
||||
PreciseInputManager.instance.initializeButtons(controls, gamepad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save this player's controls to the game's persistent save.
|
||||
*/
|
||||
public function saveControls()
|
||||
{
|
||||
var keyData = controls.createSaveData(Keys);
|
||||
if (keyData != null)
|
||||
{
|
||||
trace("saving key data: " + haxe.Json.stringify(keyData));
|
||||
Save.get().setControls(id, Keys, keyData);
|
||||
}
|
||||
|
||||
if (controls.gamepadsAdded.length > 0)
|
||||
{
|
||||
var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0]));
|
||||
if (padData != null)
|
||||
{
|
||||
trace("saving pad data: " + haxe.Json.stringify(padData));
|
||||
Save.get().setControls(id, Gamepad(controls.gamepadsAdded[0]), padData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
138
source/funkin/Preferences.hx
Normal file
138
source/funkin/Preferences.hx
Normal file
|
@ -0,0 +1,138 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.save.Save;
|
||||
|
||||
/**
|
||||
* A store of user-configurable, globally relevant values.
|
||||
*/
|
||||
class Preferences
|
||||
{
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var naughtyness(get, set):Bool;
|
||||
|
||||
static function get_naughtyness():Bool
|
||||
{
|
||||
return Save.get().options.naughtyness;
|
||||
}
|
||||
|
||||
static function set_naughtyness(value:Bool):Bool
|
||||
{
|
||||
return Save.get().options.naughtyness = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the strumline is at the bottom of the screen rather than the top.
|
||||
* @default `false`
|
||||
*/
|
||||
public static var downscroll(get, set):Bool;
|
||||
|
||||
static function get_downscroll():Bool
|
||||
{
|
||||
return Save.get().options.downscroll;
|
||||
}
|
||||
|
||||
static function set_downscroll(value:Bool):Bool
|
||||
{
|
||||
return Save.get().options.downscroll = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If disabled, flashing lights in the main menu and other areas will be less intense.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var flashingLights(get, set):Bool;
|
||||
|
||||
static function get_flashingLights():Bool
|
||||
{
|
||||
return Save.get().options.flashingLights;
|
||||
}
|
||||
|
||||
static function set_flashingLights(value:Bool):Bool
|
||||
{
|
||||
return Save.get().options.flashingLights = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If disabled, the camera bump synchronized to the beat.
|
||||
* @default `false`
|
||||
*/
|
||||
public static var zoomCamera(get, set):Bool;
|
||||
|
||||
static function get_zoomCamera():Bool
|
||||
{
|
||||
return Save.get().options.zoomCamera;
|
||||
}
|
||||
|
||||
static function set_zoomCamera(value:Bool):Bool
|
||||
{
|
||||
return Save.get().options.zoomCamera = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
|
||||
* @default `false`
|
||||
*/
|
||||
public static var debugDisplay(get, set):Bool;
|
||||
|
||||
static function get_debugDisplay():Bool
|
||||
{
|
||||
return Save.get().options.debugDisplay;
|
||||
}
|
||||
|
||||
static function set_debugDisplay(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.get().options.debugDisplay)
|
||||
{
|
||||
toggleDebugDisplay(value);
|
||||
}
|
||||
|
||||
return Save.get().options.debugDisplay = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the game will automatically pause when tabbing out.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var autoPause(get, set):Bool;
|
||||
|
||||
static function get_autoPause():Bool
|
||||
{
|
||||
return Save.get().options.autoPause;
|
||||
}
|
||||
|
||||
static function set_autoPause(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.get().options.autoPause) FlxG.autoPause = value;
|
||||
|
||||
return Save.get().options.autoPause = value;
|
||||
}
|
||||
|
||||
public static function init():Void
|
||||
{
|
||||
FlxG.autoPause = Preferences.autoPause;
|
||||
toggleDebugDisplay(Preferences.debugDisplay);
|
||||
}
|
||||
|
||||
static function toggleDebugDisplay(show:Bool):Void
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
// Enable the debug display.
|
||||
FlxG.stage.addChild(Main.fpsCounter);
|
||||
#if !html5
|
||||
FlxG.stage.addChild(Main.memoryCounter);
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disable the debug display.
|
||||
FlxG.stage.removeChild(Main.fpsCounter);
|
||||
#if !html5
|
||||
FlxG.stage.removeChild(Main.memoryCounter);
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
|
@ -86,10 +86,10 @@ class NGUtil
|
|||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && FlxG.save.data.sessionId != null)
|
||||
if (sessionId == null && Save.get().ngSessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = FlxG.save.data.sessionId;
|
||||
sessionId = Save.get().ngSessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
|
@ -159,8 +159,8 @@ class NGUtil
|
|||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
FlxG.save.data.sessionId = NG.core.sessionId;
|
||||
FlxG.save.flush();
|
||||
Save.get().ngSessionId = NG.core.sessionId;
|
||||
Save.get().flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
|
@ -174,8 +174,8 @@ class NGUtil
|
|||
{
|
||||
NG.core.logOut();
|
||||
|
||||
FlxG.save.data.sessionId = null;
|
||||
FlxG.save.flush();
|
||||
Save.get().ngSessionId = null;
|
||||
Save.get().flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
|
|
|
@ -7,7 +7,6 @@ import flixel.graphics.frames.FlxAtlasFrames;
|
|||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.ui.PreferencesMenu.CheckboxThingie;
|
||||
|
||||
using Lambda;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import flixel.util.typeLimit.OneOfTwo;
|
|||
import funkin.data.song.SongRegistry;
|
||||
import thx.semver.Version;
|
||||
|
||||
@:nullSafety
|
||||
class SongMetadata
|
||||
{
|
||||
/**
|
||||
|
@ -42,7 +43,7 @@ class SongMetadata
|
|||
public var timeChanges:Array<SongTimeChange>;
|
||||
|
||||
/**
|
||||
* Defaults to `default` or `''`. Populated later.
|
||||
* Defaults to `Constants.DEFAULT_VARIATION`. Populated later.
|
||||
*/
|
||||
@:jignored
|
||||
public var variation:String;
|
||||
|
@ -228,10 +229,10 @@ class SongMusicData
|
|||
public var timeChanges:Array<SongTimeChange>;
|
||||
|
||||
/**
|
||||
* Defaults to `default` or `''`. Populated later.
|
||||
* Defaults to `Constants.DEFAULT_VARIATION`. Populated later.
|
||||
*/
|
||||
@:jignored
|
||||
public var variation:String = Constants.DEFAULT_VARIATION;
|
||||
public var variation:String;
|
||||
|
||||
public function new(songName:String, artist:String, variation:String = 'default')
|
||||
{
|
||||
|
@ -375,6 +376,9 @@ class SongChartData
|
|||
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
|
||||
public var generatedBy:String;
|
||||
|
||||
/**
|
||||
* Defaults to `Constants.DEFAULT_VARIATION`. Populated later.
|
||||
*/
|
||||
@:jignored
|
||||
public var variation:String;
|
||||
|
||||
|
|
|
@ -21,11 +21,21 @@ class SongDataUtils
|
|||
* @param notes The notes to modify.
|
||||
* @param offset The time difference to apply in milliseconds.
|
||||
*/
|
||||
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Int):Array<SongNoteData>
|
||||
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Float):Array<SongNoteData>
|
||||
{
|
||||
return notes.map(function(note:SongNoteData):SongNoteData {
|
||||
return new SongNoteData(note.time + offset, note.data, note.length, note.kind);
|
||||
});
|
||||
var offsetNote = function(note:SongNoteData):SongNoteData {
|
||||
var time:Float = note.time + offset;
|
||||
var data:Int = note.data;
|
||||
var length:Float = note.length;
|
||||
var kind:String = note.kind;
|
||||
return new SongNoteData(time, data, length, kind);
|
||||
};
|
||||
|
||||
trace(notes);
|
||||
trace(notes[0]);
|
||||
var result = [for (i in 0...notes.length) offsetNote(notes[i])];
|
||||
trace(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +46,7 @@ class SongDataUtils
|
|||
* @param events The events to modify.
|
||||
* @param offset The time difference to apply in milliseconds.
|
||||
*/
|
||||
public static function offsetSongEventData(events:Array<SongEventData>, offset:Int):Array<SongEventData>
|
||||
public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
|
||||
{
|
||||
return events.map(function(event:SongEventData):SongEventData {
|
||||
return new SongEventData(event.time + offset, event.event, event.value);
|
||||
|
@ -152,7 +162,8 @@ class SongDataUtils
|
|||
*/
|
||||
public static function writeItemsToClipboard(data:SongClipboardItems):Void
|
||||
{
|
||||
var dataString = SerializerUtil.toJSON(data);
|
||||
var writer = new json2object.JsonWriter<SongClipboardItems>();
|
||||
var dataString:String = writer.write(data, ' ');
|
||||
|
||||
ClipboardUtil.setClipboard(dataString);
|
||||
|
||||
|
@ -170,19 +181,24 @@ class SongDataUtils
|
|||
|
||||
trace('Read ${notesString.length} characters from clipboard.');
|
||||
|
||||
var data:SongClipboardItems = notesString.parseJSON();
|
||||
|
||||
if (data == null)
|
||||
var parser = new json2object.JsonParser<SongClipboardItems>();
|
||||
parser.fromJson(notesString, 'clipboard');
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('Failed to parse notes from clipboard.');
|
||||
trace('[SongDataUtils] Error parsing note JSON data from clipboard.');
|
||||
for (error in parser.errors)
|
||||
DataError.printError(error);
|
||||
return {
|
||||
valid: false,
|
||||
notes: [],
|
||||
events: []
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var data:SongClipboardItems = parser.value;
|
||||
trace('Parsed ' + data.notes.length + ' notes and ' + data.events.length + ' from clipboard.');
|
||||
data.valid = true;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +246,7 @@ class SongDataUtils
|
|||
|
||||
typedef SongClipboardItems =
|
||||
{
|
||||
?valid:Bool,
|
||||
notes:Array<SongNoteData>,
|
||||
events:Array<SongEventData>
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return cleanMetadata(parser.value, variation);
|
||||
}
|
||||
|
||||
public function parseEntryMetadataWithMigration(id:String, ?variation:String, version:thx.semver.Version):Null<SongMetadata>
|
||||
public function parseEntryMetadataWithMigration(id:String, variation:String, version:thx.semver.Version):Null<SongMetadata>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
|
@ -192,7 +192,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
}
|
||||
}
|
||||
|
||||
function parseEntryMetadata_v2_0_0(id:String, variation:String = ""):Null<SongMetadata>
|
||||
function parseEntryMetadata_v2_0_0(id:String, ?variation:String):Null<SongMetadata>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ package;
|
|||
// Only import these when we aren't in a macro.
|
||||
import funkin.util.Constants;
|
||||
import funkin.Paths;
|
||||
import funkin.Preferences;
|
||||
import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
|
||||
|
||||
// These are great.
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
package funkin.input;
|
||||
|
||||
import openfl.ui.Keyboard;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import flixel.input.keyboard.FlxKeyboard.FlxKeyInput;
|
||||
import openfl.events.KeyboardEvent;
|
||||
import flixel.FlxG;
|
||||
import flixel.input.FlxInput;
|
||||
import flixel.input.FlxInput.FlxInputState;
|
||||
import flixel.input.FlxKeyManager;
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.input.keyboard.FlxKeyboard.FlxKeyInput;
|
||||
import flixel.input.keyboard.FlxKeyList;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.util.FlxGamepadUtil;
|
||||
import haxe.Int64;
|
||||
import lime.ui.Gamepad as LimeGamepad;
|
||||
import lime.ui.GamepadAxis as LimeGamepadAxis;
|
||||
import lime.ui.GamepadButton as LimeGamepadButton;
|
||||
import lime.ui.KeyCode;
|
||||
import lime.ui.KeyModifier;
|
||||
import openfl.events.KeyboardEvent;
|
||||
import openfl.ui.Keyboard;
|
||||
|
||||
/**
|
||||
* A precise input manager that:
|
||||
|
@ -43,6 +50,20 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
*/
|
||||
var _keyListDir:Map<FlxKey, NoteDirection>;
|
||||
|
||||
/**
|
||||
* A FlxGamepadID->Array<FlxGamepadInputID>, with FlxGamepadInputID being the counterpart to FlxKey.
|
||||
*/
|
||||
var _buttonList:Map<Int, Array<FlxGamepadInputID>>;
|
||||
|
||||
var _buttonListArray:Array<FlxInput<FlxGamepadInputID>>;
|
||||
|
||||
var _buttonListMap:Map<Int, Map<FlxGamepadInputID, FlxInput<FlxGamepadInputID>>>;
|
||||
|
||||
/**
|
||||
* A FlxGamepadID->FlxGamepadInputID->NoteDirection, with FlxGamepadInputID being the counterpart to FlxKey.
|
||||
*/
|
||||
var _buttonListDir:Map<Int, Map<FlxGamepadInputID, NoteDirection>>;
|
||||
|
||||
/**
|
||||
* The timestamp at which a given note direction was last pressed.
|
||||
*/
|
||||
|
@ -53,17 +74,32 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
*/
|
||||
var _dirReleaseTimestamps:Map<NoteDirection, Int64>;
|
||||
|
||||
var _deviceBinds:Map<FlxGamepad,
|
||||
{
|
||||
onButtonDown:LimeGamepadButton->Int64->Void,
|
||||
onButtonUp:LimeGamepadButton->Int64->Void
|
||||
}>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super(PreciseInputList.new);
|
||||
|
||||
_deviceBinds = [];
|
||||
|
||||
_keyList = [];
|
||||
_dirPressTimestamps = new Map<NoteDirection, Int64>();
|
||||
_dirReleaseTimestamps = new Map<NoteDirection, Int64>();
|
||||
// _keyListMap
|
||||
// _keyListArray
|
||||
_keyListDir = new Map<FlxKey, NoteDirection>();
|
||||
|
||||
FlxG.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
|
||||
FlxG.stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
|
||||
_buttonList = [];
|
||||
_buttonListMap = [];
|
||||
_buttonListArray = [];
|
||||
_buttonListDir = new Map<Int, Map<FlxGamepadInputID, NoteDirection>>();
|
||||
|
||||
_dirPressTimestamps = new Map<NoteDirection, Int64>();
|
||||
_dirReleaseTimestamps = new Map<NoteDirection, Int64>();
|
||||
|
||||
// Keyboard
|
||||
FlxG.stage.application.window.onKeyDownPrecise.add(handleKeyDown);
|
||||
FlxG.stage.application.window.onKeyUpPrecise.add(handleKeyUp);
|
||||
|
||||
|
@ -84,6 +120,17 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
};
|
||||
}
|
||||
|
||||
public static function getButtonsForDirection(controls:Controls, noteDirection:NoteDirection)
|
||||
{
|
||||
return switch (noteDirection)
|
||||
{
|
||||
case NoteDirection.LEFT: controls.getButtonsForAction(NOTE_LEFT);
|
||||
case NoteDirection.DOWN: controls.getButtonsForAction(NOTE_DOWN);
|
||||
case NoteDirection.UP: controls.getButtonsForAction(NOTE_UP);
|
||||
case NoteDirection.RIGHT: controls.getButtonsForAction(NOTE_RIGHT);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from int to Int64.
|
||||
*/
|
||||
|
@ -138,6 +185,43 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
}
|
||||
}
|
||||
|
||||
public function initializeButtons(controls:Controls, gamepad:FlxGamepad):Void
|
||||
{
|
||||
clearButtons();
|
||||
|
||||
var limeGamepad = FlxGamepadUtil.getLimeGamepad(gamepad);
|
||||
var callbacks =
|
||||
{
|
||||
onButtonDown: handleButtonDown.bind(gamepad),
|
||||
onButtonUp: handleButtonUp.bind(gamepad)
|
||||
};
|
||||
limeGamepad.onButtonDownPrecise.add(callbacks.onButtonDown);
|
||||
limeGamepad.onButtonUpPrecise.add(callbacks.onButtonUp);
|
||||
|
||||
for (noteDirection in DIRECTIONS)
|
||||
{
|
||||
var buttons = getButtonsForDirection(controls, noteDirection);
|
||||
for (button in buttons)
|
||||
{
|
||||
var input = new FlxInput<FlxGamepadInputID>(button);
|
||||
|
||||
var buttonListEntry = _buttonList.get(gamepad.id);
|
||||
if (buttonListEntry == null) _buttonList.set(gamepad.id, buttonListEntry = []);
|
||||
buttonListEntry.push(button);
|
||||
|
||||
_buttonListArray.push(input);
|
||||
|
||||
var buttonListMapEntry = _buttonListMap.get(gamepad.id);
|
||||
if (buttonListMapEntry == null) _buttonListMap.set(gamepad.id, buttonListMapEntry = new Map<FlxGamepadInputID, FlxInput<FlxGamepadInputID>>());
|
||||
buttonListMapEntry.set(button, input);
|
||||
|
||||
var buttonListDirEntry = _buttonListDir.get(gamepad.id);
|
||||
if (buttonListDirEntry == null) _buttonListDir.set(gamepad.id, buttonListDirEntry = new Map<FlxGamepadInputID, NoteDirection>());
|
||||
buttonListDirEntry.set(button, noteDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time, in nanoseconds, since the given note direction was last pressed.
|
||||
* @param noteDirection The note direction to check.
|
||||
|
@ -165,11 +249,41 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
return _keyListMap.get(key);
|
||||
}
|
||||
|
||||
public function getInputByButton(gamepad:FlxGamepad, button:FlxGamepadInputID):FlxInput<FlxGamepadInputID>
|
||||
{
|
||||
return _buttonListMap.get(gamepad.id).get(button);
|
||||
}
|
||||
|
||||
public function getDirectionForKey(key:FlxKey):NoteDirection
|
||||
{
|
||||
return _keyListDir.get(key);
|
||||
}
|
||||
|
||||
public function getDirectionForButton(gamepad:FlxGamepad, button:FlxGamepadInputID):NoteDirection
|
||||
{
|
||||
return _buttonListDir.get(gamepad.id).get(button);
|
||||
}
|
||||
|
||||
function getButton(gamepad:FlxGamepad, button:FlxGamepadInputID):FlxInput<FlxGamepadInputID>
|
||||
{
|
||||
return _buttonListMap.get(gamepad.id).get(button);
|
||||
}
|
||||
|
||||
function updateButtonStates(gamepad:FlxGamepad, button:FlxGamepadInputID, down:Bool):Void
|
||||
{
|
||||
var input = getButton(gamepad, button);
|
||||
if (input == null) return;
|
||||
|
||||
if (down)
|
||||
{
|
||||
input.press();
|
||||
}
|
||||
else
|
||||
{
|
||||
input.release();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(keyCode:KeyCode, _:KeyModifier, timestamp:Int64):Void
|
||||
{
|
||||
var key:FlxKey = convertKeyCode(keyCode);
|
||||
|
@ -198,7 +312,7 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
if (_keyList.indexOf(key) == -1) return;
|
||||
|
||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
||||
// This is because SDL3's timestamps are measured in nanoseconds, not milliseconds.
|
||||
// This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds.
|
||||
timestamp *= Constants.NS_PER_MS;
|
||||
|
||||
updateKeyStates(key, false);
|
||||
|
@ -214,6 +328,54 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
}
|
||||
}
|
||||
|
||||
function handleButtonDown(gamepad:FlxGamepad, button:LimeGamepadButton, timestamp:Int64):Void
|
||||
{
|
||||
var buttonId:FlxGamepadInputID = FlxGamepadUtil.getInputID(gamepad, button);
|
||||
|
||||
var buttonListEntry = _buttonList.get(gamepad.id);
|
||||
if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return;
|
||||
|
||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
||||
// This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds.
|
||||
timestamp *= Constants.NS_PER_MS;
|
||||
|
||||
updateButtonStates(gamepad, buttonId, true);
|
||||
|
||||
if (getInputByButton(gamepad, buttonId)?.justPressed ?? false)
|
||||
{
|
||||
onInputPressed.dispatch(
|
||||
{
|
||||
noteDirection: getDirectionForButton(gamepad, buttonId),
|
||||
timestamp: timestamp
|
||||
});
|
||||
_dirPressTimestamps.set(getDirectionForButton(gamepad, buttonId), timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
function handleButtonUp(gamepad:FlxGamepad, button:LimeGamepadButton, timestamp:Int64):Void
|
||||
{
|
||||
var buttonId:FlxGamepadInputID = FlxGamepadUtil.getInputID(gamepad, button);
|
||||
|
||||
var buttonListEntry = _buttonList.get(gamepad.id);
|
||||
if (buttonListEntry == null || buttonListEntry.indexOf(buttonId) == -1) return;
|
||||
|
||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
||||
// This is because SDL3's timestamps ar e measured in nanoseconds, not milliseconds.
|
||||
timestamp *= Constants.NS_PER_MS;
|
||||
|
||||
updateButtonStates(gamepad, buttonId, false);
|
||||
|
||||
if (getInputByButton(gamepad, buttonId)?.justReleased ?? false)
|
||||
{
|
||||
onInputReleased.dispatch(
|
||||
{
|
||||
noteDirection: getDirectionForButton(gamepad, buttonId),
|
||||
timestamp: timestamp
|
||||
});
|
||||
_dirReleaseTimestamps.set(getDirectionForButton(gamepad, buttonId), timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
static function convertKeyCode(input:KeyCode):FlxKey
|
||||
{
|
||||
@:privateAccess
|
||||
|
@ -228,6 +390,31 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
_keyListMap.clear();
|
||||
_keyListDir.clear();
|
||||
}
|
||||
|
||||
function clearButtons():Void
|
||||
{
|
||||
_buttonListArray = [];
|
||||
_buttonListDir.clear();
|
||||
|
||||
for (gamepad in _deviceBinds.keys())
|
||||
{
|
||||
var callbacks = _deviceBinds.get(gamepad);
|
||||
var limeGamepad = FlxGamepadUtil.getLimeGamepad(gamepad);
|
||||
limeGamepad.onButtonDownPrecise.remove(callbacks.onButtonDown);
|
||||
limeGamepad.onButtonUpPrecise.remove(callbacks.onButtonUp);
|
||||
}
|
||||
_deviceBinds.clear();
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
// Keyboard
|
||||
FlxG.stage.application.window.onKeyDownPrecise.remove(handleKeyDown);
|
||||
FlxG.stage.application.window.onKeyUpPrecise.remove(handleKeyUp);
|
||||
|
||||
clearKeys();
|
||||
clearButtons();
|
||||
}
|
||||
}
|
||||
|
||||
class PreciseInputList extends FlxKeyList
|
||||
|
|
|
@ -14,6 +14,7 @@ import funkin.data.level.LevelRegistry;
|
|||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
|
||||
import funkin.save.Save;
|
||||
import funkin.play.cutscene.dialogue.SpeakerDataParser;
|
||||
import funkin.data.song.SongRegistry;
|
||||
|
||||
|
@ -59,7 +60,7 @@ class PolymodHandler
|
|||
createModRoot();
|
||||
|
||||
trace("Initializing Polymod (using configured mods)...");
|
||||
loadModsById(getEnabledModIds());
|
||||
loadModsById(Save.get().enabledModIds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,33 +233,9 @@ class PolymodHandler
|
|||
return modIds;
|
||||
}
|
||||
|
||||
public static function setEnabledMods(newModList:Array<String>):Void
|
||||
{
|
||||
FlxG.save.data.enabledMods = newModList;
|
||||
// Make sure to COMMIT the changes.
|
||||
FlxG.save.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled mods.
|
||||
* @return Array<String>
|
||||
*/
|
||||
public static function getEnabledModIds():Array<String>
|
||||
{
|
||||
if (FlxG.save.data.enabledMods == null)
|
||||
{
|
||||
// NOTE: If the value is null, the enabled mod list is unconfigured.
|
||||
// Currently, we default to disabling newly installed mods.
|
||||
// If we want to auto-enable new mods, but otherwise leave the configured list in place,
|
||||
// we will need some custom logic.
|
||||
FlxG.save.data.enabledMods = [];
|
||||
}
|
||||
return FlxG.save.data.enabledMods;
|
||||
}
|
||||
|
||||
public static function getEnabledMods():Array<ModMetadata>
|
||||
{
|
||||
var modIds = getEnabledModIds();
|
||||
var modIds = Save.get().enabledModIds;
|
||||
var modMetadata = getAllMods();
|
||||
var enabledMods = [];
|
||||
for (item in modMetadata)
|
||||
|
|
|
@ -11,7 +11,6 @@ import funkin.modding.events.ScriptEvent;
|
|||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
|
||||
/**
|
||||
* A substate which renders over the PlayState when the player dies.
|
||||
|
@ -292,7 +291,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
{
|
||||
var randomCensor:Array<Int> = [];
|
||||
|
||||
if (PreferencesMenu.getPref('censor-naughty')) randomCensor = [1, 3, 8, 13, 17, 21];
|
||||
if (!Preferences.naughtyness) randomCensor = [1, 3, 8, 13, 17, 21];
|
||||
|
||||
FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function() {
|
||||
// Once the quote ends, fade in the game over music.
|
||||
|
|
|
@ -25,6 +25,7 @@ import flixel.ui.FlxBar;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.save.Save;
|
||||
import funkin.Highscore.Tallies;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
@ -919,7 +920,6 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
// Handle keybinds.
|
||||
// if (!isInCutscene && !disableKeys) keyShit(true);
|
||||
processInputQueue();
|
||||
if (!isInCutscene && !disableKeys) debugKeyShit();
|
||||
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
|
||||
|
@ -1267,7 +1267,7 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function initHealthBar():Void
|
||||
{
|
||||
var healthBarYPos:Float = PreferencesMenu.getPref('downscroll') ? FlxG.height * 0.1 : FlxG.height * 0.9;
|
||||
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
|
||||
healthBarBG = new FlxSprite(0, healthBarYPos).loadGraphic(Paths.image('healthBar'));
|
||||
healthBarBG.screenCenter(X);
|
||||
healthBarBG.scrollFactor.set(0, 0);
|
||||
|
@ -1476,13 +1476,13 @@ class PlayState extends MusicBeatSubState
|
|||
// Position the player strumline on the right half of the screen
|
||||
playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style
|
||||
// playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style
|
||||
playerStrumline.y = PreferencesMenu.getPref('downscroll') ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
|
||||
playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
|
||||
playerStrumline.zIndex = 200;
|
||||
playerStrumline.cameras = [camHUD];
|
||||
|
||||
// Position the opponent strumline on the left half of the screen
|
||||
opponentStrumline.x = Constants.STRUMLINE_X_OFFSET;
|
||||
opponentStrumline.y = PreferencesMenu.getPref('downscroll') ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
|
||||
opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
|
||||
opponentStrumline.zIndex = 100;
|
||||
opponentStrumline.cameras = [camHUD];
|
||||
|
||||
|
@ -2393,9 +2393,32 @@ class PlayState extends MusicBeatSubState
|
|||
if (currentSong != null && currentSong.validScore)
|
||||
{
|
||||
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
||||
Highscore.tallies.isNewHighscore = Highscore.saveScoreForDifficulty(currentSong.id, songScore, currentDifficulty);
|
||||
var data =
|
||||
{
|
||||
score: songScore,
|
||||
tallies:
|
||||
{
|
||||
killer: Highscore.tallies.killer,
|
||||
sick: Highscore.tallies.sick,
|
||||
good: Highscore.tallies.good,
|
||||
bad: Highscore.tallies.bad,
|
||||
shit: Highscore.tallies.shit,
|
||||
missed: Highscore.tallies.missed,
|
||||
combo: Highscore.tallies.combo,
|
||||
maxCombo: Highscore.tallies.maxCombo,
|
||||
totalNotesHit: Highscore.tallies.totalNotesHit,
|
||||
totalNotes: Highscore.tallies.totalNotes,
|
||||
},
|
||||
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
||||
};
|
||||
|
||||
Highscore.saveCompletionForDifficulty(currentSong.id, Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, currentDifficulty);
|
||||
if (Save.get().isSongHighScore(currentSong.id, currentDifficulty, data))
|
||||
{
|
||||
Save.get().setSongScore(currentSong.id, currentDifficulty, data);
|
||||
#if newgrounds
|
||||
NGio.postScore(score, currentSong.id);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
|
@ -2419,11 +2442,35 @@ class PlayState extends MusicBeatSubState
|
|||
if (currentSong.validScore)
|
||||
{
|
||||
NGio.unlockMedal(60961);
|
||||
Highscore.saveWeekScoreForDifficulty(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignScore, PlayStatePlaylist.campaignDifficulty);
|
||||
}
|
||||
|
||||
// FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked;
|
||||
FlxG.save.flush();
|
||||
var data:SaveScoreData =
|
||||
{
|
||||
score: PlayStatePlaylist.campaignScore,
|
||||
tallies:
|
||||
{
|
||||
// TODO: Sum up the values for the whole level!
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
},
|
||||
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
||||
};
|
||||
|
||||
if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
||||
{
|
||||
Save.get().setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
||||
#if newgrounds
|
||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
if (isSubState)
|
||||
{
|
||||
|
|
|
@ -231,7 +231,7 @@ class Strumline extends FlxSpriteGroup
|
|||
notesVwoosh.add(note);
|
||||
|
||||
var targetY:Float = FlxG.height + note.y;
|
||||
if (PreferencesMenu.getPref('downscroll')) targetY = 0 - note.height;
|
||||
if (Preferences.downscroll) targetY = 0 - note.height;
|
||||
FlxTween.tween(note, {y: targetY}, 0.5,
|
||||
{
|
||||
ease: FlxEase.expoIn,
|
||||
|
@ -252,7 +252,7 @@ class Strumline extends FlxSpriteGroup
|
|||
holdNotesVwoosh.add(holdNote);
|
||||
|
||||
var targetY:Float = FlxG.height + holdNote.y;
|
||||
if (PreferencesMenu.getPref('downscroll')) targetY = 0 - holdNote.height;
|
||||
if (Preferences.downscroll) targetY = 0 - holdNote.height;
|
||||
FlxTween.tween(holdNote, {y: targetY}, 0.5,
|
||||
{
|
||||
ease: FlxEase.expoIn,
|
||||
|
@ -277,7 +277,7 @@ class Strumline extends FlxSpriteGroup
|
|||
var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
|
||||
var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
|
||||
|
||||
return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (PreferencesMenu.getPref('downscroll') ? 1 : -1);
|
||||
return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
|
||||
}
|
||||
|
||||
function updateNotes():Void
|
||||
|
@ -321,7 +321,7 @@ class Strumline extends FlxSpriteGroup
|
|||
note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime, vwoosh);
|
||||
|
||||
// If the note is miss
|
||||
var isOffscreen = PreferencesMenu.getPref('downscroll') ? note.y > FlxG.height : note.y < -note.height;
|
||||
var isOffscreen = Preferences.downscroll ? note.y > FlxG.height : note.y < -note.height;
|
||||
if (note.handledMiss && isOffscreen)
|
||||
{
|
||||
killNote(note);
|
||||
|
@ -388,7 +388,7 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
var vwoosh:Bool = false;
|
||||
|
||||
if (PreferencesMenu.getPref('downscroll'))
|
||||
if (Preferences.downscroll)
|
||||
{
|
||||
holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ class Strumline extends FlxSpriteGroup
|
|||
holdNote.visible = false;
|
||||
}
|
||||
|
||||
if (PreferencesMenu.getPref('downscroll'))
|
||||
if (Preferences.downscroll)
|
||||
{
|
||||
holdNote.y = this.y - holdNote.height + STRUMLINE_SIZE / 2;
|
||||
}
|
||||
|
@ -425,7 +425,7 @@ class Strumline extends FlxSpriteGroup
|
|||
holdNote.visible = true;
|
||||
var vwoosh:Bool = false;
|
||||
|
||||
if (PreferencesMenu.getPref('downscroll'))
|
||||
if (Preferences.downscroll)
|
||||
{
|
||||
holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ class SustainTrail extends FlxSprite
|
|||
height = sustainHeight(sustainLength, getScrollSpeed());
|
||||
// instead of scrollSpeed, PlayState.SONG.speed
|
||||
|
||||
flipY = PreferencesMenu.getPref('downscroll');
|
||||
flipY = Preferences.downscroll;
|
||||
|
||||
// alpha = 0.6;
|
||||
alpha = 1.0;
|
||||
|
|
700
source/funkin/save/Save.hx
Normal file
700
source/funkin/save/Save.hx
Normal file
|
@ -0,0 +1,700 @@
|
|||
package funkin.save;
|
||||
|
||||
import flixel.util.FlxSave;
|
||||
import funkin.save.migrator.SaveDataMigrator;
|
||||
import thx.semver.Version;
|
||||
import funkin.Controls.Device;
|
||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||
|
||||
@:nullSafety
|
||||
@:forward(volume, mute)
|
||||
abstract Save(RawSaveData)
|
||||
{
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.0";
|
||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||
|
||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||
static final SAVE_PATH:String = 'FunkinCrew';
|
||||
static final SAVE_NAME:String = 'Funkin';
|
||||
|
||||
static final SAVE_PATH_LEGACY:String = 'ninjamuffin99';
|
||||
static final SAVE_NAME_LEGACY:String = 'funkin';
|
||||
|
||||
public static function load():Void
|
||||
{
|
||||
trace("[SAVE] Loading save...");
|
||||
|
||||
// Bind save data.
|
||||
loadFromSlot(1);
|
||||
}
|
||||
|
||||
public static function get():Save
|
||||
{
|
||||
return FlxG.save.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing a new Save will load the default values.
|
||||
*/
|
||||
public function new()
|
||||
{
|
||||
this =
|
||||
{
|
||||
version: Save.SAVE_DATA_VERSION,
|
||||
|
||||
volume: 1.0,
|
||||
mute: false,
|
||||
|
||||
api:
|
||||
{
|
||||
newgrounds:
|
||||
{
|
||||
sessionId: null,
|
||||
}
|
||||
},
|
||||
scores:
|
||||
{
|
||||
// No saved scores.
|
||||
levels: [],
|
||||
songs: [],
|
||||
},
|
||||
options:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingLights: true,
|
||||
zoomCamera: true,
|
||||
debugDisplay: false,
|
||||
autoPause: true,
|
||||
|
||||
controls:
|
||||
{
|
||||
// Leave controls blank so defaults are loaded.
|
||||
p1:
|
||||
{
|
||||
keyboard: {},
|
||||
gamepad: {},
|
||||
},
|
||||
p2:
|
||||
{
|
||||
keyboard: {},
|
||||
gamepad: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mods:
|
||||
{
|
||||
// No mods enabled.
|
||||
enabledMods: [],
|
||||
modOptions: [],
|
||||
},
|
||||
|
||||
optionsChartEditor:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public var options(get, never):SaveDataOptions;
|
||||
|
||||
function get_options():SaveDataOptions
|
||||
{
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public var modOptions(get, never):Map<String, Dynamic>;
|
||||
|
||||
function get_modOptions():Map<String, Dynamic>
|
||||
{
|
||||
return this.mods.modOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current session ID for the logged-in Newgrounds user, or null if the user is cringe.
|
||||
*/
|
||||
public var ngSessionId(get, set):Null<String>;
|
||||
|
||||
function get_ngSessionId():Null<String>
|
||||
{
|
||||
return this.api.newgrounds.sessionId;
|
||||
}
|
||||
|
||||
function set_ngSessionId(value:Null<String>):Null<String>
|
||||
{
|
||||
return this.api.newgrounds.sessionId = value;
|
||||
}
|
||||
|
||||
public var enabledModIds(get, set):Array<String>;
|
||||
|
||||
function get_enabledModIds():Array<String>
|
||||
{
|
||||
return this.mods.enabledMods;
|
||||
}
|
||||
|
||||
function set_enabledModIds(value:Array<String>):Array<String>
|
||||
{
|
||||
return this.mods.enabledMods = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the score the user achieved for a given level on a given difficulty.
|
||||
*
|
||||
* @param levelId The ID of the level/week.
|
||||
* @param difficultyId The difficulty to check.
|
||||
* @return A data structure containing score, judgement counts, and accuracy. Returns `null` if no score is saved.
|
||||
*/
|
||||
public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||
{
|
||||
var level = this.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
level = [];
|
||||
this.scores.levels.set(levelId, level);
|
||||
}
|
||||
|
||||
return level.get(difficultyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the score the user achieved for a given level on a given difficulty.
|
||||
*/
|
||||
public function setLevelScore(levelId:String, difficultyId:String, score:SaveScoreData):Void
|
||||
{
|
||||
var level = this.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
level = [];
|
||||
this.scores.levels.set(levelId, level);
|
||||
}
|
||||
level.set(difficultyId, score);
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
public function isLevelHighScore(levelId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
|
||||
{
|
||||
var level = this.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
level = [];
|
||||
this.scores.levels.set(levelId, level);
|
||||
}
|
||||
|
||||
var currentScore = level.get(difficultyId);
|
||||
if (currentScore == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return score.score > currentScore.score;
|
||||
}
|
||||
|
||||
public function hasBeatenLevel(levelId:String, ?difficultyList:Array<String>):Bool
|
||||
{
|
||||
if (difficultyList == null)
|
||||
{
|
||||
difficultyList = ['easy', 'normal', 'hard'];
|
||||
}
|
||||
for (difficulty in difficultyList)
|
||||
{
|
||||
var score:Null<SaveScoreData> = getLevelScore(levelId, difficulty);
|
||||
// TODO: Do we need to check accuracy/score here?
|
||||
if (score != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the score the user achieved for a given song on a given difficulty.
|
||||
*
|
||||
* @param songId The ID of the song.
|
||||
* @param difficultyId The difficulty to check.
|
||||
* @return A data structure containing score, judgement counts, and accuracy. Returns `null` if no score is saved.
|
||||
*/
|
||||
public function getSongScore(songId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||
{
|
||||
var song = this.scores.songs.get(songId);
|
||||
if (song == null)
|
||||
{
|
||||
song = [];
|
||||
this.scores.songs.set(songId, song);
|
||||
}
|
||||
return song.get(difficultyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the score the user achieved for a given song on a given difficulty.
|
||||
*/
|
||||
public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void
|
||||
{
|
||||
var song = this.scores.songs.get(songId);
|
||||
if (song == null)
|
||||
{
|
||||
song = [];
|
||||
this.scores.songs.set(songId, song);
|
||||
}
|
||||
song.set(difficultyId, score);
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided score data better than the current high score for the given song?
|
||||
* @param songId The song ID to check.
|
||||
* @param difficultyId The difficulty to check.
|
||||
* @param score The score to check.
|
||||
* @return Whether the score is better than the current high score.
|
||||
*/
|
||||
public function isSongHighScore(songId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
|
||||
{
|
||||
var song = this.scores.songs.get(songId);
|
||||
if (song == null)
|
||||
{
|
||||
song = [];
|
||||
this.scores.songs.set(songId, song);
|
||||
}
|
||||
|
||||
var currentScore = song.get(difficultyId);
|
||||
if (currentScore == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return score.score > currentScore.score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the provided song been beaten on one of the listed difficulties?
|
||||
* @param songId The song ID to check.
|
||||
* @param difficultyList The difficulties to check. Defaults to `easy`, `normal`, and `hard`.
|
||||
* @return Whether the song has been beaten on any of the listed difficulties.
|
||||
*/
|
||||
public function hasBeatenSong(songId:String, ?difficultyList:Array<String>):Bool
|
||||
{
|
||||
if (difficultyList == null)
|
||||
{
|
||||
difficultyList = ['easy', 'normal', 'hard'];
|
||||
}
|
||||
for (difficulty in difficultyList)
|
||||
{
|
||||
var score:Null<SaveScoreData> = getSongScore(songId, difficulty);
|
||||
// TODO: Do we need to check accuracy/score here?
|
||||
if (score != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getControls(playerId:Int, inputType:Device):SaveControlsData
|
||||
{
|
||||
switch (inputType)
|
||||
{
|
||||
case Keys:
|
||||
return (playerId == 0) ? this.options.controls.p1.keyboard : this.options.controls.p2.keyboard;
|
||||
case Gamepad(_):
|
||||
return (playerId == 0) ? this.options.controls.p1.gamepad : this.options.controls.p2.gamepad;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasControls(playerId:Int, inputType:Device):Bool
|
||||
{
|
||||
var controls = getControls(playerId, inputType);
|
||||
var controlsFields = Reflect.fields(controls);
|
||||
return controlsFields.length > 0;
|
||||
}
|
||||
|
||||
public function setControls(playerId:Int, inputType:Device, controls:SaveControlsData):Void
|
||||
{
|
||||
switch (inputType)
|
||||
{
|
||||
case Keys:
|
||||
if (playerId == 0)
|
||||
{
|
||||
this.options.controls.p1.keyboard = controls;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options.controls.p2.keyboard = controls;
|
||||
}
|
||||
case Gamepad(_):
|
||||
if (playerId == 0)
|
||||
{
|
||||
this.options.controls.p1.gamepad = controls;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options.controls.p2.gamepad = controls;
|
||||
}
|
||||
}
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
public function isCharacterUnlocked(characterId:String):Bool
|
||||
{
|
||||
switch (characterId)
|
||||
{
|
||||
case 'bf':
|
||||
return true;
|
||||
case 'pico':
|
||||
return hasBeatenLevel('weekend1');
|
||||
default:
|
||||
trace('Unknown character ID: ' + characterId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to make sure the save data is written to disk.
|
||||
*/
|
||||
public function flush():Void
|
||||
{
|
||||
FlxG.save.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* If you set slot to `2`, it will load an independe
|
||||
* @param slot
|
||||
*/
|
||||
static function loadFromSlot(slot:Int):Void
|
||||
{
|
||||
trace("[SAVE] Loading save from slot " + slot + "...");
|
||||
|
||||
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
|
||||
if (FlxG.save.isEmpty())
|
||||
{
|
||||
trace('[SAVE] Save data is empty, checking for legacy save data...');
|
||||
var legacySaveData = fetchLegacySaveData();
|
||||
if (legacySaveData != null)
|
||||
{
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
FlxG.save.mergeData(SaveDataMigrator.migrateFromLegacy(legacySaveData));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Loaded save data.');
|
||||
FlxG.save.mergeData(SaveDataMigrator.migrate(FlxG.save.data));
|
||||
}
|
||||
|
||||
trace('[SAVE] Done loading save data.');
|
||||
trace(FlxG.save.data);
|
||||
}
|
||||
|
||||
static function fetchLegacySaveData():Null<RawSaveData_v1_0_0>
|
||||
{
|
||||
trace("[SAVE] Checking for legacy save data...");
|
||||
var legacySave:FlxSave = new FlxSave();
|
||||
legacySave.bind(SAVE_NAME_LEGACY, SAVE_PATH_LEGACY);
|
||||
if (legacySave?.data == null)
|
||||
{
|
||||
trace("[SAVE] No legacy save data found.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace("[SAVE] Legacy save data found.");
|
||||
trace(legacySave.data);
|
||||
return cast legacySave.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An anonymous structure containingg all the user's save data.
|
||||
*/
|
||||
typedef RawSaveData =
|
||||
{
|
||||
// Flixel save data.
|
||||
var volume:Float;
|
||||
var mute:Bool;
|
||||
|
||||
/**
|
||||
* A semantic versioning string for the save data format.
|
||||
*/
|
||||
var version:Version;
|
||||
|
||||
var api:SaveApiData;
|
||||
|
||||
/**
|
||||
* The user's saved scores.
|
||||
*/
|
||||
var scores:SaveHighScoresData;
|
||||
|
||||
/**
|
||||
* The user's preferences.
|
||||
*/
|
||||
var options:SaveDataOptions;
|
||||
|
||||
var mods:SaveDataMods;
|
||||
|
||||
/**
|
||||
* The user's preferences specific to the Chart Editor.
|
||||
*/
|
||||
var optionsChartEditor:SaveDataChartEditorOptions;
|
||||
};
|
||||
|
||||
typedef SaveApiData =
|
||||
{
|
||||
var newgrounds:SaveApiNewgroundsData;
|
||||
}
|
||||
|
||||
typedef SaveApiNewgroundsData =
|
||||
{
|
||||
var sessionId:Null<String>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An anoymous structure containing options about the user's high scores.
|
||||
*/
|
||||
typedef SaveHighScoresData =
|
||||
{
|
||||
/**
|
||||
* Scores for each level (or week).
|
||||
*/
|
||||
var levels:SaveScoreLevelsData;
|
||||
|
||||
/**
|
||||
* Scores for individual songs.
|
||||
*/
|
||||
var songs:SaveScoreSongsData;
|
||||
};
|
||||
|
||||
typedef SaveDataMods =
|
||||
{
|
||||
var enabledMods:Array<String>;
|
||||
var modOptions:Map<String, Dynamic>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key is the level ID, value is the SaveScoreLevelData.
|
||||
*/
|
||||
typedef SaveScoreLevelsData = Map<String, SaveScoreDifficultiesData>;
|
||||
|
||||
/**
|
||||
* Key is the song ID, value is the data for each difficulty.
|
||||
*/
|
||||
typedef SaveScoreSongsData = Map<String, SaveScoreDifficultiesData>;
|
||||
|
||||
/**
|
||||
* Key is the difficulty ID, value is the score.
|
||||
*/
|
||||
typedef SaveScoreDifficultiesData = Map<String, SaveScoreData>;
|
||||
|
||||
/**
|
||||
* An individual score. Contains the score, accuracy, and count of each judgement hit.
|
||||
*/
|
||||
typedef SaveScoreData =
|
||||
{
|
||||
/**
|
||||
* The score achieved.
|
||||
*/
|
||||
var score:Int;
|
||||
|
||||
/**
|
||||
* The count of each judgement hit.
|
||||
*/
|
||||
var tallies:SaveScoreTallyData;
|
||||
|
||||
/**
|
||||
* The accuracy percentage.
|
||||
*/
|
||||
var accuracy:Float;
|
||||
}
|
||||
|
||||
typedef SaveScoreTallyData =
|
||||
{
|
||||
var killer:Int;
|
||||
var sick:Int;
|
||||
var good:Int;
|
||||
var bad:Int;
|
||||
var shit:Int;
|
||||
var missed:Int;
|
||||
var combo:Int;
|
||||
var maxCombo:Int;
|
||||
var totalNotesHit:Int;
|
||||
var totalNotes:Int;
|
||||
}
|
||||
|
||||
/**
|
||||
* An anonymous structure containing all the user's options and preferences for the main game.
|
||||
* Every time you add a new option, it needs to be added here.
|
||||
*/
|
||||
typedef SaveDataOptions =
|
||||
{
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* @default `true`
|
||||
*/
|
||||
var naughtyness:Bool;
|
||||
|
||||
/**
|
||||
* If enabled, the strumline is at the bottom of the screen rather than the top.
|
||||
* @default `false`
|
||||
*/
|
||||
var downscroll:Bool;
|
||||
|
||||
/**
|
||||
* If disabled, flashing lights in the main menu and other areas will be less intense.
|
||||
* @default `true`
|
||||
*/
|
||||
var flashingLights:Bool;
|
||||
|
||||
/**
|
||||
* If disabled, the camera bump synchronized to the beat.
|
||||
* @default `false`
|
||||
*/
|
||||
var zoomCamera:Bool;
|
||||
|
||||
/**
|
||||
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
|
||||
* @default `false`
|
||||
*/
|
||||
var debugDisplay:Bool;
|
||||
|
||||
/**
|
||||
* If enabled, the game will automatically pause when tabbing out.
|
||||
* @default `true`
|
||||
*/
|
||||
var autoPause:Bool;
|
||||
|
||||
var controls:
|
||||
{
|
||||
var p1:
|
||||
{
|
||||
var keyboard:SaveControlsData;
|
||||
var gamepad:SaveControlsData;
|
||||
};
|
||||
var p2:
|
||||
{
|
||||
var keyboard:SaveControlsData;
|
||||
var gamepad:SaveControlsData;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* An anonymous structure containing a specific player's bound keys.
|
||||
* Each key is an action name and each value is an array of keycodes.
|
||||
*
|
||||
* If a keybind is `null`, it needs to be reinitialized to the default.
|
||||
* If a keybind is `[]`, it is UNBOUND by the user and should not be rebound.
|
||||
*/
|
||||
typedef SaveControlsData =
|
||||
{
|
||||
/**
|
||||
* Keybind for navigating in the menu.
|
||||
* @default `Up Arrow`
|
||||
*/
|
||||
var ?UI_UP:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for navigating in the menu.
|
||||
* @default `Left Arrow`
|
||||
*/
|
||||
var ?UI_LEFT:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for navigating in the menu.
|
||||
* @default `Right Arrow`
|
||||
*/
|
||||
var ?UI_RIGHT:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for navigating in the menu.
|
||||
* @default `Down Arrow`
|
||||
*/
|
||||
var ?UI_DOWN:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for hitting notes.
|
||||
* @default `A` and `Left Arrow`
|
||||
*/
|
||||
var ?NOTE_LEFT:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for hitting notes.
|
||||
* @default `W` and `Up Arrow`
|
||||
*/
|
||||
var ?NOTE_UP:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for hitting notes.
|
||||
* @default `S` and `Down Arrow`
|
||||
*/
|
||||
var ?NOTE_DOWN:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for hitting notes.
|
||||
* @default `D` and `Right Arrow`
|
||||
*/
|
||||
var ?NOTE_RIGHT:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for continue/OK in menus.
|
||||
* @default `Enter` and `Space`
|
||||
*/
|
||||
var ?ACCEPT:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for back/cancel in menus.
|
||||
* @default `Escape`
|
||||
*/
|
||||
var ?BACK:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for pausing the game.
|
||||
* @default `Escape`
|
||||
*/
|
||||
var ?PAUSE:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for advancing cutscenes.
|
||||
* @default `Z` and `Space` and `Enter`
|
||||
*/
|
||||
var ?CUTSCENE_ADVANCE:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for skipping a cutscene.
|
||||
* @default `Escape`
|
||||
*/
|
||||
var ?CUTSCENE_SKIP:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for increasing volume.
|
||||
* @default `Plus`
|
||||
*/
|
||||
var ?VOLUME_UP:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for decreasing volume.
|
||||
* @default `Minus`
|
||||
*/
|
||||
var ?VOLUME_DOWN:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for muting/unmuting volume.
|
||||
* @default `Zero`
|
||||
*/
|
||||
var ?VOLUME_MUTE:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for restarting a song.
|
||||
* @default `R`
|
||||
*/
|
||||
var ?RESET:Array<Int>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An anonymous structure containing all the user's options and preferences, specific to the Chart Editor.
|
||||
*/
|
||||
typedef SaveDataChartEditorOptions = {};
|
52
source/funkin/save/migrator/RawSaveData_v1_0_0.hx
Normal file
52
source/funkin/save/migrator/RawSaveData_v1_0_0.hx
Normal file
|
@ -0,0 +1,52 @@
|
|||
package funkin.save.migrator;
|
||||
|
||||
import thx.semver.Version;
|
||||
|
||||
typedef RawSaveData_v1_0_0 =
|
||||
{
|
||||
var seenVideo:Bool;
|
||||
var mute:Bool;
|
||||
var volume:Float;
|
||||
|
||||
var sessionId:String;
|
||||
|
||||
var songCompletion:Map<String, Float>;
|
||||
|
||||
var songScores:Map<String, Int>;
|
||||
|
||||
var ?controls:
|
||||
{
|
||||
?p1:SavePlayerControlsData_v1_0_0,
|
||||
?p2:SavePlayerControlsData_v1_0_0
|
||||
};
|
||||
var enabledMods:Array<String>;
|
||||
var weeksUnlocked:Array<Bool>;
|
||||
var windowSettings:Array<Bool>;
|
||||
}
|
||||
|
||||
typedef SavePlayerControlsData_v1_0_0 =
|
||||
{
|
||||
var keys:SaveControlsData_v1_0_0;
|
||||
var pad:SaveControlsData_v1_0_0;
|
||||
};
|
||||
|
||||
typedef SaveControlsData_v1_0_0 =
|
||||
{
|
||||
var ?ACCEPT:Array<Int>;
|
||||
var ?BACK:Array<Int>;
|
||||
var ?CUTSCENE_ADVANCE:Array<Int>;
|
||||
var ?CUTSCENE_SKIP:Array<Int>;
|
||||
var ?NOTE_DOWN:Array<Int>;
|
||||
var ?NOTE_LEFT:Array<Int>;
|
||||
var ?NOTE_RIGHT:Array<Int>;
|
||||
var ?NOTE_UP:Array<Int>;
|
||||
var ?PAUSE:Array<Int>;
|
||||
var ?RESET:Array<Int>;
|
||||
var ?UI_DOWN:Array<Int>;
|
||||
var ?UI_LEFT:Array<Int>;
|
||||
var ?UI_RIGHT:Array<Int>;
|
||||
var ?UI_UP:Array<Int>;
|
||||
var ?VOLUME_DOWN:Array<Int>;
|
||||
var ?VOLUME_MUTE:Array<Int>;
|
||||
var ?VOLUME_UP:Array<Int>;
|
||||
};
|
322
source/funkin/save/migrator/SaveDataMigrator.hx
Normal file
322
source/funkin/save/migrator/SaveDataMigrator.hx
Normal file
|
@ -0,0 +1,322 @@
|
|||
package funkin.save.migrator;
|
||||
|
||||
import funkin.save.Save;
|
||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||
import thx.semver.Version;
|
||||
import funkin.util.VersionUtil;
|
||||
|
||||
@:nullSafety
|
||||
class SaveDataMigrator
|
||||
{
|
||||
/**
|
||||
* Migrate from one 2.x version to another.
|
||||
*/
|
||||
public static function migrate(inputData:Dynamic):Save
|
||||
{
|
||||
// This deserializes directly into a `Version` object, not a `String`.
|
||||
var version:Null<Version> = inputData?.version ?? null;
|
||||
|
||||
if (version == null)
|
||||
{
|
||||
trace('[SAVE] No version found in save data! Returning blank data.');
|
||||
trace(inputData);
|
||||
return new Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (VersionUtil.validateVersionStr(version, Save.SAVE_DATA_VERSION_RULE))
|
||||
{
|
||||
// Simply cast the structured data.
|
||||
var save:Save = inputData;
|
||||
return save;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Invalid save data version! Returning blank data.');
|
||||
trace(inputData);
|
||||
return new Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate from 1.x to the latest version.
|
||||
*/
|
||||
public static function migrateFromLegacy(inputData:Dynamic):Save
|
||||
{
|
||||
var inputSaveData:RawSaveData_v1_0_0 = cast inputData;
|
||||
|
||||
var result:Save = new Save();
|
||||
|
||||
result.volume = inputSaveData.volume;
|
||||
result.mute = inputSaveData.mute;
|
||||
|
||||
result.ngSessionId = inputSaveData.sessionId;
|
||||
|
||||
// TODO: Port over the save data from the legacy save data format.
|
||||
migrateLegacyScores(result, inputSaveData);
|
||||
|
||||
migrateLegacyControls(result, inputSaveData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function migrateLegacyScores(result:Save, inputSaveData:RawSaveData_v1_0_0):Void
|
||||
{
|
||||
if (inputSaveData.songCompletion == null)
|
||||
{
|
||||
inputSaveData.songCompletion = [];
|
||||
}
|
||||
|
||||
if (inputSaveData.songScores == null)
|
||||
{
|
||||
inputSaveData.songScores = [];
|
||||
}
|
||||
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week0');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week1');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week2');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week3');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week4');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week5');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week6');
|
||||
migrateLegacyLevelScore(result, inputSaveData, 'week7');
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['tutorial', 'Tutorial']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['bopeebo', 'Bopeebo']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['fresh', 'Fresh']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['dadbattle', 'Dadbattle']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['monster', 'Monster']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['south', 'South']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['spookeez', 'Spookeez']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['pico', 'Pico']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['philly-nice', 'Philly', 'philly', 'Philly-Nice']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['blammed', 'Blammed']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['satin-panties', 'Satin-Panties']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['high', 'High']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['milf', 'Milf', 'MILF']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['cocoa', 'Cocoa']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['eggnog', 'Eggnog']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['winter-horrorland', 'Winter-Horrorland']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['senpai', 'Senpai']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['roses', 'Roses']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['thorns', 'Thorns']);
|
||||
|
||||
migrateLegacySongScore(result, inputSaveData, ['ugh', 'Ugh']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['guns', 'Guns']);
|
||||
migrateLegacySongScore(result, inputSaveData, ['stress', 'Stress']);
|
||||
}
|
||||
|
||||
static function migrateLegacyLevelScore(result:Save, inputSaveData:RawSaveData_v1_0_0, levelId:String):Void
|
||||
{
|
||||
var scoreDataEasy:SaveScoreData =
|
||||
{
|
||||
score: inputSaveData.songScores.get('${levelId}-easy') ?? 0,
|
||||
accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0,
|
||||
tallies:
|
||||
{
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
};
|
||||
result.setLevelScore(levelId, 'easy', scoreDataEasy);
|
||||
|
||||
var scoreDataNormal:SaveScoreData =
|
||||
{
|
||||
score: inputSaveData.songScores.get('${levelId}') ?? 0,
|
||||
accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0,
|
||||
tallies:
|
||||
{
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
};
|
||||
result.setLevelScore(levelId, 'normal', scoreDataNormal);
|
||||
|
||||
var scoreDataHard:SaveScoreData =
|
||||
{
|
||||
score: inputSaveData.songScores.get('${levelId}-hard') ?? 0,
|
||||
accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0,
|
||||
tallies:
|
||||
{
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
};
|
||||
result.setLevelScore(levelId, 'hard', scoreDataHard);
|
||||
}
|
||||
|
||||
static function migrateLegacySongScore(result:Save, inputSaveData:RawSaveData_v1_0_0, songIds:Array<String>):Void
|
||||
{
|
||||
var scoreDataEasy:SaveScoreData =
|
||||
{
|
||||
score: 0,
|
||||
accuracy: 0,
|
||||
tallies:
|
||||
{
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
};
|
||||
|
||||
for (songId in songIds)
|
||||
{
|
||||
scoreDataEasy.score = Std.int(Math.max(scoreDataEasy.score, inputSaveData.songScores.get('${songId}-easy') ?? 0));
|
||||
scoreDataEasy.accuracy = Math.max(scoreDataEasy.accuracy, inputSaveData.songCompletion.get('${songId}-easy') ?? 0.0);
|
||||
}
|
||||
result.setSongScore(songIds[0], 'easy', scoreDataEasy);
|
||||
|
||||
var scoreDataNormal:SaveScoreData =
|
||||
{
|
||||
score: 0,
|
||||
accuracy: 0,
|
||||
tallies:
|
||||
{
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
};
|
||||
|
||||
for (songId in songIds)
|
||||
{
|
||||
scoreDataNormal.score = Std.int(Math.max(scoreDataNormal.score, inputSaveData.songScores.get('${songId}') ?? 0));
|
||||
scoreDataNormal.accuracy = Math.max(scoreDataNormal.accuracy, inputSaveData.songCompletion.get('${songId}') ?? 0.0);
|
||||
}
|
||||
result.setSongScore(songIds[0], 'normal', scoreDataNormal);
|
||||
|
||||
var scoreDataHard:SaveScoreData =
|
||||
{
|
||||
score: 0,
|
||||
accuracy: 0,
|
||||
tallies:
|
||||
{
|
||||
killer: 0,
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
};
|
||||
|
||||
for (songId in songIds)
|
||||
{
|
||||
scoreDataHard.score = Std.int(Math.max(scoreDataHard.score, inputSaveData.songScores.get('${songId}-hard') ?? 0));
|
||||
scoreDataHard.accuracy = Math.max(scoreDataHard.accuracy, inputSaveData.songCompletion.get('${songId}-hard') ?? 0.0);
|
||||
}
|
||||
result.setSongScore(songIds[0], 'hard', scoreDataHard);
|
||||
}
|
||||
|
||||
static function migrateLegacyControls(result:Save, inputSaveData:RawSaveData_v1_0_0):Void
|
||||
{
|
||||
var p1Data = inputSaveData?.controls?.p1;
|
||||
if (p1Data != null)
|
||||
{
|
||||
migrateLegacyPlayerControls(result, 1, p1Data);
|
||||
}
|
||||
|
||||
var p2Data = inputSaveData?.controls?.p2;
|
||||
if (p2Data != null)
|
||||
{
|
||||
migrateLegacyPlayerControls(result, 2, p2Data);
|
||||
}
|
||||
}
|
||||
|
||||
static function migrateLegacyPlayerControls(result:Save, playerId:Int, controlsData:SavePlayerControlsData_v1_0_0):Void
|
||||
{
|
||||
var outputKeyControls:SaveControlsData =
|
||||
{
|
||||
ACCEPT: controlsData?.keys?.ACCEPT ?? null,
|
||||
BACK: controlsData?.keys?.BACK ?? null,
|
||||
CUTSCENE_ADVANCE: controlsData?.keys?.CUTSCENE_ADVANCE ?? null,
|
||||
CUTSCENE_SKIP: controlsData?.keys?.CUTSCENE_SKIP ?? null,
|
||||
NOTE_DOWN: controlsData?.keys?.NOTE_DOWN ?? null,
|
||||
NOTE_LEFT: controlsData?.keys?.NOTE_LEFT ?? null,
|
||||
NOTE_RIGHT: controlsData?.keys?.NOTE_RIGHT ?? null,
|
||||
NOTE_UP: controlsData?.keys?.NOTE_UP ?? null,
|
||||
PAUSE: controlsData?.keys?.PAUSE ?? null,
|
||||
RESET: controlsData?.keys?.RESET ?? null,
|
||||
UI_DOWN: controlsData?.keys?.UI_DOWN ?? null,
|
||||
UI_LEFT: controlsData?.keys?.UI_LEFT ?? null,
|
||||
UI_RIGHT: controlsData?.keys?.UI_RIGHT ?? null,
|
||||
UI_UP: controlsData?.keys?.UI_UP ?? null,
|
||||
VOLUME_DOWN: controlsData?.keys?.VOLUME_DOWN ?? null,
|
||||
VOLUME_MUTE: controlsData?.keys?.VOLUME_MUTE ?? null,
|
||||
VOLUME_UP: controlsData?.keys?.VOLUME_UP ?? null,
|
||||
};
|
||||
|
||||
var outputPadControls:SaveControlsData =
|
||||
{
|
||||
ACCEPT: controlsData?.pad?.ACCEPT ?? null,
|
||||
BACK: controlsData?.pad?.BACK ?? null,
|
||||
CUTSCENE_ADVANCE: controlsData?.pad?.CUTSCENE_ADVANCE ?? null,
|
||||
CUTSCENE_SKIP: controlsData?.pad?.CUTSCENE_SKIP ?? null,
|
||||
NOTE_DOWN: controlsData?.pad?.NOTE_DOWN ?? null,
|
||||
NOTE_LEFT: controlsData?.pad?.NOTE_LEFT ?? null,
|
||||
NOTE_RIGHT: controlsData?.pad?.NOTE_RIGHT ?? null,
|
||||
NOTE_UP: controlsData?.pad?.NOTE_UP ?? null,
|
||||
PAUSE: controlsData?.pad?.PAUSE ?? null,
|
||||
RESET: controlsData?.pad?.RESET ?? null,
|
||||
UI_DOWN: controlsData?.pad?.UI_DOWN ?? null,
|
||||
UI_LEFT: controlsData?.pad?.UI_LEFT ?? null,
|
||||
UI_RIGHT: controlsData?.pad?.UI_RIGHT ?? null,
|
||||
UI_UP: controlsData?.pad?.UI_UP ?? null,
|
||||
VOLUME_DOWN: controlsData?.pad?.VOLUME_DOWN ?? null,
|
||||
VOLUME_MUTE: controlsData?.pad?.VOLUME_MUTE ?? null,
|
||||
VOLUME_UP: controlsData?.pad?.VOLUME_UP ?? null,
|
||||
};
|
||||
|
||||
result.setControls(playerId, Keys, outputKeyControls);
|
||||
result.setControls(playerId, Gamepad(0), outputPadControls);
|
||||
}
|
||||
}
|
|
@ -163,7 +163,17 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
|
|||
|
||||
function onSelect():Void
|
||||
{
|
||||
keyUsedToEnterPrompt = FlxG.keys.firstJustPressed();
|
||||
switch (currentDevice)
|
||||
{
|
||||
case Keys:
|
||||
{
|
||||
keyUsedToEnterPrompt = FlxG.keys.firstJustPressed();
|
||||
}
|
||||
case Gamepad(id):
|
||||
{
|
||||
buttonUsedToEnterPrompt = FlxG.gamepads.getByID(id).firstJustPressedID();
|
||||
}
|
||||
}
|
||||
|
||||
controlGrid.enabled = false;
|
||||
canExit = false;
|
||||
|
@ -204,6 +214,7 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
|
|||
}
|
||||
|
||||
var keyUsedToEnterPrompt:Null<Int> = null;
|
||||
var buttonUsedToEnterPrompt:Null<Int> = null;
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
|
@ -246,19 +257,49 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
|
|||
case Gamepad(id):
|
||||
{
|
||||
var button = FlxG.gamepads.getByID(id).firstJustReleasedID();
|
||||
if (button != NONE && button != keyUsedToEnterPrompt)
|
||||
if (button != NONE && button != buttonUsedToEnterPrompt)
|
||||
{
|
||||
if (button != BACK) onInputSelect(button);
|
||||
closePrompt();
|
||||
}
|
||||
|
||||
var key = FlxG.keys.firstJustReleased();
|
||||
if (key != NONE && key != keyUsedToEnterPrompt)
|
||||
{
|
||||
if (key == ESCAPE)
|
||||
{
|
||||
closePrompt();
|
||||
}
|
||||
else if (key == BACKSPACE)
|
||||
{
|
||||
onInputSelect(NONE);
|
||||
closePrompt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var keyJustReleased:Int = FlxG.keys.firstJustReleased();
|
||||
if (keyJustReleased != NONE && keyJustReleased == keyUsedToEnterPrompt)
|
||||
switch (currentDevice)
|
||||
{
|
||||
keyUsedToEnterPrompt = null;
|
||||
case Keys:
|
||||
{
|
||||
var keyJustReleased:Int = FlxG.keys.firstJustReleased();
|
||||
if (keyJustReleased != NONE && keyJustReleased == keyUsedToEnterPrompt)
|
||||
{
|
||||
keyUsedToEnterPrompt = null;
|
||||
}
|
||||
buttonUsedToEnterPrompt = null;
|
||||
}
|
||||
case Gamepad(id):
|
||||
{
|
||||
var buttonJustReleased:Int = FlxG.gamepads.getByID(id).firstJustReleasedID();
|
||||
if (buttonJustReleased != NONE && buttonJustReleased == buttonUsedToEnterPrompt)
|
||||
{
|
||||
buttonUsedToEnterPrompt = null;
|
||||
}
|
||||
keyUsedToEnterPrompt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,17 +3,16 @@ package funkin.ui;
|
|||
import flixel.FlxCamera;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import funkin.ui.AtlasText.AtlasFont;
|
||||
import funkin.ui.OptionsState.Page;
|
||||
import funkin.ui.TextMenuList.TextMenuItem;
|
||||
|
||||
class PreferencesMenu extends Page
|
||||
{
|
||||
public static var preferences:Map<String, Dynamic> = new Map();
|
||||
|
||||
var items:TextMenuList;
|
||||
var preferenceItems:FlxTypedSpriteGroup<FlxSprite>;
|
||||
|
||||
var checkboxes:Array<CheckboxThingie> = [];
|
||||
var menuCamera:FlxCamera;
|
||||
var camFollow:FlxObject;
|
||||
|
||||
|
@ -27,13 +26,9 @@ class PreferencesMenu extends Page
|
|||
camera = menuCamera;
|
||||
|
||||
add(items = new TextMenuList());
|
||||
add(preferenceItems = new FlxTypedSpriteGroup<FlxSprite>());
|
||||
|
||||
createPrefItem('naughtyness', 'censor-naughty', true);
|
||||
createPrefItem('downscroll', 'downscroll', false);
|
||||
createPrefItem('flashing menu', 'flashing-menu', true);
|
||||
createPrefItem('Camera Zooming on Beat', 'camera-zoom', true);
|
||||
createPrefItem('FPS Counter', 'fps-counter', true);
|
||||
createPrefItem('Auto Pause', 'auto-pause', false);
|
||||
createPrefItems();
|
||||
|
||||
camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70);
|
||||
if (items != null) camFollow.y = items.selectedItem.y;
|
||||
|
@ -48,128 +43,63 @@ class PreferencesMenu extends Page
|
|||
});
|
||||
}
|
||||
|
||||
public static function getPref(pref:String):Dynamic
|
||||
/**
|
||||
* Create the menu items for each of the preferences.
|
||||
*/
|
||||
function createPrefItems():Void
|
||||
{
|
||||
return preferences.get(pref);
|
||||
createPrefItemCheckbox('Naughtyness', 'Toggle displaying raunchy content', function(value:Bool):Void {
|
||||
Preferences.naughtyness = value;
|
||||
}, Preferences.naughtyness);
|
||||
createPrefItemCheckbox('Downscroll', 'Enable to make notes move downwards', function(value:Bool):Void {
|
||||
Preferences.downscroll = value;
|
||||
}, Preferences.downscroll);
|
||||
createPrefItemCheckbox('Flashing Lights', 'Disable to dampen flashing effects', function(value:Bool):Void {
|
||||
Preferences.flashingLights = value;
|
||||
}, Preferences.flashingLights);
|
||||
createPrefItemCheckbox('Camera Zooming on Beat', 'Disable to stop the camera bouncing to the song', function(value:Bool):Void {
|
||||
Preferences.zoomCamera = value;
|
||||
}, Preferences.zoomCamera);
|
||||
createPrefItemCheckbox('Debug Display', 'Enable to show FPS and other debug stats', function(value:Bool):Void {
|
||||
Preferences.debugDisplay = value;
|
||||
}, Preferences.debugDisplay);
|
||||
createPrefItemCheckbox('Auto Pause', 'Automatically pause the game when it loses focus', function(value:Bool):Void {
|
||||
Preferences.autoPause = value;
|
||||
}, Preferences.autoPause);
|
||||
}
|
||||
|
||||
// easy shorthand?
|
||||
public static function setPref(pref:String, value:Dynamic):Void
|
||||
function createPrefItemCheckbox(prefName:String, prefDesc:String, onChange:Bool->Void, defaultValue:Bool):Void
|
||||
{
|
||||
preferences.set(pref, value);
|
||||
}
|
||||
var checkbox:CheckboxPreferenceItem = new CheckboxPreferenceItem(0, 120 * (items.length - 1 + 1), defaultValue);
|
||||
|
||||
public static function initPrefs():Void
|
||||
{
|
||||
preferenceCheck('censor-naughty', true);
|
||||
preferenceCheck('downscroll', false);
|
||||
preferenceCheck('flashing-menu', true);
|
||||
preferenceCheck('camera-zoom', true);
|
||||
preferenceCheck('fps-counter', true);
|
||||
preferenceCheck('auto-pause', false);
|
||||
preferenceCheck('master-volume', 1);
|
||||
|
||||
#if muted
|
||||
setPref('master-volume', 0);
|
||||
FlxG.sound.muted = true;
|
||||
#end
|
||||
|
||||
if (!getPref('fps-counter')) FlxG.stage.removeChild(Main.fpsCounter);
|
||||
|
||||
FlxG.autoPause = getPref('auto-pause');
|
||||
}
|
||||
|
||||
function createPrefItem(prefName:String, prefString:String, prefValue:Dynamic):Void
|
||||
{
|
||||
items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() {
|
||||
preferenceCheck(prefString, prefValue);
|
||||
|
||||
switch (Type.typeof(prefValue).getName())
|
||||
{
|
||||
case 'TBool':
|
||||
prefToggle(prefString);
|
||||
|
||||
default:
|
||||
trace('swag');
|
||||
}
|
||||
var value = !checkbox.currentValue;
|
||||
onChange(value);
|
||||
checkbox.currentValue = value;
|
||||
});
|
||||
|
||||
switch (Type.typeof(prefValue).getName())
|
||||
{
|
||||
case 'TBool':
|
||||
createCheckbox(prefString);
|
||||
|
||||
default:
|
||||
trace('swag');
|
||||
}
|
||||
|
||||
trace(Type.typeof(prefValue).getName());
|
||||
}
|
||||
|
||||
function createCheckbox(prefString:String)
|
||||
{
|
||||
var checkbox:CheckboxThingie = new CheckboxThingie(0, 120 * (items.length - 1), preferences.get(prefString));
|
||||
checkboxes.push(checkbox);
|
||||
add(checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes that the preference has already been checked/set?
|
||||
*/
|
||||
function prefToggle(prefName:String)
|
||||
{
|
||||
var daSwap:Bool = preferences.get(prefName);
|
||||
daSwap = !daSwap;
|
||||
preferences.set(prefName, daSwap);
|
||||
checkboxes[items.selectedIndex].daValue = daSwap;
|
||||
trace('toggled? ' + preferences.get(prefName));
|
||||
|
||||
switch (prefName)
|
||||
{
|
||||
case 'fps-counter':
|
||||
if (getPref('fps-counter')) FlxG.stage.addChild(Main.fpsCounter);
|
||||
else
|
||||
FlxG.stage.removeChild(Main.fpsCounter);
|
||||
case 'auto-pause':
|
||||
FlxG.autoPause = getPref('auto-pause');
|
||||
}
|
||||
|
||||
if (prefName == 'fps-counter') {}
|
||||
preferenceItems.add(checkbox);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// menuCamera.followLerp = CoolUtil.camLerpShit(0.05);
|
||||
|
||||
// Indent the selected item.
|
||||
// TODO: Only do this on menu change?
|
||||
items.forEach(function(daItem:TextMenuItem) {
|
||||
if (items.selectedItem == daItem) daItem.x = 150;
|
||||
else
|
||||
daItem.x = 120;
|
||||
});
|
||||
}
|
||||
|
||||
static function preferenceCheck(prefString:String, defaultValue:Dynamic):Void
|
||||
{
|
||||
if (preferences.get(prefString) == null)
|
||||
{
|
||||
// Set the value to default.
|
||||
preferences.set(prefString, defaultValue);
|
||||
trace('Set preference to default: ${prefString} = ${defaultValue}');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Found preference: ${prefString} = ${preferences.get(prefString)}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxThingie extends FlxSprite
|
||||
class CheckboxPreferenceItem extends FlxSprite
|
||||
{
|
||||
public var daValue(default, set):Bool;
|
||||
public var currentValue(default, set):Bool;
|
||||
|
||||
public function new(x:Float, y:Float, daValue:Bool = false)
|
||||
public function new(x:Float, y:Float, defaultValue:Bool = false)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
|
@ -180,7 +110,7 @@ class CheckboxThingie extends FlxSprite
|
|||
setGraphicSize(Std.int(width * 0.7));
|
||||
updateHitbox();
|
||||
|
||||
this.daValue = daValue;
|
||||
this.currentValue = defaultValue;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
|
@ -196,12 +126,17 @@ class CheckboxThingie extends FlxSprite
|
|||
}
|
||||
}
|
||||
|
||||
function set_daValue(value:Bool):Bool
|
||||
function set_currentValue(value:Bool):Bool
|
||||
{
|
||||
if (value) animation.play('checked', true);
|
||||
if (value)
|
||||
{
|
||||
animation.play('checked', true);
|
||||
}
|
||||
else
|
||||
{
|
||||
animation.play('static');
|
||||
}
|
||||
|
||||
return value;
|
||||
return currentValue = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ class StickerSubState extends MusicBeatSubState
|
|||
var daSound:String = FlxG.random.getObject(sounds);
|
||||
FlxG.sound.play(Paths.sound(daSound));
|
||||
|
||||
if (ind == grpStickers.members.length - 1)
|
||||
if (grpStickers == null || ind == grpStickers.members.length - 1)
|
||||
{
|
||||
switchingState = false;
|
||||
close();
|
||||
|
@ -206,6 +206,8 @@ class StickerSubState extends MusicBeatSubState
|
|||
sticker.timing = FlxMath.remapToRange(ind, 0, grpStickers.members.length, 0, 0.9);
|
||||
|
||||
new FlxTimer().start(sticker.timing, _ -> {
|
||||
if (grpStickers == null) return;
|
||||
|
||||
sticker.visible = true;
|
||||
var daSound:String = FlxG.random.getObject(sounds);
|
||||
FlxG.sound.play(Paths.sound(daSound));
|
||||
|
@ -269,10 +271,10 @@ class StickerSubState extends MusicBeatSubState
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.keys.justPressed.ANY)
|
||||
{
|
||||
regenStickers();
|
||||
}
|
||||
// if (FlxG.keys.justPressed.ANY)
|
||||
// {
|
||||
// regenStickers();
|
||||
// }
|
||||
}
|
||||
|
||||
var switchingState:Bool = false;
|
||||
|
|
|
@ -10,7 +10,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
|
|||
super(navControls, wrapMode);
|
||||
}
|
||||
|
||||
public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback, fireInstantly = false)
|
||||
public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void, fireInstantly = false)
|
||||
{
|
||||
var item = new TextMenuItem(x, y, name, font, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
|
@ -20,7 +20,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
|
|||
|
||||
class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
||||
{
|
||||
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback)
|
||||
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void)
|
||||
{
|
||||
super(x, y, new AtlasText(0, 0, name, font), name, callback);
|
||||
setEmptyBackground();
|
||||
|
@ -29,7 +29,7 @@ class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
|||
|
||||
class TextTypedMenuItem<T:AtlasText> extends MenuTypedItem<T>
|
||||
{
|
||||
public function new(x = 0.0, y = 0.0, label:T, name:String, callback)
|
||||
public function new(x = 0.0, y = 0.0, label:T, name:String, ?callback:Void->Void)
|
||||
{
|
||||
super(x, y, label, name, callback);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import openfl.utils.Assets;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.system.FlxSound;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import flixel.system.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Functions for loading audio for the chart editor.
|
||||
|
@ -17,16 +20,18 @@ import haxe.io.Path;
|
|||
class ChartEditorAudioHandler
|
||||
{
|
||||
/**
|
||||
* Loads a vocal track from an absolute file path.
|
||||
* Loads and stores byte data for a vocal track from an absolute file path
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
* @param charKey The character to load the vocal track for.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadVocalsFromPath(state:ChartEditorState, path:Path, charKey:String = 'default'):Bool
|
||||
static function loadVocalsFromPath(state:ChartEditorState, path:Path, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadVocalsFromBytes(state, fileBytes, charKey);
|
||||
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadVocalsFromBytes(state, fileBytes, charId, instId);
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
|
@ -34,137 +39,235 @@ class ChartEditorAudioHandler
|
|||
}
|
||||
|
||||
/**
|
||||
* Load a vocal track for a given song and character and add it to the voices group.
|
||||
* Loads and stores byte data for a vocal track from an asset
|
||||
*
|
||||
* @param path ID of the asset.
|
||||
* @param charKey Character to load the vocal track for.
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadVocalsFromAsset(state:ChartEditorState, path:String, charType:CharacterType = OTHER):Bool
|
||||
static function loadVocalsFromAsset(state:ChartEditorState, path:String, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
var trackData:Null<Bytes> = Assets.getBytes(path);
|
||||
if (trackData != null)
|
||||
{
|
||||
return loadVocalsFromBytes(state, trackData, charId, instId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for a vocal track
|
||||
*
|
||||
* @param bytes The audio byte data.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
*/
|
||||
static function loadVocalsFromBytes(state:ChartEditorState, bytes:Bytes, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
state.audioVocalTrackData.set(trackId, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for an instrumental track from an absolute file path
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstFromPath(state:ChartEditorState, path:Path, instId:String = ''):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadInstFromBytes(state, fileBytes, instId);
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for an instrumental track from an asset
|
||||
*
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstFromAsset(state:ChartEditorState, path:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackData:Null<Bytes> = Assets.getBytes(path);
|
||||
if (trackData != null)
|
||||
{
|
||||
return loadInstFromBytes(state, trackData, instId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for a vocal track
|
||||
*
|
||||
* @param bytes The audio byte data.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
*/
|
||||
static function loadInstFromBytes(state:ChartEditorState, bytes:Bytes, instId:String = ''):Bool
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
state.audioInstTrackData.set(instId, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool
|
||||
{
|
||||
var result:Bool = playInstrumental(state, instId);
|
||||
if (!result) return false;
|
||||
|
||||
stopExistingVocals(state);
|
||||
result = playVocals(state, BF, playerId, instId);
|
||||
if (!result) return false;
|
||||
result = playVocals(state, DAD, opponentId, instId);
|
||||
if (!result) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the Chart Editor to select a specific instrumental track, that is already loaded.
|
||||
*/
|
||||
static function playInstrumental(state:ChartEditorState, instId:String = ''):Bool
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||
var instTrack:Null<FlxSound> = buildFlxSoundFromBytes(instTrackData);
|
||||
if (instTrack == null) return false;
|
||||
|
||||
stopExistingInstrumental(state);
|
||||
state.audioInstTrack = instTrack;
|
||||
state.postLoadInstrumental();
|
||||
return true;
|
||||
}
|
||||
|
||||
static function stopExistingInstrumental(state:ChartEditorState):Void
|
||||
{
|
||||
if (state.audioInstTrack != null)
|
||||
{
|
||||
state.audioInstTrack.stop();
|
||||
state.audioInstTrack.destroy();
|
||||
state.audioInstTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the Chart Editor to select a specific vocal track, that is already loaded.
|
||||
*/
|
||||
static function playVocals(state:ChartEditorState, charType:CharacterType, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||
var vocalTrack:Null<FlxSound> = buildFlxSoundFromBytes(vocalTrackData);
|
||||
|
||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||
|
||||
if (vocalTrack != null)
|
||||
{
|
||||
switch (charType)
|
||||
{
|
||||
case CharacterType.BF:
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
state.audioVocalTrackData.set(state.currentSongCharacterPlayer, Assets.getBytes(path));
|
||||
case CharacterType.DAD:
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
state.audioVocalTrackData.set(state.currentSongCharacterOpponent, Assets.getBytes(path));
|
||||
case BF:
|
||||
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
return true;
|
||||
case DAD:
|
||||
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
return true;
|
||||
case OTHER:
|
||||
state.audioVocalTrackGroup.add(vocalTrack);
|
||||
return true;
|
||||
default:
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack);
|
||||
state.audioVocalTrackData.set('default', Assets.getBytes(path));
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a vocal track from audio byte data.
|
||||
*/
|
||||
static function loadVocalsFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, charKey:String = ''):Bool
|
||||
static function stopExistingVocals(state:ChartEditorState):Void
|
||||
{
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack);
|
||||
state.audioVocalTrackData.set(charKey, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from an absolute file path, replacing the current instrumental.
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
*
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstrumentalFromPath(state:ChartEditorState, path:Path):Bool
|
||||
{
|
||||
#if sys
|
||||
// Validate file extension.
|
||||
if (path.ext != null && !ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext))
|
||||
if (state.audioVocalTrackGroup != null)
|
||||
{
|
||||
return false;
|
||||
state.audioVocalTrackGroup.clear();
|
||||
}
|
||||
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadInstrumentalFromBytes(state, fileBytes, '${path.file}.${path.ext}');
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from audio byte data, replacing the current instrumental.
|
||||
* @param bytes The audio byte data.
|
||||
* @param fileName The name of the file, if available. Used for notifications.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstrumentalFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, fileName:String = null):Bool
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
|
||||
state.audioInstTrack = FlxG.sound.load(openflSound, 1.0, false);
|
||||
state.audioInstTrack.autoDestroy = false;
|
||||
state.audioInstTrack.pause();
|
||||
|
||||
state.audioInstTrackData = bytes;
|
||||
|
||||
state.postLoadInstrumental();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from an OpenFL asset, replacing the current instrumental.
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstrumentalFromAsset(state:ChartEditorState, path:String):Bool
|
||||
{
|
||||
var instTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
if (instTrack != null)
|
||||
{
|
||||
state.audioInstTrack = instTrack;
|
||||
|
||||
state.audioInstTrackData = Assets.getBytes(path);
|
||||
|
||||
state.postLoadInstrumental();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a sound effect.
|
||||
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
||||
* @param path The path to the sound effect. Use `Paths` to build this.
|
||||
*/
|
||||
public static function playSound(path:String):Void
|
||||
{
|
||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
||||
|
||||
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
||||
if (asset == null)
|
||||
{
|
||||
trace('WARN: Failed to play sound $path, asset not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
snd.loadEmbedded(asset);
|
||||
snd.autoDestroy = true;
|
||||
FlxG.sound.list.add(snd);
|
||||
snd.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert byte data into a playable sound.
|
||||
*
|
||||
* @param input The byte data.
|
||||
* @return The playable sound, or `null` if loading failed.
|
||||
*/
|
||||
public static function buildFlxSoundFromBytes(input:Null<Bytes>):Null<FlxSound>
|
||||
{
|
||||
if (input == null) return null;
|
||||
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length);
|
||||
var output:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
return output;
|
||||
}
|
||||
|
||||
static function makeZIPEntriesFromInstrumentals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
||||
for (key in state.audioInstTrackData.keys())
|
||||
{
|
||||
if (key == 'default')
|
||||
{
|
||||
var data:Null<Bytes> = state.audioInstTrackData.get('default');
|
||||
if (data == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', data));
|
||||
}
|
||||
else
|
||||
{
|
||||
var data:Null<Bytes> = state.audioInstTrackData.get(key);
|
||||
if (data == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst-${key}.ogg', data));
|
||||
}
|
||||
}
|
||||
|
||||
return zipEntries;
|
||||
}
|
||||
|
||||
static function makeZIPEntriesFromVocals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
||||
for (key in state.audioVocalTrackData.keys())
|
||||
{
|
||||
var data:Null<Bytes> = state.audioVocalTrackData.get(key);
|
||||
if (data == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-${key}.ogg', data));
|
||||
}
|
||||
|
||||
return zipEntries;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
@ -760,6 +762,22 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||
{
|
||||
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||
|
||||
if (currentClipboard.valid != true)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failed to Paste',
|
||||
body: 'Could not parse clipboard contents.',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
return;
|
||||
}
|
||||
|
||||
trace(currentClipboard.notes);
|
||||
|
||||
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
|
||||
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
|
||||
|
||||
|
@ -773,6 +791,16 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||
state.notePreviewDirty = true;
|
||||
|
||||
state.sortChartData();
|
||||
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Paste Successful',
|
||||
body: 'Successfully pasted clipboard contents.',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
|
|
|
@ -83,7 +83,7 @@ class ChartEditorDialogHandler
|
|||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
||||
if (dialog == null) throw 'Could not locate Welcome dialog';
|
||||
|
||||
// Add handlers to the "Create From Song" section.
|
||||
// Create New Song "Easy/Normal/Hard"
|
||||
var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasic', Link);
|
||||
if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog';
|
||||
linkCreateBasic.onClick = function(_event) {
|
||||
|
@ -94,7 +94,20 @@ class ChartEditorDialogHandler
|
|||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
openCreateSongWizard(state, false);
|
||||
openCreateSongWizardBasic(state, false);
|
||||
}
|
||||
|
||||
// Create New Song "Erect/Nightmare"
|
||||
var linkCreateErect:Null<Link> = dialog.findComponent('splashCreateFromSongErect', Link);
|
||||
if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongErect link in Welcome dialog';
|
||||
linkCreateErect.onClick = function(_event) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
openCreateSongWizardErect(state, false);
|
||||
}
|
||||
|
||||
var linkImportChartLegacy:Null<Link> = dialog.findComponent('splashImportChartLegacy', Link);
|
||||
|
@ -237,34 +250,112 @@ class ChartEditorDialogHandler
|
|||
};
|
||||
}
|
||||
|
||||
public static function openCreateSongWizard(state:ChartEditorState, closable:Bool):Void
|
||||
public static function openCreateSongWizardBasic(state:ChartEditorState, closable:Bool):Void
|
||||
{
|
||||
// Step 1. Upload Instrumental
|
||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||
uploadInstDialog.onDialogClosed = function(_event) {
|
||||
// Step 1. Song Metadata
|
||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Step 2. Song Metadata
|
||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = function(_event) {
|
||||
// Step 2. Upload Instrumental
|
||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||
uploadInstDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Step 3. Upload Vocals
|
||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||
openUploadVocalsDialog(state, false); // var uploadVocalsDialog:Dialog
|
||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
state.switchToCurrentInstrumental();
|
||||
state.postLoadInstrumental();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static function openCreateSongWizardErect(state:ChartEditorState, closable:Bool):Void
|
||||
{
|
||||
// Step 1. Song Metadata
|
||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Step 2. Upload Instrumental
|
||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||
uploadInstDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Step 3. Upload Vocals
|
||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
||||
state.switchToCurrentInstrumental();
|
||||
// Step 4. Song Metadata (Erect)
|
||||
var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, 'erect');
|
||||
songMetadataDialogErect.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Switch to the Erect variation so uploading the instrumental applies properly.
|
||||
state.selectedVariation = 'erect';
|
||||
|
||||
// Step 5. Upload Instrumental (Erect)
|
||||
var uploadInstDialogErect:Dialog = openUploadInstDialog(state, closable);
|
||||
uploadInstDialogErect.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Step 6. Upload Vocals (Erect)
|
||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||
uploadVocalsDialogErect.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
state.switchToCurrentInstrumental();
|
||||
state.postLoadInstrumental();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 5! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 4! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
|
@ -302,6 +393,8 @@ class ChartEditorDialogHandler
|
|||
Cursor.cursorMode = Default;
|
||||
}
|
||||
|
||||
var instId:String = state.currentInstrumentalId;
|
||||
|
||||
var onDropFile:String->Void;
|
||||
|
||||
instrumentalBox.onClick = function(_event) {
|
||||
|
@ -309,14 +402,14 @@ class ChartEditorDialogHandler
|
|||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
if (ChartEditorAudioHandler.loadInstrumentalFromBytes(state, selectedFile.bytes))
|
||||
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.fullPath);
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${selectedFile.name})',
|
||||
body: 'Loaded instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
@ -333,7 +426,7 @@ class ChartEditorDialogHandler
|
|||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load instrumental track (${selectedFile.name})',
|
||||
body: 'Failed to load instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
@ -346,14 +439,14 @@ class ChartEditorDialogHandler
|
|||
onDropFile = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
if (ChartEditorAudioHandler.loadInstrumentalFromPath(state, path))
|
||||
if (ChartEditorAudioHandler.loadInstFromPath(state, path, instId))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${path.file}.${path.ext})',
|
||||
body: 'Loaded instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
@ -370,7 +463,7 @@ class ChartEditorDialogHandler
|
|||
}
|
||||
else
|
||||
{
|
||||
'Failed to load instrumental track (${path.file}.${path.ext})';
|
||||
'Failed to load instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})';
|
||||
}
|
||||
|
||||
// Tell the user the load was successful.
|
||||
|
@ -457,11 +550,18 @@ class ChartEditorDialogHandler
|
|||
* @return The dialog to open.
|
||||
*/
|
||||
@:haxe.warning("-WVarInit")
|
||||
public static function openSongMetadataDialog(state:ChartEditorState):Dialog
|
||||
public static function openSongMetadataDialog(state:ChartEditorState, ?targetVariation:String):Dialog
|
||||
{
|
||||
if (targetVariation == null) targetVariation = Constants.DEFAULT_VARIATION;
|
||||
|
||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
||||
if (dialog == null) throw 'Could not locate Song Metadata dialog';
|
||||
|
||||
if (targetVariation != Constants.DEFAULT_VARIATION)
|
||||
{
|
||||
dialog.title = 'New Chart - Provide Song Metadata (${targetVariation.toTitleCase()})';
|
||||
}
|
||||
|
||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
||||
buttonCancel.onClick = function(_event) {
|
||||
|
@ -574,7 +674,11 @@ class ChartEditorDialogHandler
|
|||
|
||||
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
|
||||
dialogContinue.onClick = (_event) -> dialog.hideDialog(DialogButton.APPLY);
|
||||
dialogContinue.onClick = (_event) -> {
|
||||
state.songMetadata.set(targetVariation, newSongMetadata);
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
@ -587,6 +691,7 @@ class ChartEditorDialogHandler
|
|||
*/
|
||||
public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
||||
{
|
||||
var instId:String = state.currentInstrumentalId;
|
||||
var charIdsForVocals:Array<String> = [];
|
||||
|
||||
var charData:SongCharacterData = state.currentSongMetadata.playData.characters;
|
||||
|
@ -633,14 +738,14 @@ class ChartEditorDialogHandler
|
|||
trace('Selected file: $pathStr');
|
||||
var path:Path = new Path(pathStr);
|
||||
|
||||
if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey))
|
||||
if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey, instId))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded vocal track for $charName (${path.file}.${path.ext})',
|
||||
body: 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
@ -656,21 +761,14 @@ class ChartEditorDialogHandler
|
|||
}
|
||||
else
|
||||
{
|
||||
var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext ?? ''))
|
||||
{
|
||||
'File format (${path.ext}) not supported for vocal track (${path.file}.${path.ext})';
|
||||
}
|
||||
else
|
||||
{
|
||||
'Failed to load vocal track (${path.file}.${path.ext})';
|
||||
}
|
||||
trace('Failed to load vocal track (${path.file}.${path.ext})');
|
||||
|
||||
// Vocals failed to load.
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: message,
|
||||
body: 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
@ -690,14 +788,46 @@ class ChartEditorDialogHandler
|
|||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.name);
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}';
|
||||
#end
|
||||
ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey);
|
||||
dialogNoVocals.hidden = true;
|
||||
removeDropHandler(onDropFile);
|
||||
if (ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey, instId))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}';
|
||||
#end
|
||||
|
||||
dialogNoVocals.hidden = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load vocal track (${selectedFile.fullPath})');
|
||||
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load vocal track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class ChartEditorImportExportHandler
|
|||
for (metadata in rawSongMetadata)
|
||||
{
|
||||
if (metadata == null) continue;
|
||||
var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
|
||||
var variation = (metadata.variation == null || metadata.variation == '') ? Constants.DEFAULT_VARIATION : metadata.variation;
|
||||
|
||||
// Clone to prevent modifying the original.
|
||||
var metadataClone:SongMetadata = metadata.clone(variation);
|
||||
|
@ -52,23 +52,44 @@ class ChartEditorImportExportHandler
|
|||
|
||||
state.clearVocals();
|
||||
|
||||
ChartEditorAudioHandler.loadInstrumentalFromAsset(state, Paths.inst(songId));
|
||||
|
||||
var diff:Null<SongDifficulty> = song.getDifficulty(state.selectedDifficulty);
|
||||
var voiceList:Array<String> = diff != null ? diff.buildVoiceList() : [];
|
||||
if (voiceList.length == 2)
|
||||
var variations:Array<String> = state.availableVariations;
|
||||
for (variation in variations)
|
||||
{
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], BF);
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[1], DAD);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (voicePath in voiceList)
|
||||
if (variation == Constants.DEFAULT_VARIATION)
|
||||
{
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voicePath);
|
||||
ChartEditorAudioHandler.loadInstFromAsset(state, Paths.inst(songId));
|
||||
}
|
||||
else
|
||||
{
|
||||
ChartEditorAudioHandler.loadInstFromAsset(state, Paths.inst(songId, '-$variation'), variation);
|
||||
}
|
||||
}
|
||||
|
||||
for (difficultyId in song.listDifficulties())
|
||||
{
|
||||
var diff:Null<SongDifficulty> = song.getDifficulty(difficultyId);
|
||||
if (diff == null) continue;
|
||||
|
||||
var instId:String = diff.variation == Constants.DEFAULT_VARIATION ? '' : diff.variation;
|
||||
var voiceList:Array<String> = diff.buildVoiceList(); // SongDifficulty accounts for variation already.
|
||||
|
||||
if (voiceList.length == 2)
|
||||
{
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], diff.characters.player, instId);
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[1], diff.characters.opponent, instId);
|
||||
}
|
||||
else if (voiceList.length == 1)
|
||||
{
|
||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], diff.characters.player, instId);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] Strange quantity of voice paths for difficulty ${difficultyId}: ${voiceList.length}');
|
||||
}
|
||||
}
|
||||
|
||||
state.switchToCurrentInstrumental();
|
||||
|
||||
state.refreshMetadataToolbox();
|
||||
|
||||
#if !mac
|
||||
|
@ -148,13 +169,8 @@ class ChartEditorImportExportHandler
|
|||
}
|
||||
}
|
||||
|
||||
if (state.audioInstTrackData != null) zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', state.audioInstTrackData));
|
||||
for (charId in state.audioVocalTrackData.keys())
|
||||
{
|
||||
var entryData = state.audioVocalTrackData.get(charId);
|
||||
if (entryData == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-$charId.ogg', entryData));
|
||||
}
|
||||
if (state.audioInstTrackData != null) zipEntries.concat(ChartEditorAudioHandler.makeZIPEntriesFromInstrumentals(state));
|
||||
if (state.audioVocalTrackData != null) zipEntries.concat(ChartEditorAudioHandler.makeZIPEntriesFromVocals(state));
|
||||
|
||||
trace('Exporting ${zipEntries.length} files to ZIP...');
|
||||
|
||||
|
|
|
@ -461,6 +461,8 @@ class ChartEditorState extends HaxeUIState
|
|||
notePreviewDirty = true;
|
||||
notePreviewViewportBoundsDirty = true;
|
||||
this.scrollPositionInPixels = this.scrollPositionInPixels;
|
||||
// Characters have probably changed too.
|
||||
healthIconsDirty = true;
|
||||
|
||||
return isViewDownscroll;
|
||||
}
|
||||
|
@ -519,8 +521,14 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION;
|
||||
|
||||
/**
|
||||
* Setter called when we are switching variations.
|
||||
* We will likely need to switch instrumentals as well.
|
||||
*/
|
||||
function set_selectedVariation(value:String):String
|
||||
{
|
||||
// Don't update if we're already on the variation.
|
||||
if (selectedVariation == value) return selectedVariation;
|
||||
selectedVariation = value;
|
||||
|
||||
// Make sure view is updated when the variation changes.
|
||||
|
@ -528,6 +536,8 @@ class ChartEditorState extends HaxeUIState
|
|||
notePreviewDirty = true;
|
||||
notePreviewViewportBoundsDirty = true;
|
||||
|
||||
switchToCurrentInstrumental();
|
||||
|
||||
return selectedVariation;
|
||||
}
|
||||
|
||||
|
@ -548,6 +558,23 @@ class ChartEditorState extends HaxeUIState
|
|||
return selectedDifficulty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The instrumental ID which is currently selected.
|
||||
*/
|
||||
var currentInstrumentalId(get, set):String;
|
||||
|
||||
function get_currentInstrumentalId():String
|
||||
{
|
||||
var instId:Null<String> = currentSongMetadata.playData.characters.instrumental;
|
||||
if (instId == null || instId == '') instId = (selectedVariation == Constants.DEFAULT_VARIATION) ? '' : selectedVariation;
|
||||
return instId;
|
||||
}
|
||||
|
||||
function set_currentInstrumentalId(value:String):String
|
||||
{
|
||||
return currentSongMetadata.playData.characters.instrumental = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The character ID for the character which is currently selected.
|
||||
*/
|
||||
|
@ -592,6 +619,11 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
var noteDisplayDirty:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether the selected charactesr have been modified and the health icons need to be updated.
|
||||
*/
|
||||
var healthIconsDirty:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether the note preview graphic needs to be FULLY rebuilt.
|
||||
*/
|
||||
|
@ -773,28 +805,29 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
/**
|
||||
* The audio track for the instrumental.
|
||||
* Replaced when switching instrumentals.
|
||||
* `null` until an instrumental track is loaded.
|
||||
*/
|
||||
var audioInstTrack:Null<FlxSound> = null;
|
||||
|
||||
/**
|
||||
* The raw byte data for the instrumental audio track.
|
||||
* The raw byte data for the instrumental audio tracks.
|
||||
* Key is the instrumental name.
|
||||
* `null` until an instrumental track is loaded.
|
||||
*/
|
||||
var audioInstTrackData:Null<Bytes> = null;
|
||||
var audioInstTrackData:Map<String, Bytes> = [];
|
||||
|
||||
/**
|
||||
* The audio track for the vocals.
|
||||
* `null` until vocal track(s) are loaded.
|
||||
* When switching characters, the elements of the VoicesGroup will be swapped to match the new character.
|
||||
*/
|
||||
var audioVocalTrackGroup:Null<VoicesGroup> = null;
|
||||
|
||||
/**
|
||||
* A map of the audio tracks for each character's vocals.
|
||||
* - Keys are the character IDs.
|
||||
* - Values are the FlxSound objects to play that character's vocals.
|
||||
*
|
||||
* When switching characters, the elements of the VoicesGroup will be swapped to match the new character.
|
||||
* - Keys are `characterId-variation` (with `characterId` being the default variation).
|
||||
* - Values are the byte data for the audio track.
|
||||
*/
|
||||
var audioVocalTrackData:Map<String, Bytes> = [];
|
||||
|
||||
|
@ -1045,30 +1078,6 @@ class ChartEditorState extends HaxeUIState
|
|||
return currentSongMetadata.artist = value;
|
||||
}
|
||||
|
||||
var currentSongCharacterPlayer(get, set):String;
|
||||
|
||||
function get_currentSongCharacterPlayer():String
|
||||
{
|
||||
return currentSongMetadata.playData.characters.player;
|
||||
}
|
||||
|
||||
function set_currentSongCharacterPlayer(value:String):String
|
||||
{
|
||||
return currentSongMetadata.playData.characters.player = value;
|
||||
}
|
||||
|
||||
var currentSongCharacterOpponent(get, set):String;
|
||||
|
||||
function get_currentSongCharacterOpponent():String
|
||||
{
|
||||
return currentSongMetadata.playData.characters.opponent;
|
||||
}
|
||||
|
||||
function set_currentSongCharacterOpponent(value:String):String
|
||||
{
|
||||
return currentSongMetadata.playData.characters.opponent = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* SIGNALS
|
||||
*/
|
||||
|
@ -1379,7 +1388,7 @@ class ChartEditorState extends HaxeUIState
|
|||
gridPlayhead.add(playheadBlock);
|
||||
|
||||
// Character icons.
|
||||
healthIconDad = new HealthIcon(currentSongCharacterOpponent);
|
||||
healthIconDad = new HealthIcon(currentSongMetadata.playData.characters.opponent);
|
||||
healthIconDad.autoUpdate = false;
|
||||
healthIconDad.size.set(0.5, 0.5);
|
||||
healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5);
|
||||
|
@ -1387,7 +1396,7 @@ class ChartEditorState extends HaxeUIState
|
|||
add(healthIconDad);
|
||||
healthIconDad.zIndex = 30;
|
||||
|
||||
healthIconBF = new HealthIcon(currentSongCharacterPlayer);
|
||||
healthIconBF = new HealthIcon(currentSongMetadata.playData.characters.player);
|
||||
healthIconBF.autoUpdate = false;
|
||||
healthIconBF.size.set(0.5, 0.5);
|
||||
healthIconBF.x = gridTiledSprite.x + gridTiledSprite.width + 15;
|
||||
|
@ -1484,6 +1493,12 @@ class ChartEditorState extends HaxeUIState
|
|||
return bounds;
|
||||
}
|
||||
|
||||
public function switchToCurrentInstrumental():Void
|
||||
{
|
||||
ChartEditorAudioHandler.switchToInstrumental(this, currentInstrumentalId, currentSongMetadata.playData.characters.player,
|
||||
currentSongMetadata.playData.characters.opponent);
|
||||
}
|
||||
|
||||
function setNotePreviewViewportBounds(bounds:FlxRect = null):Void
|
||||
{
|
||||
if (notePreviewViewport == null)
|
||||
|
@ -1532,11 +1547,11 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedEvents);
|
||||
renderedNotes.zIndex = 25;
|
||||
renderedEvents.zIndex = 25;
|
||||
|
||||
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedSelectionSquares);
|
||||
renderedNotes.zIndex = 26;
|
||||
renderedSelectionSquares.zIndex = 26;
|
||||
}
|
||||
|
||||
function buildAdditionalUI():Void
|
||||
|
@ -1647,7 +1662,18 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
addUIClickListener('menubarItemCut', _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
addUIClickListener('menubarItemPaste', _ -> performCommand(new PasteItemsCommand(scrollPositionInMs + playheadPositionInMs)));
|
||||
addUIClickListener('menubarItemPaste', _ -> {
|
||||
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
|
||||
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
|
||||
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
|
||||
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
|
||||
performCommand(new PasteItemsCommand(targetSnappedMs));
|
||||
});
|
||||
|
||||
addUIClickListener('menubarItemPasteUnsnapped', _ -> {
|
||||
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
|
||||
performCommand(new PasteItemsCommand(targetMs));
|
||||
});
|
||||
|
||||
addUIClickListener('menubarItemDelete', function(_) {
|
||||
if (currentNoteSelection.length > 0 && currentEventSelection.length > 0)
|
||||
|
@ -1691,6 +1717,7 @@ class ChartEditorState extends HaxeUIState
|
|||
});
|
||||
|
||||
addUIClickListener('menubarItemAbout', _ -> ChartEditorDialogHandler.openAboutDialog(this));
|
||||
addUIClickListener('menubarItemWelcomeDialog', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||
|
||||
addUIClickListener('menubarItemUserGuide', _ -> ChartEditorDialogHandler.openUserGuideDialog(this));
|
||||
|
||||
|
@ -1713,6 +1740,11 @@ class ChartEditorState extends HaxeUIState
|
|||
});
|
||||
setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark);
|
||||
|
||||
addUIClickListener('menubarItemPlayPause', _ -> toggleAudioPlayback());
|
||||
|
||||
addUIClickListener('menubarItemLoadInstrumental', _ -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
|
||||
addUIClickListener('menubarItemLoadVocals', _ -> ChartEditorDialogHandler.openUploadVocalsDialog(this, true));
|
||||
|
||||
addUIChangeListener('menubarItemMetronomeEnabled', event -> isMetronomeEnabled = event.value);
|
||||
setUICheckboxSelected('menubarItemMetronomeEnabled', isMetronomeEnabled);
|
||||
|
||||
|
@ -1726,7 +1758,7 @@ class ChartEditorState extends HaxeUIState
|
|||
if (instVolumeLabel != null)
|
||||
{
|
||||
addUIChangeListener('menubarItemVolumeInstrumental', function(event:UIEvent) {
|
||||
var volume:Float = event?.value ?? 0 / 100.0;
|
||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||
instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||
});
|
||||
|
@ -1736,7 +1768,7 @@ class ChartEditorState extends HaxeUIState
|
|||
if (vocalsVolumeLabel != null)
|
||||
{
|
||||
addUIChangeListener('menubarItemVolumeVocals', function(event:UIEvent) {
|
||||
var volume:Float = event?.value ?? 0 / 100.0;
|
||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||
vocalsVolumeLabel.text = 'Vocals - ${Std.int(event.value)}%';
|
||||
});
|
||||
|
@ -1920,33 +1952,33 @@ class ChartEditorState extends HaxeUIState
|
|||
// Mouse Wheel = Scroll
|
||||
if (FlxG.mouse.wheel != 0 && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
scrollAmount = -10 * FlxG.mouse.wheel;
|
||||
scrollAmount = -50 * FlxG.mouse.wheel;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// Up Arrow = Scroll Up
|
||||
if (upKeyHandler.activated && currentLiveInputStyle == None)
|
||||
{
|
||||
scrollAmount = -GRID_SIZE * 0.25 * 5.0;
|
||||
scrollAmount = -GRID_SIZE * 0.25 * 25.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
// Down Arrow = Scroll Down
|
||||
if (downKeyHandler.activated && currentLiveInputStyle == None)
|
||||
{
|
||||
scrollAmount = GRID_SIZE * 0.25 * 5.0;
|
||||
scrollAmount = GRID_SIZE * 0.25 * 25.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// W = Scroll Up (doesn't work with Ctrl+Scroll)
|
||||
if (wKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
scrollAmount = -GRID_SIZE * 0.25 * 5.0;
|
||||
scrollAmount = -GRID_SIZE * 0.25 * 25.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
// S = Scroll Down (doesn't work with Ctrl+Scroll)
|
||||
if (sKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
scrollAmount = GRID_SIZE * 0.25 * 5.0;
|
||||
scrollAmount = GRID_SIZE * 0.25 * 25.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
|
@ -2011,7 +2043,7 @@ class ChartEditorState extends HaxeUIState
|
|||
// SHIFT + Scroll = Scroll Fast
|
||||
if (FlxG.keys.pressed.SHIFT)
|
||||
{
|
||||
scrollAmount *= 5;
|
||||
scrollAmount *= 2;
|
||||
}
|
||||
// CONTROL + Scroll = Scroll Precise
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
|
@ -2314,7 +2346,6 @@ class ChartEditorState extends HaxeUIState
|
|||
// Scroll up.
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY;
|
||||
scrollPositionInPixels -= diff * 0.5; // Too fast!
|
||||
trace('Scroll up: ' + diff);
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0))
|
||||
|
@ -2322,7 +2353,6 @@ class ChartEditorState extends HaxeUIState
|
|||
// Scroll down.
|
||||
var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0);
|
||||
scrollPositionInPixels += diff * 0.5; // Too fast!
|
||||
trace('Scroll down: ' + diff);
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
|
@ -2947,8 +2977,8 @@ class ChartEditorState extends HaxeUIState
|
|||
// Set the position and size (because we might be recycling one with bad values).
|
||||
selectionSquare.x = noteSprite.x;
|
||||
selectionSquare.y = noteSprite.y;
|
||||
selectionSquare.width = noteSprite.width;
|
||||
selectionSquare.height = noteSprite.height;
|
||||
selectionSquare.width = GRID_SIZE;
|
||||
selectionSquare.height = GRID_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2979,6 +3009,8 @@ class ChartEditorState extends HaxeUIState
|
|||
FlxG.watch.addQuick("tapNotesRendered", renderedNotes.members.length);
|
||||
FlxG.watch.addQuick("holdNotesRendered", renderedHoldNotes.members.length);
|
||||
FlxG.watch.addQuick("eventsRendered", renderedEvents.members.length);
|
||||
FlxG.watch.addQuick("notesSelected", currentNoteSelection.length);
|
||||
FlxG.watch.addQuick("eventsSelected", currentEventSelection.length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2986,6 +3018,12 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function handleHealthIcons():Void
|
||||
{
|
||||
if (healthIconsDirty)
|
||||
{
|
||||
if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player;
|
||||
if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
|
||||
}
|
||||
|
||||
// Right align the BF health icon.
|
||||
if (healthIconBF != null)
|
||||
{
|
||||
|
@ -3002,6 +3040,8 @@ class ChartEditorState extends HaxeUIState
|
|||
if (selectionSquareBitmap == null)
|
||||
throw "ERROR: Tried to build selection square, but selectionSquareBitmap is null! Check ChartEditorThemeHandler.updateSelectionSquare()";
|
||||
|
||||
FlxG.bitmapLog.add(selectionSquareBitmap, "selectionSquareBitmap");
|
||||
|
||||
return new FlxSprite().loadGraphic(selectionSquareBitmap);
|
||||
}
|
||||
|
||||
|
@ -3121,8 +3161,20 @@ class ChartEditorState extends HaxeUIState
|
|||
// CTRL + V = Paste
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.V)
|
||||
{
|
||||
// Paste notes from clipboard, at the playhead.
|
||||
performCommand(new PasteItemsCommand(scrollPositionInMs + playheadPositionInMs));
|
||||
// CTRL + SHIFT + V = Paste Unsnapped.
|
||||
var targetMs:Float = if (FlxG.keys.pressed.SHIFT)
|
||||
{
|
||||
scrollPositionInMs + playheadPositionInMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
|
||||
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
|
||||
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
|
||||
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
|
||||
targetSnappedMs;
|
||||
}
|
||||
performCommand(new PasteItemsCommand(targetMs));
|
||||
}
|
||||
|
||||
// DELETE = Delete
|
||||
|
@ -3287,7 +3339,7 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function handleTestKeybinds():Void
|
||||
{
|
||||
if (!isHaxeUIDialogOpen && FlxG.keys.justPressed.ENTER)
|
||||
if (!isHaxeUIDialogOpen && !isCursorOverHaxeUI && FlxG.keys.justPressed.ENTER)
|
||||
{
|
||||
var minimal = FlxG.keys.pressed.SHIFT;
|
||||
testSongInPlayState(minimal);
|
||||
|
@ -3413,11 +3465,11 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
playerPreviewDirty = false;
|
||||
|
||||
if (currentSongCharacterPlayer != charPlayer.charId)
|
||||
if (currentSongMetadata.playData.characters.player != charPlayer.charId)
|
||||
{
|
||||
if (healthIconBF != null) healthIconBF.characterId = currentSongCharacterPlayer;
|
||||
if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player;
|
||||
|
||||
charPlayer.loadCharacter(currentSongCharacterPlayer);
|
||||
charPlayer.loadCharacter(currentSongMetadata.playData.characters.player);
|
||||
charPlayer.characterType = CharacterType.BF;
|
||||
charPlayer.flip = true;
|
||||
charPlayer.targetScale = 0.5;
|
||||
|
@ -3449,11 +3501,11 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
opponentPreviewDirty = false;
|
||||
|
||||
if (currentSongCharacterOpponent != charPlayer.charId)
|
||||
if (currentSongMetadata.playData.characters.opponent != charPlayer.charId)
|
||||
{
|
||||
if (healthIconDad != null) healthIconDad.characterId = currentSongCharacterOpponent;
|
||||
if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
|
||||
|
||||
charPlayer.loadCharacter(currentSongCharacterOpponent);
|
||||
charPlayer.loadCharacter(currentSongMetadata.playData.characters.opponent);
|
||||
charPlayer.characterType = CharacterType.DAD;
|
||||
charPlayer.flip = false;
|
||||
charPlayer.targetScale = 0.5;
|
||||
|
@ -3833,9 +3885,9 @@ class ChartEditorState extends HaxeUIState
|
|||
switch (noteData.getStrumlineIndex())
|
||||
{
|
||||
case 0: // Player
|
||||
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-09'));
|
||||
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('ui/chart-editor/playerHitsound'));
|
||||
case 1: // Opponent
|
||||
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-010'));
|
||||
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('ui/chart-editor/opponentHitsound'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4072,12 +4124,18 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
buildSpectrogram(audioInstTrack);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] Instrumental track was null!');
|
||||
}
|
||||
|
||||
// Pretty much everything is going to need to be reset.
|
||||
scrollPositionInPixels = 0;
|
||||
playheadPositionInPixels = 0;
|
||||
notePreviewDirty = true;
|
||||
notePreviewViewportBoundsDirty = true;
|
||||
noteDisplayDirty = true;
|
||||
healthIconsDirty = true;
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.ui.story;
|
||||
|
||||
import funkin.save.Save;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -624,7 +626,8 @@ class StoryMenuState extends MusicBeatState
|
|||
tracklistText.screenCenter(X);
|
||||
tracklistText.x -= FlxG.width * 0.35;
|
||||
|
||||
// TODO: Fix this.
|
||||
highScore = Highscore.getWeekScore(0, 0);
|
||||
var levelScore:Null<SaveScoreData> = Save.get().getLevelScore(currentLevelId, currentDifficultyId);
|
||||
highScore = levelScore?.score ?? 0;
|
||||
// levelScore.accuracy
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import flixel.util.FlxColor;
|
|||
import lime.app.Application;
|
||||
import funkin.data.song.SongData.SongTimeFormat;
|
||||
|
||||
/**
|
||||
* A store of unchanging, globally relevant values.
|
||||
*/
|
||||
class Constants
|
||||
{
|
||||
/**
|
||||
|
@ -39,7 +42,7 @@ class Constants
|
|||
*/
|
||||
public static final VERSION_SUFFIX:String = ' PROTOTYPE';
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
static function get_VERSION():String
|
||||
{
|
||||
return 'v${Application.current.meta.get('version')} (${GIT_BRANCH} : ${GIT_HASH})' + VERSION_SUFFIX;
|
||||
|
@ -71,7 +74,7 @@ class Constants
|
|||
*/
|
||||
// ==============================
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
/**
|
||||
* The current Git branch.
|
||||
*/
|
||||
|
|
44
source/funkin/util/FlxGamepadUtil.hx
Normal file
44
source/funkin/util/FlxGamepadUtil.hx
Normal file
|
@ -0,0 +1,44 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import lime.ui.Gamepad as LimeGamepad;
|
||||
import lime.ui.GamepadAxis as LimeGamepadAxis;
|
||||
import lime.ui.GamepadButton as LimeGamepadButton;
|
||||
|
||||
class FlxGamepadUtil
|
||||
{
|
||||
public static function getInputID(gamepad:FlxGamepad, button:LimeGamepadButton):FlxGamepadInputID
|
||||
{
|
||||
#if FLX_GAMEINPUT_API
|
||||
// FLX_GAMEINPUT_API internally assigns 6 axes to IDs 0-5, which LimeGamepadButton doesn't account for, so we need to offset the ID by 6.
|
||||
final OFFSET:Int = 6;
|
||||
#else
|
||||
final OFFSET:Int = 0;
|
||||
#end
|
||||
|
||||
var result:FlxGamepadInputID = gamepad.mapping.getID(button + OFFSET);
|
||||
if (result == NONE) return NONE;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function getLimeGamepad(input:FlxGamepad):Null<LimeGamepad>
|
||||
{
|
||||
#if FLX_GAMEINPUT_API @:privateAccess
|
||||
return input._device.getLimeGamepad();
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
@:privateAccess
|
||||
public static function getFlxGamepadByLimeGamepad(gamepad:LimeGamepad):FlxGamepad
|
||||
{
|
||||
// Why is this so elaborate?
|
||||
@:privateAccess
|
||||
var gameInputDevice:openfl.ui.GameInputDevice = openfl.ui.GameInput.__getDevice(gamepad);
|
||||
@:privateAccess
|
||||
var gamepadIndex:Int = FlxG.gamepads.findGamepadIndex(gameInputDevice);
|
||||
return FlxG.gamepads.getByID(gamepadIndex);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.util.macro;
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
class GitCommit
|
||||
{
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue