mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-23 02:19:46 +00:00
Merge remote-tracking branch 'origin/rewrite/master' into rewrite/feature/remember-difficulty
This commit is contained in:
commit
4a6904d52c
.github
Project.xmlassetshmm.jsonsource
Main.hx
funkin
Controls.hxFreeplayState.hxInitState.hxMainMenuState.hxPauseSubState.hxPlayerSettings.hxPreferences.hx
audiovis
data/song
import.hxinput
play
save
ui
ControlsMenu.hxPreferencesMenu.hxStickerSubState.hxTextMenuList.hx
debug/charting
ChartEditorAudioHandler.hxChartEditorCommand.hxChartEditorDialogHandler.hxChartEditorImportExportHandler.hxChartEditorState.hx
story
util
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 486ea1cdc37a1f1907ba9231b0a1946ff4051f27
|
||||
Subproject commit 15a238b4c59914849df282f9ce2eec5b80030207
|
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"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -85,6 +85,13 @@ 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();
|
||||
|
||||
|
@ -93,15 +100,6 @@ class Main extends Sprite
|
|||
#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);
|
||||
|
|
|
@ -557,7 +557,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;
|
||||
|
@ -616,6 +616,8 @@ 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);
|
||||
|
@ -667,6 +669,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
txtCompletion.text = Math.floor(lerpCompletion * 100) + "%";
|
||||
|
||||
handleInputs(elapsed);
|
||||
}
|
||||
|
||||
function handleInputs(elapsed:Float):Void
|
||||
{
|
||||
if (busy) return;
|
||||
|
||||
var upP = controls.UI_UP_P;
|
||||
var downP = controls.UI_DOWN_P;
|
||||
var accepted = controls.ACCEPT;
|
||||
|
@ -928,7 +937,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
for (song in songs)
|
||||
{
|
||||
if (song == null) return;
|
||||
if (song == null) continue;
|
||||
if (song.songName != actualSongTho)
|
||||
{
|
||||
trace('trying to remove: ' + song.songName);
|
||||
|
@ -937,8 +946,17 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
function capsuleOnConfirmRandom(cap:SongMenuItem):Void
|
||||
{
|
||||
trace("RANDOM SELECTED");
|
||||
|
||||
busy = true;
|
||||
}
|
||||
|
||||
function capsuleOnConfirmDefault(cap:SongMenuItem):Void
|
||||
{
|
||||
busy = true;
|
||||
|
||||
PlayStatePlaylist.isStoryMode = false;
|
||||
|
||||
var songId:String = cap.songTitle.toLowerCase();
|
||||
|
@ -963,6 +981,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,
|
||||
|
|
|
@ -48,7 +48,7 @@ class InitState extends FlxState
|
|||
|
||||
// loadSaveData(); // Moved to Main.hx
|
||||
// Load player options from save data.
|
||||
PreferencesMenu.initPrefs();
|
||||
Preferences.init();
|
||||
// Load controls from save data.
|
||||
PlayerSettings.init();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -16,17 +16,18 @@ class PauseSubState extends MusicBeatSubState
|
|||
{
|
||||
var grpMenuShit:FlxTypedGroup<Alphabet>;
|
||||
|
||||
var pauseOptionsBase:Array<String> = [
|
||||
final pauseOptionsBase:Array<String> = [
|
||||
'Resume',
|
||||
'Restart Song',
|
||||
'Change Difficulty',
|
||||
'Toggle Practice Mode',
|
||||
'Exit to Menu'
|
||||
];
|
||||
final pauseOptionsCharting:Array<String> = ['Resume', 'Restart Song', 'Exit to Chart Editor'];
|
||||
|
||||
var pauseOptionsDifficulty:Array<String> = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK'];
|
||||
final pauseOptionsDifficultyBase:Array<String> = ['BACK'];
|
||||
|
||||
var pauseOptionsCharting:Array<String> = ['Resume', 'Restart Song', 'Exit to Chart Editor'];
|
||||
var pauseOptionsDifficulty:Array<String> = []; // AUTO-POPULATED
|
||||
|
||||
var menuItems:Array<String> = [];
|
||||
var curSelected:Int = 0;
|
||||
|
@ -48,6 +49,12 @@ class PauseSubState extends MusicBeatSubState
|
|||
this.isChartingMode = isChartingMode;
|
||||
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation);
|
||||
trace('DIFFICULTIES: ${difficultiesInVariation}');
|
||||
|
||||
pauseOptionsDifficulty = difficultiesInVariation.map(function(item:String):String {
|
||||
return item.toUpperCase();
|
||||
}).concat(pauseOptionsDifficultyBase);
|
||||
|
||||
if (PlayStatePlaylist.campaignId == 'week6')
|
||||
{
|
||||
|
@ -150,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;
|
||||
|
@ -196,18 +208,6 @@ class PauseSubState extends MusicBeatSubState
|
|||
menuItems = pauseOptionsDifficulty;
|
||||
regenMenu();
|
||||
|
||||
case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT':
|
||||
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
|
||||
|
||||
PlayState.instance.currentDifficulty = daSelected.toLowerCase();
|
||||
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
close();
|
||||
case 'BACK':
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
regenMenu();
|
||||
|
||||
case 'Toggle Practice Mode':
|
||||
PlayState.instance.isPracticeMode = true;
|
||||
practiceText.visible = PlayState.instance.isPracticeMode;
|
||||
|
@ -229,14 +229,43 @@ 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();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
|
||||
case 'BACK':
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
regenMenu();
|
||||
|
||||
default:
|
||||
if (pauseOptionsDifficulty.contains(daSelected))
|
||||
{
|
||||
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
|
||||
|
||||
// Reset campaign score when changing difficulty
|
||||
// So if you switch difficulty on the last song of a week you get a really low overall score.
|
||||
PlayStatePlaylist.campaignScore = 0;
|
||||
PlayStatePlaylist.campaignDifficulty = daSelected.toLowerCase();
|
||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
close();
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] Unhandled pause menu option: ${daSelected}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,11 @@ class PlayerSettings
|
|||
this.id = id;
|
||||
this.controls = new Controls('player$id', None);
|
||||
|
||||
addKeyboard();
|
||||
}
|
||||
|
||||
function addKeyboard():Void
|
||||
{
|
||||
var useDefault = true;
|
||||
if (Save.get().hasControls(id, Keys))
|
||||
{
|
||||
|
@ -96,7 +101,6 @@ class PlayerSettings
|
|||
controls.setKeyboardScheme(Solo);
|
||||
}
|
||||
|
||||
// Apply loaded settings.
|
||||
PreciseInputManager.instance.initializeKeys(controls);
|
||||
}
|
||||
|
||||
|
@ -124,6 +128,7 @@ class PlayerSettings
|
|||
trace("Loading gamepad control scheme");
|
||||
controls.addDefaultGamepad(gamepad.id);
|
||||
}
|
||||
PreciseInputManager.instance.initializeButtons(controls, gamepad);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,15 +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>();
|
||||
|
||||
_buttonList = [];
|
||||
_buttonListMap = [];
|
||||
_buttonListArray = [];
|
||||
_buttonListDir = new Map<Int, Map<FlxGamepadInputID, NoteDirection>>();
|
||||
|
||||
_dirPressTimestamps = new Map<NoteDirection, Int64>();
|
||||
_dirReleaseTimestamps = new Map<NoteDirection, Int64>();
|
||||
|
||||
// Keyboard
|
||||
FlxG.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
|
||||
FlxG.stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
|
||||
FlxG.stage.application.window.onKeyDownPrecise.add(handleKeyDown);
|
||||
|
@ -84,6 +122,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 +187,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 +251,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 +314,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 +330,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 +392,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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -920,7 +920,6 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
// Handle keybinds.
|
||||
// if (!isInCutscene && !disableKeys) keyShit(true);
|
||||
processInputQueue();
|
||||
if (!isInCutscene && !disableKeys) debugKeyShit();
|
||||
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
|
||||
|
@ -1268,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);
|
||||
|
@ -1477,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];
|
||||
|
||||
|
@ -2464,9 +2463,9 @@ class PlayState extends MusicBeatSubState
|
|||
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
||||
};
|
||||
|
||||
if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, currentDifficulty, data))
|
||||
if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
||||
{
|
||||
Save.get().setLevelScore(PlayStatePlaylist.campaignId, currentDifficulty, data);
|
||||
Save.get().setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
||||
#if newgrounds
|
||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||
#end
|
||||
|
@ -2514,7 +2513,7 @@ class PlayState extends MusicBeatSubState
|
|||
var nextPlayState:PlayState = new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: currentDifficulty,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetCharacter: currentPlayerId,
|
||||
});
|
||||
nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
|
||||
|
@ -2530,7 +2529,7 @@ class PlayState extends MusicBeatSubState
|
|||
var nextPlayState:PlayState = new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: currentDifficulty,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetCharacter: currentPlayerId,
|
||||
});
|
||||
nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
|
||||
|
@ -2656,7 +2655,12 @@ class PlayState extends MusicBeatSubState
|
|||
persistentUpdate = false;
|
||||
vocals.stop();
|
||||
camHUD.alpha = 1;
|
||||
var res:ResultState = new ResultState();
|
||||
var res:ResultState = new ResultState(
|
||||
{
|
||||
storyMode: PlayStatePlaylist.isStoryMode,
|
||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||
tallies: Highscore.tallies,
|
||||
});
|
||||
res.camera = camHUD;
|
||||
openSubState(res);
|
||||
}
|
||||
|
|
|
@ -34,10 +34,7 @@ class PlayStatePlaylist
|
|||
*/
|
||||
public static var campaignId:String = 'unknown';
|
||||
|
||||
/**
|
||||
* The current difficulty selected for this level (as a named ID).
|
||||
*/
|
||||
public static var currentDifficulty(default, default):String = Constants.DEFAULT_DIFFICULTY;
|
||||
public static var campaignDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
/**
|
||||
* Resets the playlist to its default state.
|
||||
|
@ -49,6 +46,6 @@ class PlayStatePlaylist
|
|||
campaignScore = 0;
|
||||
campaignTitle = 'UNKNOWN';
|
||||
campaignId = 'unknown';
|
||||
currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
campaignDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import flxanimate.FlxAnimate.Settings;
|
|||
|
||||
class ResultState extends MusicBeatSubState
|
||||
{
|
||||
final params:ResultsStateParams;
|
||||
|
||||
var resultsVariation:ResultVariations;
|
||||
var songName:FlxBitmapText;
|
||||
var difficulty:FlxSprite;
|
||||
|
@ -29,13 +31,18 @@ class ResultState extends MusicBeatSubState
|
|||
var maskShaderSongName = new LeftMaskShader();
|
||||
var maskShaderDifficulty = new LeftMaskShader();
|
||||
|
||||
public function new(params:ResultsStateParams)
|
||||
{
|
||||
super();
|
||||
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
{
|
||||
if (Highscore.tallies.sick == Highscore.tallies.totalNotesHit
|
||||
&& Highscore.tallies.maxCombo == Highscore.tallies.totalNotesHit) resultsVariation = PERFECT;
|
||||
else if (Highscore.tallies.missed
|
||||
+ Highscore.tallies.bad
|
||||
+ Highscore.tallies.shit >= Highscore.tallies.totalNotes * 0.50)
|
||||
if (params.tallies.sick == params.tallies.totalNotesHit
|
||||
&& params.tallies.maxCombo == params.tallies.totalNotesHit) resultsVariation = PERFECT;
|
||||
else if (params.tallies.missed + params.tallies.bad + params.tallies.shit >= params.tallies.totalNotes * 0.50)
|
||||
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
||||
else
|
||||
resultsVariation = NORMAL;
|
||||
|
@ -135,17 +142,7 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890";
|
||||
songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62)));
|
||||
|
||||
// stole this from PauseSubState, I think eric wrote it!!
|
||||
if (PlayState.instance.currentChart != null)
|
||||
{
|
||||
songName.text += '${PlayState.instance.currentChart.songName}:${PlayState.instance.currentChart.songArtist}';
|
||||
}
|
||||
else
|
||||
{
|
||||
songName.text += PlayState.instance.currentSong.id;
|
||||
}
|
||||
|
||||
songName.text = params.title;
|
||||
songName.letterSpacing = -15;
|
||||
songName.angle = -4.1;
|
||||
add(songName);
|
||||
|
@ -194,27 +191,27 @@ class ResultState extends MusicBeatSubState
|
|||
var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
|
||||
add(ratingGrp);
|
||||
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, Highscore.tallies.totalNotesHit);
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit);
|
||||
ratingGrp.add(totalHit);
|
||||
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, Highscore.tallies.maxCombo);
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.tallies.maxCombo);
|
||||
ratingGrp.add(maxCombo);
|
||||
|
||||
hStuf += 2;
|
||||
var extraYOffset:Float = 5;
|
||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, Highscore.tallies.sick, 0xFF89E59E);
|
||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.tallies.sick, 0xFF89E59E);
|
||||
ratingGrp.add(tallySick);
|
||||
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, Highscore.tallies.good, 0xFF89C9E5);
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5);
|
||||
ratingGrp.add(tallyGood);
|
||||
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, Highscore.tallies.bad, 0xffE6CF8A);
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xffE6CF8A);
|
||||
ratingGrp.add(tallyBad);
|
||||
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, Highscore.tallies.shit, 0xFFE68C8A);
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A);
|
||||
ratingGrp.add(tallyShit);
|
||||
|
||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, Highscore.tallies.missed, 0xFFC68AE6);
|
||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.tallies.missed, 0xFFC68AE6);
|
||||
ratingGrp.add(tallyMissed);
|
||||
|
||||
for (ind => rating in ratingGrp.members)
|
||||
|
@ -275,7 +272,7 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
});
|
||||
|
||||
if (Highscore.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
|
||||
if (params.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
@ -351,7 +348,7 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
if (controls.PAUSE)
|
||||
{
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
if (params.storyMode)
|
||||
{
|
||||
FlxG.switchState(new StoryMenuState());
|
||||
}
|
||||
|
@ -372,3 +369,21 @@ enum abstract ResultVariations(String)
|
|||
var NORMAL;
|
||||
var SHIT;
|
||||
}
|
||||
|
||||
typedef ResultsStateParams =
|
||||
{
|
||||
/**
|
||||
* True if results are for a level, false if results are for a single song.
|
||||
*/
|
||||
var storyMode:Bool;
|
||||
|
||||
/**
|
||||
* Either "Song Name by Artist Name" or "Week Name"
|
||||
*/
|
||||
var title:String;
|
||||
|
||||
/**
|
||||
* The score, accuracy, and judgements.
|
||||
*/
|
||||
var tallies:Highscore.Tallies;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -56,8 +56,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
*/
|
||||
public var validScore:Bool = true;
|
||||
|
||||
var difficultyIds:Array<String>;
|
||||
|
||||
public var songName(get, never):String;
|
||||
|
||||
function get_songName():String
|
||||
|
@ -85,7 +83,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
this.id = id;
|
||||
|
||||
variations = [];
|
||||
difficultyIds = [];
|
||||
difficulties = new Map<String, SongDifficulty>();
|
||||
|
||||
_data = _fetchData(id);
|
||||
|
@ -127,8 +124,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
for (vari in variations)
|
||||
result.variations.push(vari);
|
||||
|
||||
result.difficultyIds.clear();
|
||||
|
||||
result.difficulties.clear();
|
||||
result.populateDifficulties();
|
||||
|
||||
for (variation => chartData in charts)
|
||||
|
@ -162,8 +158,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
// but all the difficulties in the metadata must be in the chart file.
|
||||
for (diffId in metadata.playData.difficulties)
|
||||
{
|
||||
difficultyIds.pushUnique(diffId);
|
||||
|
||||
var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
|
||||
|
||||
variations.push(metadata.variation);
|
||||
|
@ -237,19 +231,62 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
*/
|
||||
public inline function getDifficulty(?diffId:String):Null<SongDifficulty>
|
||||
{
|
||||
if (diffId == null) diffId = difficulties.keys().array()[0];
|
||||
if (diffId == null) diffId = listDifficulties()[0];
|
||||
|
||||
return difficulties.get(diffId);
|
||||
}
|
||||
|
||||
public function listDifficulties():Array<String>
|
||||
/**
|
||||
* List all the difficulties in this song.
|
||||
* @param variationId Optionally filter by variation.
|
||||
* @return The list of difficulties.
|
||||
*/
|
||||
public function listDifficulties(?variationId:String):Array<String>
|
||||
{
|
||||
return difficultyIds;
|
||||
if (variationId == '') variationId = null;
|
||||
|
||||
var diffFiltered:Array<String> = difficulties.keys().array().filter(function(diffId:String):Bool {
|
||||
if (variationId == null) return true;
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null) return false;
|
||||
return difficulty.variation == variationId;
|
||||
});
|
||||
|
||||
// sort the difficulties, since they may be out of order in the chart JSON
|
||||
// maybe be careful of lowercase/uppercase?
|
||||
// also used in Level.listDifficulties()!!
|
||||
var diffMap:Map<String, Int> = new Map<String, Int>();
|
||||
for (difficulty in diffFiltered)
|
||||
{
|
||||
var num:Int = 0;
|
||||
switch (difficulty)
|
||||
{
|
||||
case 'easy':
|
||||
num = 0;
|
||||
case 'normal':
|
||||
num = 1;
|
||||
case 'hard':
|
||||
num = 2;
|
||||
case 'erect':
|
||||
num = 3;
|
||||
case 'nightmare':
|
||||
num = 4;
|
||||
}
|
||||
diffMap.set(difficulty, num);
|
||||
}
|
||||
|
||||
diffFiltered.sort(function(a:String, b:String) {
|
||||
return (diffMap.get(a) ?? 0) - (diffMap.get(b) ?? 0);
|
||||
});
|
||||
|
||||
return diffFiltered;
|
||||
}
|
||||
|
||||
public function hasDifficulty(diffId:String):Bool
|
||||
public function hasDifficulty(diffId:String, ?variationId:String):Bool
|
||||
{
|
||||
return difficulties.exists(diffId);
|
||||
if (variationId == '') variationId = null;
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
return variationId == null ? (difficulty != null) : (difficulty != null && difficulty.variation == variationId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,10 +63,10 @@ abstract Save(RawSaveData)
|
|||
// Reasonable defaults.
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingMenu: true,
|
||||
flashingLights: true,
|
||||
zoomCamera: true,
|
||||
debugDisplay: false,
|
||||
pauseOnTabOut: true,
|
||||
autoPause: true,
|
||||
|
||||
controls:
|
||||
{
|
||||
|
@ -88,7 +88,7 @@ abstract Save(RawSaveData)
|
|||
{
|
||||
// No mods enabled.
|
||||
enabledMods: [],
|
||||
modSettings: [],
|
||||
modOptions: [],
|
||||
},
|
||||
|
||||
optionsChartEditor:
|
||||
|
@ -98,6 +98,20 @@ abstract Save(RawSaveData)
|
|||
};
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
@ -458,7 +472,7 @@ typedef SaveHighScoresData =
|
|||
typedef SaveDataMods =
|
||||
{
|
||||
var enabledMods:Array<String>;
|
||||
var modSettings:Map<String, Dynamic>;
|
||||
var modOptions:Map<String, Dynamic>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -530,10 +544,10 @@ typedef SaveDataOptions =
|
|||
var downscroll:Bool;
|
||||
|
||||
/**
|
||||
* If disabled, the main menu won't flash when entering a submenu.
|
||||
* If disabled, flashing lights in the main menu and other areas will be less intense.
|
||||
* @default `true`
|
||||
*/
|
||||
var flashingMenu:Bool;
|
||||
var flashingLights:Bool;
|
||||
|
||||
/**
|
||||
* If disabled, the camera bump synchronized to the beat.
|
||||
|
@ -551,7 +565,7 @@ typedef SaveDataOptions =
|
|||
* If enabled, the game will automatically pause when tabbing out.
|
||||
* @default `true`
|
||||
*/
|
||||
var pauseOnTabOut:Bool;
|
||||
var autoPause:Bool;
|
||||
|
||||
var controls:
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ import openfl.geom.Matrix;
|
|||
import openfl.display.Sprite;
|
||||
import openfl.display.Bitmap;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
||||
class StickerSubState extends MusicBeatSubState
|
||||
{
|
||||
public var grpStickers:FlxTypedGroup<StickerSprite>;
|
||||
|
@ -26,10 +29,60 @@ class StickerSubState extends MusicBeatSubState
|
|||
|
||||
var nextState:NEXTSTATE = FREEPLAY;
|
||||
|
||||
// what "folders" to potentially load from (as of writing only "keys" exist)
|
||||
var soundSelections:Array<String> = [];
|
||||
// what "folder" was randomly selected
|
||||
var soundSelection:String = "";
|
||||
var sounds:Array<String> = [];
|
||||
|
||||
public function new(?oldStickers:Array<StickerSprite>, ?nextState:NEXTSTATE = FREEPLAY):Void
|
||||
{
|
||||
super();
|
||||
|
||||
// todo still
|
||||
// make sure that ONLY plays mp3/ogg files
|
||||
// if there's no mp3/ogg file, then it regenerates/reloads the random folder
|
||||
|
||||
var assetsInList = openfl.utils.Assets.list();
|
||||
|
||||
var soundFilterFunc = function(a:String) {
|
||||
return a.startsWith('assets/shared/sounds/stickersounds/');
|
||||
};
|
||||
|
||||
soundSelections = assetsInList.filter(soundFilterFunc);
|
||||
soundSelections = soundSelections.map(function(a:String) {
|
||||
return a.replace('assets/shared/sounds/stickersounds/', '').split('/')[0];
|
||||
});
|
||||
|
||||
// cracked cleanup... yuchh...
|
||||
for (i in soundSelections)
|
||||
{
|
||||
while (soundSelections.contains(i))
|
||||
{
|
||||
soundSelections.remove(i);
|
||||
}
|
||||
soundSelections.push(i);
|
||||
}
|
||||
|
||||
trace(soundSelections);
|
||||
|
||||
soundSelection = FlxG.random.getObject(soundSelections);
|
||||
|
||||
var filterFunc = function(a:String) {
|
||||
return a.startsWith('assets/shared/sounds/stickersounds/' + soundSelection + '/');
|
||||
};
|
||||
var assetsInList3 = openfl.utils.Assets.list();
|
||||
sounds = assetsInList3.filter(filterFunc);
|
||||
for (i in 0...sounds.length)
|
||||
{
|
||||
sounds[i] = sounds[i].replace('assets/shared/sounds/', '');
|
||||
sounds[i] = sounds[i].substring(0, sounds[i].lastIndexOf('.'));
|
||||
}
|
||||
|
||||
trace(sounds);
|
||||
|
||||
// trace(assetsInList);
|
||||
|
||||
this.nextState = nextState;
|
||||
|
||||
grpStickers = new FlxTypedGroup<StickerSprite>();
|
||||
|
@ -66,8 +119,10 @@ class StickerSubState extends MusicBeatSubState
|
|||
{
|
||||
new FlxTimer().start(sticker.timing, _ -> {
|
||||
sticker.visible = false;
|
||||
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();
|
||||
|
@ -151,7 +206,11 @@ 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));
|
||||
|
||||
var frameTimer:Int = FlxG.random.int(0, 2);
|
||||
|
||||
|
@ -212,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
|
||||
|
@ -116,10 +137,10 @@ class ChartEditorImportExportHandler
|
|||
|
||||
/**
|
||||
* @param force Whether to force the export without prompting the user for a file location.
|
||||
* @param tmp If true, save to the temporary directory instead of the local `backup` directory.
|
||||
*/
|
||||
public static function exportAllSongData(state:ChartEditorState, force:Bool = false, tmp:Bool = false):Void
|
||||
public static function exportAllSongData(state:ChartEditorState, force:Bool = false):Void
|
||||
{
|
||||
var tmp = false;
|
||||
var zipEntries:Array<haxe.zip.Entry> = [];
|
||||
|
||||
for (variation in state.availableVariations)
|
||||
|
@ -133,9 +154,9 @@ class ChartEditorImportExportHandler
|
|||
if (variationId == '')
|
||||
{
|
||||
var variationMetadata:Null<SongMetadata> = state.songMetadata.get(variation);
|
||||
if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata.json', SerializerUtil.toJSON(variationMetadata)));
|
||||
if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata.json', variationMetadata.serialize()));
|
||||
var variationChart:Null<SongChartData> = state.songChartData.get(variation);
|
||||
if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart.json', SerializerUtil.toJSON(variationChart)));
|
||||
if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart.json', variationChart.serialize()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -695,6 +727,16 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
var downKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.DOWN);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the W keybind.
|
||||
*/
|
||||
var wKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.W);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the S keybind.
|
||||
*/
|
||||
var sKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.S);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the page-up keybind.
|
||||
*/
|
||||
|
@ -763,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> = [];
|
||||
|
||||
|
@ -1035,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
|
||||
*/
|
||||
|
@ -1369,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);
|
||||
|
@ -1377,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;
|
||||
|
@ -1474,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)
|
||||
|
@ -1522,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
|
||||
|
@ -1609,6 +1634,7 @@ class ChartEditorState extends HaxeUIState
|
|||
addUIClickListener('menubarItemSaveChartAs', _ -> ChartEditorImportExportHandler.exportAllSongData(this));
|
||||
addUIClickListener('menubarItemLoadInst', _ -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
|
||||
addUIClickListener('menubarItemImportChart', _ -> ChartEditorDialogHandler.openImportChartDialog(this, 'legacy', true));
|
||||
addUIClickListener('menubarItemExit', _ -> quitChartEditor());
|
||||
|
||||
addUIClickListener('menubarItemUndo', _ -> undoLastCommand());
|
||||
|
||||
|
@ -1636,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)
|
||||
|
@ -1663,19 +1700,24 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
addUIClickListener('menubarItemSelectNone', _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
// TODO: Implement these.
|
||||
// addUIClickListener('menubarItemSelectRegion', _ -> doSomething());
|
||||
// addUIClickListener('menubarItemSelectBeforeCursor', _ -> doSomething());
|
||||
// addUIClickListener('menubarItemSelectAfterCursor', _ -> doSomething());
|
||||
|
||||
addUIClickListener('menubarItemPlaytestFull', _ -> testSongInPlayState(false));
|
||||
addUIClickListener('menubarItemPlaytestMinimal', _ -> testSongInPlayState(true));
|
||||
|
||||
addUIChangeListener('menubarItemInputStyleGroup', function(event:UIEvent) {
|
||||
trace('Change input style: ${event.target}');
|
||||
addUIClickListener('menuBarItemNoteSnapDecrease', _ -> noteSnapQuantIndex--);
|
||||
addUIClickListener('menuBarItemNoteSnapIncrease', _ -> noteSnapQuantIndex++);
|
||||
|
||||
addUIChangeListener('menuBarItemInputStyleNone', function(event:UIEvent) {
|
||||
currentLiveInputStyle = None;
|
||||
});
|
||||
addUIChangeListener('menuBarItemInputStyleNumberKeys', function(event:UIEvent) {
|
||||
currentLiveInputStyle = NumberKeys;
|
||||
});
|
||||
addUIChangeListener('menuBarItemInputStyleWASD', function(event:UIEvent) {
|
||||
currentLiveInputStyle = WASD;
|
||||
});
|
||||
|
||||
addUIClickListener('menubarItemAbout', _ -> ChartEditorDialogHandler.openAboutDialog(this));
|
||||
addUIClickListener('menubarItemWelcomeDialog', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||
|
||||
addUIClickListener('menubarItemUserGuide', _ -> ChartEditorDialogHandler.openUserGuideDialog(this));
|
||||
|
||||
|
@ -1698,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);
|
||||
|
||||
|
@ -1711,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)}%';
|
||||
});
|
||||
|
@ -1721,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)}%';
|
||||
});
|
||||
|
@ -1769,6 +1816,8 @@ class ChartEditorState extends HaxeUIState
|
|||
add(redoKeyHandler);
|
||||
add(upKeyHandler);
|
||||
add(downKeyHandler);
|
||||
add(wKeyHandler);
|
||||
add(sKeyHandler);
|
||||
add(pageUpKeyHandler);
|
||||
add(pageDownKeyHandler);
|
||||
}
|
||||
|
@ -1795,7 +1844,7 @@ class ChartEditorState extends HaxeUIState
|
|||
// Auto-save to local storage.
|
||||
#else
|
||||
// Auto-save to temp file.
|
||||
ChartEditorImportExportHandler.exportAllSongData(this, true, true);
|
||||
ChartEditorImportExportHandler.exportAllSongData(this, true);
|
||||
#end
|
||||
}
|
||||
|
||||
|
@ -1817,6 +1866,13 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
// Override F4 behavior to include the autosave.
|
||||
if (FlxG.keys.justPressed.F4)
|
||||
{
|
||||
quitChartEditor();
|
||||
return;
|
||||
}
|
||||
|
||||
// dispatchEvent gets called here.
|
||||
super.update(elapsed);
|
||||
|
||||
|
@ -1896,20 +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 != LiveInputStyle.WASD)
|
||||
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 != LiveInputStyle.WASD)
|
||||
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 * 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 * 25.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
|
@ -1974,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)
|
||||
|
@ -2045,14 +2114,17 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
function handleSnap():Void
|
||||
{
|
||||
if (FlxG.keys.justPressed.LEFT && !FlxG.keys.pressed.CONTROL)
|
||||
if (currentLiveInputStyle == None)
|
||||
{
|
||||
noteSnapQuantIndex--;
|
||||
}
|
||||
if (FlxG.keys.justPressed.LEFT && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
noteSnapQuantIndex--;
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.RIGHT && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
noteSnapQuantIndex++;
|
||||
if (FlxG.keys.justPressed.RIGHT && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
noteSnapQuantIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2274,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))
|
||||
|
@ -2282,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();
|
||||
}
|
||||
|
||||
|
@ -2907,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2939,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2946,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)
|
||||
{
|
||||
|
@ -2962,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);
|
||||
}
|
||||
|
||||
|
@ -3032,10 +3112,16 @@ class ChartEditorState extends HaxeUIState
|
|||
// CTRL + Q = Quit to Menu
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q)
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
quitChartEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function quitChartEditor():Void
|
||||
{
|
||||
autoSave();
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keybinds for edit menu items.
|
||||
*/
|
||||
|
@ -3075,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
|
||||
|
@ -3124,13 +3222,17 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function handleViewKeybinds():Void
|
||||
{
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.LEFT)
|
||||
if (currentLiveInputStyle == None)
|
||||
{
|
||||
incrementDifficulty(-1);
|
||||
}
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.RIGHT)
|
||||
{
|
||||
incrementDifficulty(1);
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.LEFT)
|
||||
{
|
||||
incrementDifficulty(-1);
|
||||
}
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.RIGHT)
|
||||
{
|
||||
incrementDifficulty(1);
|
||||
}
|
||||
// Would bind Ctrl+A and Ctrl+D here, but they are already bound to Select All and Select None.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3237,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);
|
||||
|
@ -3363,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;
|
||||
|
@ -3399,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;
|
||||
|
@ -3783,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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3829,25 +3931,26 @@ class ChartEditorState extends HaxeUIState
|
|||
switch (currentLiveInputStyle)
|
||||
{
|
||||
case LiveInputStyle.WASD:
|
||||
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(3);
|
||||
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
|
||||
|
||||
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(7);
|
||||
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
|
||||
case LiveInputStyle.NumberKeys:
|
||||
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(3);
|
||||
// Flipped because Dad is on the left but represents data 0-3.
|
||||
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
|
||||
|
||||
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.SIX) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(6);
|
||||
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(7);
|
||||
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(0);
|
||||
if (FlxG.keys.justPressed.SIX) placeNoteAtPlayhead(1);
|
||||
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
|
||||
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
|
||||
case LiveInputStyle.None:
|
||||
// Do nothing.
|
||||
}
|
||||
|
@ -3856,12 +3959,24 @@ class ChartEditorState extends HaxeUIState
|
|||
function placeNoteAtPlayhead(column:Int):Void
|
||||
{
|
||||
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
|
||||
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / (16 / noteSnapQuant);
|
||||
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
|
||||
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
|
||||
var playheadPosMs:Float = playheadPosStep * Conductor.stepLengthMs * (16 / noteSnapQuant);
|
||||
var playheadPosSnappedMs:Float = playheadPosStep * Conductor.stepLengthMs * noteSnapRatio;
|
||||
|
||||
var newNoteData:SongNoteData = new SongNoteData(playheadPosMs, column, 0, selectedNoteKind);
|
||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
||||
// Look for notes within 1 step of the playhead.
|
||||
var notesAtPos:Array<SongNoteData> = SongDataUtils.getNotesInTimeRange(currentSongChartNoteData, playheadPosSnappedMs,
|
||||
playheadPosSnappedMs + Conductor.stepLengthMs * noteSnapRatio);
|
||||
notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]);
|
||||
|
||||
if (notesAtPos.length == 0)
|
||||
{
|
||||
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, selectedNoteKind);
|
||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Already a note there.');
|
||||
}
|
||||
}
|
||||
|
||||
function set_scrollPositionInPixels(value:Float):Float
|
||||
|
@ -3920,6 +4035,8 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
public function testSongInPlayState(minimal:Bool = false):Void
|
||||
{
|
||||
autoSave();
|
||||
|
||||
var startTimestamp:Float = 0;
|
||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||
|
||||
|
@ -4007,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();
|
||||
}
|
||||
|
||||
|
|
|
@ -155,6 +155,32 @@ class Level implements IRegistryEntry<LevelData>
|
|||
}
|
||||
}
|
||||
|
||||
// sort the difficulties, since they may be out of order in the chart JSON
|
||||
// also copy/pasted to Song.listDifficulties()!
|
||||
var diffMap:Map<String, Int> = new Map<String, Int>();
|
||||
for (difficulty in difficulties)
|
||||
{
|
||||
var num:Int = 0;
|
||||
switch (difficulty)
|
||||
{
|
||||
case 'easy':
|
||||
num = 0;
|
||||
case 'normal':
|
||||
num = 1;
|
||||
case 'hard':
|
||||
num = 2;
|
||||
case 'erect':
|
||||
num = 3;
|
||||
case 'nightmare':
|
||||
num = 4;
|
||||
}
|
||||
diffMap.set(difficulty, num);
|
||||
}
|
||||
|
||||
difficulties.sort(function(a:String, b:String) {
|
||||
return diffMap.get(a) - diffMap.get(b);
|
||||
});
|
||||
|
||||
// Filter to only include difficulties that are present in all songs
|
||||
for (songIndex in 1...songList.length)
|
||||
{
|
||||
|
|
|
@ -544,6 +544,7 @@ class StoryMenuState extends MusicBeatState
|
|||
|
||||
PlayStatePlaylist.campaignId = currentLevel.id;
|
||||
PlayStatePlaylist.campaignTitle = currentLevel.getTitle();
|
||||
PlayStatePlaylist.campaignDifficulty = currentDifficultyId;
|
||||
|
||||
if (targetSong != null)
|
||||
{
|
||||
|
@ -559,7 +560,7 @@ class StoryMenuState extends MusicBeatState
|
|||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: currentDifficultyId,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
}), true);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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