1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-07-14 22:25:49 +00:00

Work in progress for Chart Editor gamepad support (navigation implemented, note placement WIP)

This commit is contained in:
EliteMasterEric 2024-01-06 01:06:10 -05:00
parent e507cbd327
commit 4804b9ae22
10 changed files with 1009 additions and 148 deletions

View file

@ -30,47 +30,31 @@ class Controls extends FlxActionSet
* A list of actions that a player would invoke via some input device.
* Uses FlxActions to funnel various inputs to a single action.
*/
var _ui_up = new FlxActionDigital(Action.UI_UP);
var _ui_left = new FlxActionDigital(Action.UI_LEFT);
var _ui_right = new FlxActionDigital(Action.UI_RIGHT);
var _ui_down = new FlxActionDigital(Action.UI_DOWN);
var _ui_upP = new FlxActionDigital(Action.UI_UP_P);
var _ui_leftP = new FlxActionDigital(Action.UI_LEFT_P);
var _ui_rightP = new FlxActionDigital(Action.UI_RIGHT_P);
var _ui_downP = new FlxActionDigital(Action.UI_DOWN_P);
var _ui_upR = new FlxActionDigital(Action.UI_UP_R);
var _ui_leftR = new FlxActionDigital(Action.UI_LEFT_R);
var _ui_rightR = new FlxActionDigital(Action.UI_RIGHT_R);
var _ui_downR = new FlxActionDigital(Action.UI_DOWN_R);
var _note_up = new FlxActionDigital(Action.NOTE_UP);
var _note_left = new FlxActionDigital(Action.NOTE_LEFT);
var _note_right = new FlxActionDigital(Action.NOTE_RIGHT);
var _note_down = new FlxActionDigital(Action.NOTE_DOWN);
var _note_upP = new FlxActionDigital(Action.NOTE_UP_P);
var _note_leftP = new FlxActionDigital(Action.NOTE_LEFT_P);
var _note_rightP = new FlxActionDigital(Action.NOTE_RIGHT_P);
var _note_downP = new FlxActionDigital(Action.NOTE_DOWN_P);
var _note_upR = new FlxActionDigital(Action.NOTE_UP_R);
var _note_leftR = new FlxActionDigital(Action.NOTE_LEFT_R);
var _note_rightR = new FlxActionDigital(Action.NOTE_RIGHT_R);
var _note_downR = new FlxActionDigital(Action.NOTE_DOWN_R);
var _accept = new FlxActionDigital(Action.ACCEPT);
var _back = new FlxActionDigital(Action.BACK);
var _pause = new FlxActionDigital(Action.PAUSE);
var _reset = new FlxActionDigital(Action.RESET);
var _cutscene_advance = new FlxActionDigital(Action.CUTSCENE_ADVANCE);
var _cutscene_skip = new FlxActionDigital(Action.CUTSCENE_SKIP);
var _debug_menu = new FlxActionDigital(Action.DEBUG_MENU);
var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART);
var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE);
var _volume_up = new FlxActionDigital(Action.VOLUME_UP);
var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN);
var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE);
var _ui_up = new FunkinAction(Action.UI_UP, Action.UI_UP_P, Action.UI_UP_R);
var _ui_left = new FunkinAction(Action.UI_LEFT, Action.UI_LEFT_P, Action.UI_LEFT_R);
var _ui_right = new FunkinAction(Action.UI_RIGHT, Action.UI_RIGHT_P, Action.UI_RIGHT_R);
var _ui_down = new FunkinAction(Action.UI_DOWN, Action.UI_DOWN_P, Action.UI_DOWN_R);
var _note_up = new FunkinAction(Action.NOTE_UP, Action.NOTE_UP_P, Action.NOTE_UP_R);
var _note_left = new FunkinAction(Action.NOTE_LEFT, Action.NOTE_LEFT_P, Action.NOTE_LEFT_R);
var _note_right = new FunkinAction(Action.NOTE_RIGHT, Action.NOTE_RIGHT_P, Action.NOTE_RIGHT_R);
var _note_down = new FunkinAction(Action.NOTE_DOWN, Action.NOTE_DOWN_P, Action.NOTE_DOWN_R);
var _accept = new FunkinAction(Action.ACCEPT);
var _back = new FunkinAction(Action.BACK);
var _pause = new FunkinAction(Action.PAUSE);
var _reset = new FunkinAction(Action.RESET);
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
var _cutscene_skip = new FunkinAction(Action.CUTSCENE_SKIP);
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
var _debug_stage = new FunkinAction(Action.DEBUG_STAGE);
var _volume_up = new FunkinAction(Action.VOLUME_UP);
var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
#if CAN_CHEAT
var _cheat = new FlxActionDigital(Action.CHEAT);
var _cheat = new FunkinAction(Action.CHEAT);
#end
var byName:Map<String, FlxActionDigital> = new Map<String, FlxActionDigital>();
var byName:Map<String, FunkinAction> = new Map<String, FunkinAction>();
public var gamepadsAdded:Array<Int> = [];
public var keyboardScheme = KeyboardScheme.None;
@ -78,122 +62,142 @@ class Controls extends FlxActionSet
public var UI_UP(get, never):Bool;
inline function get_UI_UP()
return _ui_up.check();
return _ui_up.checkPressed();
public var UI_LEFT(get, never):Bool;
inline function get_UI_LEFT()
return _ui_left.check();
return _ui_left.checkPressed();
public var UI_RIGHT(get, never):Bool;
inline function get_UI_RIGHT()
return _ui_right.check();
return _ui_right.checkPressed();
public var UI_DOWN(get, never):Bool;
inline function get_UI_DOWN()
return _ui_down.check();
return _ui_down.checkPressed();
public var UI_UP_P(get, never):Bool;
inline function get_UI_UP_P()
return _ui_upP.check();
return _ui_up.checkJustPressed();
public var UI_LEFT_P(get, never):Bool;
inline function get_UI_LEFT_P()
return _ui_leftP.check();
return _ui_left.checkJustPressed();
public var UI_RIGHT_P(get, never):Bool;
inline function get_UI_RIGHT_P()
return _ui_rightP.check();
return _ui_right.checkJustPressed();
public var UI_DOWN_P(get, never):Bool;
inline function get_UI_DOWN_P()
return _ui_downP.check();
return _ui_down.checkJustPressed();
public var UI_UP_R(get, never):Bool;
inline function get_UI_UP_R()
return _ui_upR.check();
return _ui_up.checkJustReleased();
public var UI_LEFT_R(get, never):Bool;
inline function get_UI_LEFT_R()
return _ui_leftR.check();
return _ui_left.checkJustReleased();
public var UI_RIGHT_R(get, never):Bool;
inline function get_UI_RIGHT_R()
return _ui_rightR.check();
return _ui_right.checkJustReleased();
public var UI_DOWN_R(get, never):Bool;
inline function get_UI_DOWN_R()
return _ui_downR.check();
return _ui_down.checkJustReleased();
public var UI_UP_GAMEPAD(get, never):Bool;
inline function get_UI_UP_GAMEPAD()
return _ui_up.checkPressedGamepad();
public var UI_LEFT_GAMEPAD(get, never):Bool;
inline function get_UI_LEFT_GAMEPAD()
return _ui_left.checkPressedGamepad();
public var UI_RIGHT_GAMEPAD(get, never):Bool;
inline function get_UI_RIGHT_GAMEPAD()
return _ui_right.checkPressedGamepad();
public var UI_DOWN_GAMEPAD(get, never):Bool;
inline function get_UI_DOWN_GAMEPAD()
return _ui_down.checkPressedGamepad();
public var NOTE_UP(get, never):Bool;
inline function get_NOTE_UP()
return _note_up.check();
return _note_up.checkPressed();
public var NOTE_LEFT(get, never):Bool;
inline function get_NOTE_LEFT()
return _note_left.check();
return _note_left.checkPressed();
public var NOTE_RIGHT(get, never):Bool;
inline function get_NOTE_RIGHT()
return _note_right.check();
return _note_right.checkPressed();
public var NOTE_DOWN(get, never):Bool;
inline function get_NOTE_DOWN()
return _note_down.check();
return _note_down.checkPressed();
public var NOTE_UP_P(get, never):Bool;
inline function get_NOTE_UP_P()
return _note_upP.check();
return _note_up.checkJustPressed();
public var NOTE_LEFT_P(get, never):Bool;
inline function get_NOTE_LEFT_P()
return _note_leftP.check();
return _note_left.checkJustPressed();
public var NOTE_RIGHT_P(get, never):Bool;
inline function get_NOTE_RIGHT_P()
return _note_rightP.check();
return _note_right.checkJustPressed();
public var NOTE_DOWN_P(get, never):Bool;
inline function get_NOTE_DOWN_P()
return _note_downP.check();
return _note_down.checkJustPressed();
public var NOTE_UP_R(get, never):Bool;
inline function get_NOTE_UP_R()
return _note_upR.check();
return _note_up.checkJustReleased();
public var NOTE_LEFT_R(get, never):Bool;
inline function get_NOTE_LEFT_R()
return _note_leftR.check();
return _note_left.checkJustReleased();
public var NOTE_RIGHT_R(get, never):Bool;
inline function get_NOTE_RIGHT_R()
return _note_rightR.check();
return _note_right.checkJustReleased();
public var NOTE_DOWN_R(get, never):Bool;
inline function get_NOTE_DOWN_R()
return _note_downR.check();
return _note_down.checkJustReleased();
public var ACCEPT(get, never):Bool;
@ -270,26 +274,10 @@ class Controls extends FlxActionSet
add(_ui_left);
add(_ui_right);
add(_ui_down);
add(_ui_upP);
add(_ui_leftP);
add(_ui_rightP);
add(_ui_downP);
add(_ui_upR);
add(_ui_leftR);
add(_ui_rightR);
add(_ui_downR);
add(_note_up);
add(_note_left);
add(_note_right);
add(_note_down);
add(_note_upP);
add(_note_leftP);
add(_note_rightP);
add(_note_downP);
add(_note_upR);
add(_note_leftR);
add(_note_rightR);
add(_note_downR);
add(_accept);
add(_back);
add(_pause);
@ -303,8 +291,16 @@ class Controls extends FlxActionSet
add(_cheat);
#end
for (action in digitalActions)
byName[action.name] = action;
for (action in digitalActions) {
if (Std.isOfType(action, FunkinAction)) {
var funkinAction:FunkinAction = cast action;
byName[funkinAction.name] = funkinAction;
if (funkinAction.namePressed != null)
byName[funkinAction.namePressed] = funkinAction;
if (funkinAction.nameReleased != null)
byName[funkinAction.nameReleased] = funkinAction;
}
}
if (scheme == null)
scheme = None;
@ -317,14 +313,17 @@ class Controls extends FlxActionSet
super.update();
}
// inline
public function checkByName(name:Action):Bool
public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool
{
#if debug
if (!byName.exists(name))
throw 'Invalid name: $name';
#end
return byName[name].check();
var action = byName[name];
if (gamepadOnly)
return action.checkFiltered(trigger, GAMEPAD);
else
return action.checkFiltered(trigger);
}
public function getKeysForAction(name:Action):Array<FlxKey> {
@ -411,36 +410,36 @@ class Controls extends FlxActionSet
{
case UI_UP:
func(_ui_up, PRESSED);
func(_ui_upP, JUST_PRESSED);
func(_ui_upR, JUST_RELEASED);
func(_ui_up, JUST_PRESSED);
func(_ui_up, JUST_RELEASED);
case UI_LEFT:
func(_ui_left, PRESSED);
func(_ui_leftP, JUST_PRESSED);
func(_ui_leftR, JUST_RELEASED);
func(_ui_left, JUST_PRESSED);
func(_ui_left, JUST_RELEASED);
case UI_RIGHT:
func(_ui_right, PRESSED);
func(_ui_rightP, JUST_PRESSED);
func(_ui_rightR, JUST_RELEASED);
func(_ui_right, JUST_PRESSED);
func(_ui_right, JUST_RELEASED);
case UI_DOWN:
func(_ui_down, PRESSED);
func(_ui_downP, JUST_PRESSED);
func(_ui_downR, JUST_RELEASED);
func(_ui_down, JUST_PRESSED);
func(_ui_down, JUST_RELEASED);
case NOTE_UP:
func(_note_up, PRESSED);
func(_note_upP, JUST_PRESSED);
func(_note_upR, JUST_RELEASED);
func(_note_up, JUST_PRESSED);
func(_note_up, JUST_RELEASED);
case NOTE_LEFT:
func(_note_left, PRESSED);
func(_note_leftP, JUST_PRESSED);
func(_note_leftR, JUST_RELEASED);
func(_note_left, JUST_PRESSED);
func(_note_left, JUST_RELEASED);
case NOTE_RIGHT:
func(_note_right, PRESSED);
func(_note_rightP, JUST_PRESSED);
func(_note_rightR, JUST_RELEASED);
func(_note_right, JUST_PRESSED);
func(_note_right, JUST_RELEASED);
case NOTE_DOWN:
func(_note_down, PRESSED);
func(_note_downP, JUST_PRESSED);
func(_note_downR, JUST_RELEASED);
func(_note_down, JUST_PRESSED);
func(_note_down, JUST_RELEASED);
case ACCEPT:
func(_accept, JUST_PRESSED);
case BACK:
@ -1053,6 +1052,173 @@ typedef Swipes =
?curTouchPos:FlxPoint
};
/**
* An FlxActionDigital with additional functionality, including:
* - Combining `pressed` and `released` inputs into one action.
* - Filtering by input method (`KEYBOARD`, `MOUSE`, `GAMEPAD`, etc).
*/
class FunkinAction extends FlxActionDigital {
public var namePressed(default, null):Null<String>;
public var nameReleased(default, null):Null<String>;
var cache:Map<String, {timestamp:Int, value:Bool}> = [];
public function new(?name:String = "", ?namePressed:String, ?nameReleased:String)
{
super(name);
this.namePressed = namePressed;
this.nameReleased = nameReleased;
}
/**
* Input checks default to whether the input was just pressed, on any input device.
*/
public override function check():Bool {
return checkFiltered(JUST_PRESSED);
}
/**
* Check whether the input is currently being held.
*/
public function checkPressed():Bool {
return checkFiltered(PRESSED);
}
/**
* Check whether the input is currently being held, and was not held last frame.
*/
public function checkJustPressed():Bool {
return checkFiltered(JUST_PRESSED);
}
/**
* Check whether the input is not currently being held.
*/
public function checkReleased():Bool {
return checkFiltered(RELEASED);
}
/**
* Check whether the input is not currently being held, and was held last frame.
*/
public function checkJustReleased():Bool {
return checkFiltered(JUST_RELEASED);
}
/**
* Check whether the input is currently being held by a gamepad device.
*/
public function checkPressedGamepad():Bool {
return checkFiltered(PRESSED, GAMEPAD);
}
/**
* Check whether the input is currently being held by a gamepad device, and was not held last frame.
*/
public function checkJustPressedGamepad():Bool {
return checkFiltered(JUST_PRESSED, GAMEPAD);
}
/**
* Check whether the input is not currently being held by a gamepad device.
*/
public function checkReleasedGamepad():Bool {
return checkFiltered(RELEASED, GAMEPAD);
}
/**
* Check whether the input is not currently being held by a gamepad device, and was held last frame.
*/
public function checkJustReleasedGamepad():Bool {
return checkFiltered(JUST_RELEASED, GAMEPAD);
}
public function checkMultiFiltered(?filterTriggers:Array<FlxInputState>, ?filterDevices:Array<FlxInputDevice>):Bool {
if (filterTriggers == null) {
filterTriggers = [PRESSED, JUST_PRESSED];
}
if (filterDevices == null) {
filterDevices = [];
}
// Perform checkFiltered for each combination.
for (i in filterTriggers) {
if (filterDevices.length == 0) {
if (checkFiltered(i)) {
return true;
}
} else {
for (j in filterDevices) {
if (checkFiltered(i, j)) {
return true;
}
}
}
}
return false;
}
/**
* Performs the functionality of `FlxActionDigital.check()`, but with optional filters.
* @param action The action to check for.
* @param filterTrigger Optionally filter by trigger condition (`JUST_PRESSED`, `PRESSED`, `JUST_RELEASED`, `RELEASED`).
* @param filterDevice Optionally filter by device (`KEYBOARD`, `MOUSE`, `GAMEPAD`, `OTHER`).
*/
public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool {
// The normal
// Make sure we only update the inputs once per frame.
var key = '${filterTrigger}:${filterDevice}';
var cacheEntry = cache.get(key);
if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks) {
return cacheEntry.value;
}
// Use a for loop instead so we can remove inputs while iterating.
// We don't return early because we need to call check() on ALL inputs.
var result = false;
var len = inputs != null ? inputs.length : 0;
for (i in 0...len)
{
var j = len - i - 1;
var input = inputs[j];
// Filter out dead inputs.
if (input.destroyed)
{
inputs.splice(j, 1);
continue;
}
// Update the input.
input.update();
// Check whether the input is the right trigger.
if (filterTrigger != null && input.trigger != filterTrigger) {
continue;
}
// Check whether the input is the right device.
if (filterDevice != null && input.device != filterDevice) {
continue;
}
// Check whether the input has triggered.
if (input.check(this))
{
result = true;
}
}
// We need to cache this result.
cache.set(key, {timestamp: FlxG.game.ticks, value: result});
return result;
}
}
class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
{
var touchMap:Map<Int, Swipes> = new Map();
@ -1242,8 +1408,7 @@ enum Control
#end
}
enum
abstract Action(String) to String from String
enum abstract Action(String) to String from String
{
// NOTE
var NOTE_UP = "note_up";

View file

@ -0,0 +1,111 @@
package funkin.input;
import flixel.input.keyboard.FlxKey;
import flixel.FlxBasic;
import funkin.input.Controls;
import funkin.input.Controls.Action;
/**
* Handles repeating behavior when holding down a control action.
*
* When the `action` is pressed, `activated` will be true for the first frame,
* then wait `delay` seconds before becoming true for one frame every `interval` seconds.
*
* Example: Pressing Ctrl+Z will undo, while holding Ctrl+Z will start to undo repeatedly.
*/
class TurboActionHandler extends FlxBasic
{
/**
* Default delay before repeating.
*/
static inline final DEFAULT_DELAY:Float = 0.4;
/**
* Default interval between repeats.
*/
static inline final DEFAULT_INTERVAL:Float = 0.1;
/**
* Whether the action for this handler is pressed.
*/
public var pressed(get, never):Bool;
/**
* Whether the action for this handler is pressed,
* and the handler is ready to repeat.
*/
public var activated(default, null):Bool = false;
/**
* The Funkin Controls handler.
*/
var controls(get, never):Controls;
function get_controls():Controls
{
return PlayerSettings.player1.controls;
}
var action:Action;
var delay:Float;
var interval:Float;
var gamepadOnly:Bool;
var pressedTime:Float = 0;
function new(action:Action, delay:Float = DEFAULT_DELAY, interval:Float = DEFAULT_INTERVAL, gamepadOnly:Bool = false)
{
super();
this.action = action;
this.delay = delay;
this.interval = interval;
this.gamepadOnly = gamepadOnly;
}
function get_pressed():Bool
{
return controls.check(action, PRESSED, gamepadOnly);
}
public override function update(elapsed:Float):Void
{
super.update(elapsed);
if (pressed)
{
if (pressedTime == 0)
{
activated = true;
}
else if (pressedTime >= (delay + interval))
{
activated = true;
pressedTime -= interval;
}
else
{
activated = false;
}
pressedTime += elapsed;
}
else
{
pressedTime = 0;
activated = false;
}
}
/**
* Builds a TurboActionHandler that monitors from a single key.
* @param inputKey The key to monitor.
* @param delay How long to wait before repeating.
* @param repeatDelay How long to wait between repeats.
* @return A TurboActionHandler
*/
public static overload inline extern function build(action:Action, ?delay:Float = DEFAULT_DELAY, ?interval:Float = DEFAULT_INTERVAL,
?gamepadOnly:Bool = false):TurboActionHandler
{
return new TurboActionHandler(action, delay, interval);
}
}

View file

@ -0,0 +1,127 @@
package funkin.input;
import flixel.input.gamepad.FlxGamepadInputID;
import flixel.input.gamepad.FlxGamepad;
import flixel.FlxBasic;
/**
* Handles repeating behavior when holding down a gamepad button or button combination.
*
* When the `inputs` are pressed, `activated` will be true for the first frame,
* then wait `delay` seconds before becoming true for one frame every `interval` seconds.
*
* Example: Pressing Ctrl+Z will undo, while holding Ctrl+Z will start to undo repeatedly.
*/
class TurboButtonHandler extends FlxBasic
{
/**
* Default delay before repeating.
*/
static inline final DEFAULT_DELAY:Float = 0.4;
/**
* Default interval between repeats.
*/
static inline final DEFAULT_INTERVAL:Float = 0.1;
/**
* Whether all of the keys for this handler are pressed.
*/
public var allPressed(get, never):Bool;
/**
* Whether all of the keys for this handler are activated,
* and the handler is ready to repeat.
*/
public var activated(default, null):Bool = false;
var inputs:Array<FlxGamepadInputID>;
var delay:Float;
var interval:Float;
var targetGamepad:FlxGamepad;
var allPressedTime:Float = 0;
function new(inputs:Array<FlxGamepadInputID>, delay:Float = DEFAULT_DELAY, interval:Float = DEFAULT_INTERVAL, ?targetGamepad:FlxGamepad)
{
super();
this.inputs = inputs;
this.delay = delay;
this.interval = interval;
this.targetGamepad = targetGamepad ?? FlxG.gamepads.firstActive;
}
function get_allPressed():Bool
{
if (targetGamepad == null) return false;
if (inputs == null || inputs.length == 0) return false;
if (inputs.length == 1) return targetGamepad.anyPressed(inputs);
// Check if ANY keys are unpressed
for (input in inputs)
{
if (!targetGamepad.anyPressed([input])) return false;
}
return true;
}
public override function update(elapsed:Float):Void
{
super.update(elapsed);
// Try to find a gamepad if we don't have one
if (targetGamepad == null)
{
targetGamepad = FlxG.gamepads.firstActive;
}
if (allPressed)
{
if (allPressedTime == 0)
{
activated = true;
}
else if (allPressedTime >= (delay + interval))
{
activated = true;
allPressedTime -= interval;
}
else
{
activated = false;
}
allPressedTime += elapsed;
}
else
{
allPressedTime = 0;
activated = false;
}
}
/**
* Builds a TurboButtonHandler that monitors from a single input.
* @param input The input to monitor.
* @param delay How long to wait before repeating.
* @param repeatDelay How long to wait between repeats.
* @return A TurboKeyHandler
*/
public static overload inline extern function build(input:FlxGamepadInputID, ?delay:Float = DEFAULT_DELAY,
?interval:Float = DEFAULT_INTERVAL):TurboButtonHandler
{
return new TurboButtonHandler([input], delay, interval);
}
/**
* Builds a TurboKeyHandler that monitors a key combination.
* @param inputs The combination of inputs to monitor.
* @param delay How long to wait before repeating.
* @param repeatDelay How long to wait between repeats.
* @return A TurboKeyHandler
*/
public static overload inline extern function build(inputs:Array<FlxGamepadInputID>, ?delay:Float = DEFAULT_DELAY,
?interval:Float = DEFAULT_INTERVAL):TurboButtonHandler
{
return new TurboButtonHandler(inputs, delay, interval);
}
}

View file

@ -1,24 +1,25 @@
package funkin.ui.debug.charting;
import funkin.util.logging.CrashHandler;
import haxe.ui.containers.HBox;
import haxe.ui.containers.Grid;
import haxe.ui.containers.ScrollView;
import haxe.ui.containers.menus.MenuBar;
import flixel.addons.display.FlxSliceSprite;
import flixel.addons.display.FlxTiledSprite;
import flixel.addons.transition.FlxTransitionableState;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.FlxSubState;
import flixel.graphics.FlxGraphic;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup;
import flixel.input.gamepad.FlxGamepadInputID;
import flixel.input.keyboard.FlxKey;
import flixel.input.mouse.FlxMouseEvent;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxRect;
import flixel.sound.FlxSound;
import flixel.system.debug.log.LogStyle;
import flixel.system.FlxAssets.FlxSoundAsset;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.tweens.misc.VarTween;
@ -26,19 +27,31 @@ import haxe.ui.Toolkit;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
import flixel.util.FlxTimer;
import funkin.audio.visualize.PolygonSpectogram;
import funkin.audio.VoicesGroup;
import funkin.audio.FunkinSound;
import funkin.audio.visualize.PolygonSpectogram;
import funkin.audio.visualize.PolygonSpectogram;
import funkin.audio.visualize.PolygonVisGroup;
import funkin.audio.VoicesGroup;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongData.SongCharacterData;
import funkin.data.song.SongData.SongCharacterData;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongOffsets;
import funkin.data.song.SongDataUtils;
import funkin.data.song.SongDataUtils;
import funkin.data.song.SongRegistry;
import funkin.data.song.SongRegistry;
import funkin.input.Controls.Action;
import funkin.input.Cursor;
import funkin.input.TurboActionHandler;
import funkin.input.TurboButtonHandler;
import funkin.input.TurboKeyHandler;
import funkin.modding.events.ScriptEvent;
import funkin.play.character.BaseCharacter.CharacterType;
@ -48,20 +61,13 @@ import funkin.play.components.HealthIcon;
import funkin.play.notes.NoteSprite;
import funkin.play.PlayState;
import funkin.play.song.Song;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongRegistry;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongCharacterData;
import funkin.data.song.SongDataUtils;
import funkin.ui.debug.charting.commands.ChartEditorCommand;
import funkin.play.stage.StageData;
import funkin.save.Save;
import funkin.ui.debug.charting.commands.AddEventsCommand;
import funkin.ui.debug.charting.commands.AddNotesCommand;
import funkin.ui.debug.charting.commands.ChartEditorCommand;
import funkin.ui.debug.charting.commands.ChartEditorCommand;
import funkin.ui.debug.charting.commands.ChartEditorCommand;
import funkin.ui.debug.charting.commands.CutItemsCommand;
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
@ -80,17 +86,20 @@ import funkin.ui.debug.charting.commands.SelectItemsCommand;
import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
import funkin.ui.debug.charting.components.ChartEditorEventSprite;
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite;
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
import funkin.ui.haxeui.components.CharacterPlayer;
import funkin.ui.haxeui.HaxeUIState;
import funkin.ui.mainmenu.MainMenuState;
import funkin.util.Constants;
import funkin.util.FileUtil;
import funkin.util.logging.CrashHandler;
import funkin.util.SortUtil;
import funkin.util.WindowUtil;
import haxe.DynamicAccess;
@ -98,22 +107,25 @@ import haxe.io.Bytes;
import haxe.io.Path;
import haxe.ui.backend.flixel.UIRuntimeState;
import haxe.ui.backend.flixel.UIState;
import haxe.ui.components.DropDown;
import haxe.ui.components.Label;
import haxe.ui.components.Button;
import haxe.ui.components.DropDown;
import haxe.ui.components.Image;
import haxe.ui.components.Label;
import haxe.ui.components.NumberStepper;
import haxe.ui.components.Slider;
import haxe.ui.components.TextField;
import haxe.ui.containers.dialogs.CollapsibleDialog;
import haxe.ui.containers.Frame;
import haxe.ui.containers.Grid;
import haxe.ui.containers.HBox;
import haxe.ui.containers.menus.Menu;
import haxe.ui.containers.menus.MenuBar;
import haxe.ui.containers.menus.MenuItem;
import haxe.ui.containers.menus.MenuBar;
import haxe.ui.containers.menus.MenuCheckBox;
import haxe.ui.containers.menus.MenuItem;
import haxe.ui.containers.ScrollView;
import haxe.ui.containers.TreeView;
import haxe.ui.containers.TreeViewNode;
import haxe.ui.components.Image;
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
import haxe.ui.core.Component;
import haxe.ui.core.Screen;
import haxe.ui.events.DragEvent;
@ -122,12 +134,6 @@ import haxe.ui.events.UIEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.focus.FocusManager;
import openfl.display.BitmapData;
import funkin.audio.visualize.PolygonSpectogram;
import flixel.group.FlxGroup.FlxTypedGroup;
import funkin.audio.visualize.PolygonVisGroup;
import flixel.input.mouse.FlxMouseEvent;
import flixel.text.FlxText;
import flixel.system.debug.log.LogStyle;
using Lambda;
@ -360,6 +366,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
}
updatePlayheadGhostHoldNotes();
// Move the rendered notes to the correct position.
renderedNotes.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0);
renderedHoldNotes.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0);
@ -429,6 +437,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Move the playhead sprite to the correct position.
gridPlayhead.y = this.playheadPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
updatePlayheadGhostHoldNotes();
return this.playheadPositionInPixels;
}
@ -720,6 +730,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var currentPlaceNoteData:Null<SongNoteData> = null;
/**
* The SongNoteData which is currently being placed, for each column.
* `null` if the user isn't currently placing a note.
* As the user moves down, we will update this note's sustain length, and finalize the note when they release.
*/
var currentLiveInputPlaceNoteData:Array<SongNoteData> = [];
// Note Movement
/**
@ -750,6 +767,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var dragLengthCurrent:Float = 0;
/**
* The current length of the hold note we are placing with the playhead, in steps.
* Play a sound when this value changes.
*/
var playheadDragLengthCurrent:Array<Float> = [];
/**
* Flip-flop to alternate between two stretching sounds.
*/
@ -1020,6 +1043,66 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var pageDownKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEDOWN);
/**
* Variable used to track how long the user has been holding up on the dpad.
*/
var dpadUpGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_UP);
/**
* Variable used to track how long the user has been holding down on the dpad.
*/
var dpadDownGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_DOWN);
/**
* Variable used to track how long the user has been holding left on the dpad.
*/
var dpadLeftGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_LEFT);
/**
* Variable used to track how long the user has been holding right on the dpad.
*/
var dpadRightGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_RIGHT);
/**
* Variable used to track how long the user has been holding up on the left stick.
*/
var leftStickUpGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_UP);
/**
* Variable used to track how long the user has been holding down on the left stick.
*/
var leftStickDownGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_DOWN);
/**
* Variable used to track how long the user has been holding left on the left stick.
*/
var leftStickLeftGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_LEFT);
/**
* Variable used to track how long the user has been holding right on the left stick.
*/
var leftStickRightGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_RIGHT);
/**
* Variable used to track how long the user has been holding up on the right stick.
*/
var rightStickUpGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_UP);
/**
* Variable used to track how long the user has been holding down on the right stick.
*/
var rightStickDownGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_DOWN);
/**
* Variable used to track how long the user has been holding left on the right stick.
*/
var rightStickLeftGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_LEFT);
/**
* Variable used to track how long the user has been holding right on the right stick.
*/
var rightStickRightGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_RIGHT);
/**
* AUDIO AND SOUND DATA
*/
@ -1733,10 +1816,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var gridGhostNote:Null<ChartEditorNoteSprite> = null;
/**
* A sprite used to indicate the note that will be placed on click.
* A sprite used to indicate the hold note that will be placed on click.
*/
var gridGhostHoldNote:Null<ChartEditorHoldNoteSprite> = null;
/**
* A sprite used to indicate the hold note that will be placed on button release.
*/
var gridPlayheadGhostHoldNotes:Array<ChartEditorHoldNoteSprite> = [];
/**
* A sprite used to indicate the event that will be placed on click.
*/
@ -2126,11 +2214,23 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
gridGhostHoldNote = new ChartEditorHoldNoteSprite(this);
gridGhostHoldNote.alpha = 0.6;
gridGhostHoldNote.noteData = new SongNoteData(0, 0, 0, "");
gridGhostHoldNote.noteData = null;
gridGhostHoldNote.visible = false;
add(gridGhostHoldNote);
gridGhostHoldNote.zIndex = 11;
while (gridPlayheadGhostHoldNotes.length < (STRUMLINE_SIZE * 2))
{
var ghost = new ChartEditorHoldNoteSprite(this);
ghost.alpha = 0.6;
ghost.noteData = null;
ghost.visible = false;
add(ghost);
ghost.zIndex = 11;
gridPlayheadGhostHoldNotes.push(ghost);
}
gridGhostEvent = new ChartEditorEventSprite(this);
gridGhostEvent.alpha = 0.6;
gridGhostEvent.eventData = new SongEventData(-1, '', {});
@ -2702,6 +2802,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
function setupTurboKeyHandlers():Void
{
// Keyboard shortcuts
add(undoKeyHandler);
add(redoKeyHandler);
add(upKeyHandler);
@ -2710,6 +2811,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(sKeyHandler);
add(pageUpKeyHandler);
add(pageDownKeyHandler);
// Gamepad inputs
add(dpadUpGamepadHandler);
add(dpadDownGamepadHandler);
add(dpadLeftGamepadHandler);
add(dpadRightGamepadHandler);
add(leftStickUpGamepadHandler);
add(leftStickDownGamepadHandler);
add(leftStickLeftGamepadHandler);
add(leftStickRightGamepadHandler);
add(rightStickUpGamepadHandler);
add(rightStickDownGamepadHandler);
add(rightStickLeftGamepadHandler);
add(rightStickRightGamepadHandler);
}
/**
@ -2865,6 +2980,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
handleTestKeybinds();
handleHelpKeybinds();
this.handleGamepadControls();
#if debug
handleQuickWatch();
#end
@ -3370,32 +3487,56 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Up Arrow = Scroll Up
if (upKeyHandler.activated && currentLiveInputStyle == None)
{
scrollAmount = -GRID_SIZE * 0.25 * 25.0;
scrollAmount = -GRID_SIZE * 4;
shouldPause = true;
}
// Down Arrow = Scroll Down
if (downKeyHandler.activated && currentLiveInputStyle == None)
{
scrollAmount = GRID_SIZE * 0.25 * 25.0;
scrollAmount = GRID_SIZE * 4;
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;
scrollAmount = -GRID_SIZE * 4;
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;
scrollAmount = GRID_SIZE * 4;
shouldPause = true;
}
// PAGE UP = Jump up to nearest measure
if (pageUpKeyHandler.activated)
// GAMEPAD LEFT STICK UP = Scroll Up by 1 note snap
if (leftStickUpGamepadHandler.activated)
{
scrollAmount = -GRID_SIZE * noteSnapRatio;
shouldPause = true;
}
// GAMEPAD LEFT STICK DOWN = Scroll Down by 1 note snap
if (leftStickDownGamepadHandler.activated)
{
scrollAmount = GRID_SIZE * noteSnapRatio;
shouldPause = true;
}
// GAMEPAD RIGHT STICK UP = Scroll Up by 1 note snap (playhead only)
if (rightStickUpGamepadHandler.activated)
{
playheadAmount = -GRID_SIZE * noteSnapRatio;
shouldPause = true;
}
// GAMEPAD RIGHT STICK DOWN = Scroll Down by 1 note snap (playhead only)
if (rightStickDownGamepadHandler.activated)
{
playheadAmount = GRID_SIZE * noteSnapRatio;
shouldPause = true;
}
var funcJumpUp = (playheadOnly:Bool) -> {
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight;
@ -3405,20 +3546,37 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
scrollAmount = targetScrollPosition - playheadPos;
if (playheadOnly)
{
playheadAmount = targetScrollPosition - playheadPos;
}
else
{
scrollAmount = targetScrollPosition - playheadPos;
}
}
// PAGE UP = Jump up to nearest measure
// GAMEPAD LEFT STICK LEFT = Jump up to nearest measure
if (pageUpKeyHandler.activated || leftStickLeftGamepadHandler.activated)
{
funcJumpUp(false);
shouldPause = true;
}
if (rightStickLeftGamepadHandler.activated)
{
funcJumpUp(true);
shouldPause = true;
}
if (playbarButtonPressed == 'playbarBack')
{
playbarButtonPressed = '';
scrollAmount = -GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
funcJumpUp(false);
shouldPause = true;
}
// PAGE DOWN = Jump down to nearest measure
if (pageDownKeyHandler.activated)
{
var funcJumpDown = (playheadOnly:Bool) -> {
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight;
@ -3428,26 +3586,46 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
scrollAmount = targetScrollPosition - playheadPos;
if (playheadOnly)
{
playheadAmount = targetScrollPosition - playheadPos;
}
else
{
scrollAmount = targetScrollPosition - playheadPos;
}
}
// PAGE DOWN = Jump down to nearest measure
// GAMEPAD LEFT STICK RIGHT = Jump down to nearest measure
if (pageDownKeyHandler.activated || leftStickRightGamepadHandler.activated)
{
funcJumpDown(false);
shouldPause = true;
}
if (rightStickRightGamepadHandler.activated)
{
funcJumpDown(true);
shouldPause = true;
}
if (playbarButtonPressed == 'playbarForward')
{
playbarButtonPressed = '';
scrollAmount = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
funcJumpDown(false);
shouldPause = true;
}
// SHIFT + Scroll = Scroll Fast
if (FlxG.keys.pressed.SHIFT)
// GAMEPAD LEFT STICK CLICK + Scroll = Scroll Fast
if (FlxG.keys.pressed.SHIFT || (FlxG.gamepads.firstActive?.pressed?.LEFT_STICK_CLICK ?? false))
{
scrollAmount *= 2;
}
// CONTROL + Scroll = Scroll Precise
if (FlxG.keys.pressed.CONTROL)
{
scrollAmount /= 10;
scrollAmount /= 4;
}
// Alt + Drag = Scroll but move the playhead the same amount.
@ -4520,37 +4698,77 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handlePlayhead():Void
{
// Place notes at the playhead.
// Place notes at the playhead with the keyboard.
switch (currentLiveInputStyle)
{
case ChartEditorLiveInputStyle.WASD:
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
if (FlxG.keys.justReleased.A) finishPlaceNoteAtPlayhead(4);
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
if (FlxG.keys.justReleased.S) finishPlaceNoteAtPlayhead(5);
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
if (FlxG.keys.justReleased.W) finishPlaceNoteAtPlayhead(6);
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
if (FlxG.keys.justReleased.D) finishPlaceNoteAtPlayhead(7);
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
if (FlxG.keys.justReleased.LEFT) finishPlaceNoteAtPlayhead(0);
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
if (FlxG.keys.justReleased.DOWN) finishPlaceNoteAtPlayhead(1);
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
if (FlxG.keys.justReleased.UP) finishPlaceNoteAtPlayhead(2);
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
if (FlxG.keys.justReleased.RIGHT) finishPlaceNoteAtPlayhead(3);
case ChartEditorLiveInputStyle.NumberKeys:
// Flipped because Dad is on the left but represents data 0-3.
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
if (FlxG.keys.justReleased.ONE) finishPlaceNoteAtPlayhead(4);
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
if (FlxG.keys.justReleased.TWO) finishPlaceNoteAtPlayhead(5);
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
if (FlxG.keys.justReleased.THREE) finishPlaceNoteAtPlayhead(6);
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
if (FlxG.keys.justReleased.FOUR) finishPlaceNoteAtPlayhead(7);
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(0);
if (FlxG.keys.justReleased.FIVE) finishPlaceNoteAtPlayhead(0);
if (FlxG.keys.justPressed.SIX) placeNoteAtPlayhead(1);
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
if (FlxG.keys.justReleased.SEVEN) finishPlaceNoteAtPlayhead(2);
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
if (FlxG.keys.justReleased.EIGHT) finishPlaceNoteAtPlayhead(3);
case ChartEditorLiveInputStyle.None:
// Do nothing.
}
// Place notes at the playhead with the gamepad.
if (FlxG.gamepads.firstActive != null)
{
if (FlxG.gamepads.firstActive.justPressed.DPAD_LEFT) placeNoteAtPlayhead(4);
if (FlxG.gamepads.firstActive.justReleased.DPAD_LEFT) finishPlaceNoteAtPlayhead(4);
if (FlxG.gamepads.firstActive.justPressed.DPAD_DOWN) placeNoteAtPlayhead(5);
if (FlxG.gamepads.firstActive.justReleased.DPAD_DOWN) finishPlaceNoteAtPlayhead(5);
if (FlxG.gamepads.firstActive.justPressed.DPAD_UP) placeNoteAtPlayhead(6);
if (FlxG.gamepads.firstActive.justReleased.DPAD_UP) finishPlaceNoteAtPlayhead(6);
if (FlxG.gamepads.firstActive.justPressed.DPAD_RIGHT) placeNoteAtPlayhead(7);
if (FlxG.gamepads.firstActive.justReleased.DPAD_RIGHT) finishPlaceNoteAtPlayhead(7);
if (FlxG.gamepads.firstActive.justPressed.X) placeNoteAtPlayhead(0);
if (FlxG.gamepads.firstActive.justReleased.X) finishPlaceNoteAtPlayhead(0);
if (FlxG.gamepads.firstActive.justPressed.A) placeNoteAtPlayhead(1);
if (FlxG.gamepads.firstActive.justReleased.A) finishPlaceNoteAtPlayhead(1);
if (FlxG.gamepads.firstActive.justPressed.Y) placeNoteAtPlayhead(2);
if (FlxG.gamepads.firstActive.justReleased.Y) finishPlaceNoteAtPlayhead(2);
if (FlxG.gamepads.firstActive.justPressed.B) placeNoteAtPlayhead(3);
if (FlxG.gamepads.firstActive.justReleased.B) finishPlaceNoteAtPlayhead(3);
}
}
function placeNoteAtPlayhead(column:Int):Void
{
// SHIFT + press or LEFT_SHOULDER + press to remove notes instead of placing them.
var removeNoteInstead:Bool = FlxG.keys.pressed.SHIFT || (FlxG.gamepads.firstActive?.pressed?.LEFT_SHOULDER ?? false);
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
@ -4561,10 +4779,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]);
if (notesAtPos.length == 0)
if (notesAtPos.length == 0 && !removeNoteInstead)
{
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace);
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
currentLiveInputPlaceNoteData[column] = newNoteData;
gridPlayheadGhostHoldNotes[column].noteData = newNoteData.clone();
gridPlayheadGhostHoldNotes[column].noteDirection = newNoteData.getDirection();
}
else if (removeNoteInstead)
{
trace('Removing existing note at position.');
performCommand(new RemoveNotesCommand(notesAtPos));
}
else
{
@ -4572,6 +4798,87 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
}
function updatePlayheadGhostHoldNotes():Void
{
// Update playhead ghost hold notes.
for (index in 0...gridPlayheadGhostHoldNotes.length)
{
var ghostHold = gridPlayheadGhostHoldNotes[index];
if (ghostHold == null) continue;
if (ghostHold.noteData == null)
{
ghostHold.visible = false;
ghostHold.setHeightDirectly(0);
playheadDragLengthCurrent[index] = 0;
continue;
};
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
var newNoteLength:Float = playheadPosSnappedMs - ghostHold.noteData.time;
trace('newNoteLength: ${newNoteLength}');
if (newNoteLength > 0)
{
ghostHold.noteData.length = newNoteLength;
var targetNoteLengthSteps:Float = ghostHold.noteData.getStepLength(true);
var targetNoteLengthStepsInt:Int = Std.int(Math.floor(targetNoteLengthSteps));
var targetNoteLengthPixels:Float = targetNoteLengthSteps * GRID_SIZE;
if (playheadDragLengthCurrent[index] != targetNoteLengthStepsInt)
{
stretchySounds = !stretchySounds;
this.playSound(Paths.sound('chartingSounds/stretch' + (stretchySounds ? '1' : '2') + '_UI'));
playheadDragLengthCurrent[index] = targetNoteLengthStepsInt;
}
ghostHold.visible = true;
trace('newHeight: ${targetNoteLengthPixels}');
ghostHold.setHeightDirectly(targetNoteLengthPixels, true);
ghostHold.updateHoldNotePosition(renderedHoldNotes);
}
else
{
ghostHold.visible = false;
ghostHold.setHeightDirectly(0);
playheadDragLengthCurrent[index] = 0;
}
}
}
function finishPlaceNoteAtPlayhead(column:Int):Void
{
if (currentLiveInputPlaceNoteData[column] == null) return;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
var newNoteLength:Float = playheadPosSnappedMs - currentLiveInputPlaceNoteData[column].time;
trace('finishPlace newNoteLength: ${newNoteLength}');
if (newNoteLength < Conductor.instance.stepLengthMs)
{
// Don't extend the note if it's too short.
trace('Not extending note.');
currentLiveInputPlaceNoteData[column] = null;
gridPlayheadGhostHoldNotes[column].noteData = null;
}
else
{
// Extend the note to the playhead position.
trace('Extending note.');
this.playSound(Paths.sound('chartingSounds/stretchSNAP_UI'));
performCommand(new ExtendNoteLengthCommand(currentLiveInputPlaceNoteData[column], newNoteLength));
currentLiveInputPlaceNoteData[column] = null;
gridPlayheadGhostHoldNotes[column].noteData = null;
}
}
/**
* Handle aligning the health icons next to the grid.
*/

View file

@ -20,6 +20,8 @@ class RemoveEventsCommand implements ChartEditorCommand
public function execute(state:ChartEditorState):Void
{
if (events.length == 0) return;
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentEventSelection = [];
@ -34,6 +36,8 @@ class RemoveEventsCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
if (events.length == 0) return;
for (event in events)
{
state.currentSongChartEventData.push(event);

View file

@ -23,6 +23,8 @@ class RemoveItemsCommand implements ChartEditorCommand
public function execute(state:ChartEditorState):Void
{
if ((notes.length + events.length) == 0) return;
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
@ -40,6 +42,8 @@ class RemoveItemsCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
if ((notes.length + events.length) == 0) return;
for (note in notes)
{
state.currentSongChartNoteData.push(note);

View file

@ -20,6 +20,8 @@ class RemoveNotesCommand implements ChartEditorCommand
public function execute(state:ChartEditorState):Void
{
if (notes.length == 0) return;
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentNoteSelection = [];
state.currentEventSelection = [];
@ -35,6 +37,8 @@ class RemoveNotesCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
if (notes.length == 0) return;
for (note in notes)
{
state.currentSongChartNoteData.push(note);

View file

@ -43,11 +43,16 @@ class ChartEditorHoldNoteSprite extends SustainTrail
* Set the height directly, to a value in pixels.
* @param h The desired height in pixels.
*/
public function setHeightDirectly(h:Float, ?lerp:Bool = false)
public function setHeightDirectly(h:Float, lerp:Bool = false)
{
if (lerp != null && lerp) sustainLength = FlxMath.lerp(sustainLength, h / (getScrollSpeed() * Constants.PIXELS_PER_MS), 0.25);
if (lerp)
{
sustainLength = FlxMath.lerp(sustainLength, h / (getScrollSpeed() * Constants.PIXELS_PER_MS), 0.25);
}
else
{
sustainLength = h / (getScrollSpeed() * Constants.PIXELS_PER_MS);
}
fullSustainLength = sustainLength;
}

View file

@ -0,0 +1,133 @@
package funkin.ui.debug.charting.handlers;
/**
* Yes, we're that crazy. Gamepad support for the chart editor.
*/
@:nullSafety
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorGamepadHandler
{
public static function handleGamepadControls(chartEditorState:ChartEditorState)
{
if (FlxG.gamepads.firstActive == null) return;
if (FlxG.gamepads.firstActive.justPressed.A)
{
// trace('Gamepad: A pressed');
}
if (FlxG.gamepads.firstActive.justPressed.B)
{
// trace('Gamepad: B pressed');
}
if (FlxG.gamepads.firstActive.justPressed.X)
{
// trace('Gamepad: X pressed');
}
if (FlxG.gamepads.firstActive.justPressed.Y)
{
// trace('Gamepad: Y pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_SHOULDER)
{
// trace('Gamepad: LEFT_SHOULDER pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_SHOULDER)
{
// trace('Gamepad: RIGHT_SHOULDER pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_STICK_CLICK)
{
// trace('Gamepad: LEFT_STICK_CLICK pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_STICK_CLICK)
{
// trace('Gamepad: RIGHT_STICK_CLICK pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_TRIGGER)
{
// trace('Gamepad: LEFT_TRIGGER pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_TRIGGER)
{
// trace('Gamepad: RIGHT_TRIGGER pressed');
}
if (FlxG.gamepads.firstActive.justPressed.START)
{
// trace('Gamepad: START pressed');
}
if (FlxG.gamepads.firstActive.justPressed.BACK)
{
// trace('Gamepad: BACK pressed');
}
if (FlxG.gamepads.firstActive.justPressed.GUIDE)
{
// trace('Gamepad: GUIDE pressed');
}
if (FlxG.gamepads.firstActive.justPressed.DPAD_UP)
{
// trace('Gamepad: DPAD_UP pressed');
}
if (FlxG.gamepads.firstActive.justPressed.DPAD_DOWN)
{
// trace('Gamepad: DPAD_DOWN pressed');
}
if (FlxG.gamepads.firstActive.justPressed.DPAD_LEFT)
{
// trace('Gamepad: DPAD_LEFT pressed');
}
if (FlxG.gamepads.firstActive.justPressed.DPAD_RIGHT)
{
// trace('Gamepad: DPAD_RIGHT pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_STICK_DIGITAL_UP)
{
// trace('Gamepad: LEFT_STICK_DIGITAL_UP pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_STICK_DIGITAL_DOWN)
{
// trace('Gamepad: LEFT_STICK_DIGITAL_DOWN pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_STICK_DIGITAL_LEFT)
{
// trace('Gamepad: LEFT_STICK_DIGITAL_LEFT pressed');
}
if (FlxG.gamepads.firstActive.justPressed.LEFT_STICK_DIGITAL_RIGHT)
{
// trace('Gamepad: LEFT_STICK_DIGITAL_RIGHT pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_STICK_DIGITAL_UP)
{
// trace('Gamepad: RIGHT_STICK_DIGITAL_UP pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_STICK_DIGITAL_DOWN)
{
// trace('Gamepad: RIGHT_STICK_DIGITAL_DOWN pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_STICK_DIGITAL_LEFT)
{
// trace('Gamepad: RIGHT_STICK_DIGITAL_LEFT pressed');
}
if (FlxG.gamepads.firstActive.justPressed.RIGHT_STICK_DIGITAL_RIGHT)
{
// trace('Gamepad: RIGHT_STICK_DIGITAL_RIGHT pressed');
}
}
}

View file

@ -5,6 +5,7 @@ package funkin.ui.debug.charting;
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
using funkin.ui.debug.charting.handlers.ChartEditorContextMenuHandler;
using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler;
using funkin.ui.debug.charting.handlers.ChartEditorGamepadHandler;
using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler;
using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler;
using funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;