From 9c6b3bf51d3c51219c79ab37daba2d8305ed607a Mon Sep 17 00:00:00 2001
From: George FunBook <gkurelic@gmail.com>
Date: Mon, 22 Mar 2021 21:39:35 -0500
Subject: [PATCH] finalize options menu

---
 Project.xml               |   2 +
 source/Controls.hx        |  96 ++++++++++---
 source/OptionsMenu_old.hx |   2 +-
 source/PlayerSettings.hx  | 284 ++++++++++++++++++++++++++------------
 source/TitleState.hx      |   3 +-
 source/ui/ControlsMenu.hx |  14 +-
 6 files changed, 281 insertions(+), 120 deletions(-)

diff --git a/Project.xml b/Project.xml
index 85284f76c..3055cf457 100644
--- a/Project.xml
+++ b/Project.xml
@@ -180,6 +180,8 @@
 	<haxedef name="CAN_OPEN_LINKS" unless="switch"/>
 	<haxedef name="CAN_CHEAT" if="switch debug"/>
 	
+	<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
+	
 	<section if="newgrounds">
 		<!-- Enables Ng.core.verbose -->
 		<!-- <haxedef name="NG_VERBOSE" /> -->
diff --git a/source/Controls.hx b/source/Controls.hx
index 6e49c5387..95748946f 100644
--- a/source/Controls.hx
+++ b/source/Controls.hx
@@ -18,10 +18,11 @@ import flixel.input.keyboard.FlxKey;
  */
 enum Control
 {
-	NOTE_UP;
+	// List notes in order from left to right on gameplay screen.
 	NOTE_LEFT;
-	NOTE_RIGHT;
 	NOTE_DOWN;
+	NOTE_UP;
+	NOTE_RIGHT;
 	UI_UP;
 	UI_LEFT;
 	UI_RIGHT;
@@ -327,7 +328,7 @@ class Controls extends FlxActionSet
 		}
 	}
 
-	public function replaceBinding(control:Control, device:Device, ?toAdd:Int, ?toRemove:Int)
+	public function replaceBinding(control:Control, device:Device, toAdd:Int, toRemove:Int)
 	{
 		if (toAdd == toRemove)
 			return;
@@ -335,16 +336,36 @@ class Controls extends FlxActionSet
 		switch (device)
 		{
 			case Keys:
-				if (toRemove != null)
-					unbindKeys(control, [toRemove]);
-				if (toAdd != null)
-					bindKeys(control, [toAdd]);
+				forEachBound(control, function(action, _) replaceKey(action, toAdd, toRemove));
 
 			case Gamepad(id):
-				if (toRemove != null)
-					unbindButtons(control, id, [toRemove]);
-				if (toAdd != null)
-					bindButtons(control, id, [toAdd]);
+				forEachBound(control, function(action, _) replaceButton(action, id, toAdd, toRemove));
+		}
+	}
+	
+	function replaceKey(action:FlxActionDigital, toAdd:Int, toRemove:Int)
+	{
+		for (i in 0...action.inputs.length)
+		{
+			var input = action.inputs[i];
+			if (input.device == KEYBOARD && input.inputID == toRemove)
+			{
+				@:privateAccess
+				action.inputs[i].inputID = toAdd;
+			}
+		}
+	}
+	
+	function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:Int, toRemove:Int)
+	{
+		for (i in 0...action.inputs.length)
+		{
+			var input = action.inputs[i];
+			if (isGamepad(input, deviceID) && input.inputID == toRemove)
+			{
+				@:privateAccess
+				action.inputs[i].inputID = toAdd;
+			}
 		}
 	}
 
@@ -498,12 +519,11 @@ class Controls extends FlxActionSet
 		}
 	}
 
-	public function addGamepad(id:Int, ?buttonMap:Map<Control, Array<FlxGamepadInputID>>):Void
+	public function addGamepadWithSaveData(id:Int, ?padData:Dynamic):Void
 	{
 		gamepadsAdded.push(id);
 		
-		for (control in buttonMap.keys())
-			bindButtons(control, id, buttonMap[control]);
+		fromSaveData(padData, Gamepad(id));
 	}
 
 	inline function addGamepadLiteral(id:Int, ?buttonMap:Map<Control, Array<FlxGamepadInputID>>):Void
@@ -522,7 +542,7 @@ class Controls extends FlxActionSet
 			while (i-- > 0)
 			{
 				var input = action.inputs[i];
-				if (input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID))
+				if (isGamepad(input, deviceID))
 					action.remove(input);
 			}
 		}
@@ -533,13 +553,9 @@ class Controls extends FlxActionSet
 	public function addDefaultGamepad(id):Void
 	{
 		addGamepadLiteral(id, [
-			#if switch
-			Control.ACCEPT => [B],
-			Control.BACK => [A],
-			#else
-			Control.ACCEPT => [A],
-			Control.BACK => [B],
-			#end
+			
+			Control.ACCEPT => [#if switch B #else A #end],
+			Control.BACK => [#if switch A #else B #end, BACK],
 			Control.UI_UP => [DPAD_UP, LEFT_STICK_DIGITAL_UP],
 			Control.UI_DOWN => [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN],
 			Control.UI_LEFT => [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT],
@@ -608,7 +624,7 @@ class Controls extends FlxActionSet
 			case Gamepad(id):
 				for (input in getActionFromControl(control).inputs)
 				{
-					if (input.deviceID == id)
+					if (isGamepad(input, id))
 						list.push(input.inputID);
 				}
 		}
@@ -626,6 +642,37 @@ class Controls extends FlxActionSet
 		}
 	}
 
+	public function fromSaveData(data:Dynamic, device:Device)
+	{
+		for (control in Control.createAll())
+		{
+			var inputs:Array<Int> = Reflect.field(data, control.getName());
+			if (inputs != null)
+			{
+				switch(device)
+				{
+					case Keys: bindKeys(control, inputs.copy()); 
+					case Gamepad(id): bindButtons(control, id, inputs.copy()); 
+				}
+			}
+		}
+	}
+	
+	public function createSaveData(device:Device):Dynamic
+	{
+		var isEmpty = true;
+		var data = {};
+		for (control in Control.createAll())
+		{
+			var inputs = getInputsFor(control, device);
+			isEmpty = isEmpty && inputs.length == 0;
+			
+			Reflect.setField(data, control.getName(), inputs);
+		}
+		
+		return isEmpty ? null : data;
+	}
+
 	static function isDevice(input:FlxActionInput, device:Device)
 	{
 		return switch device
@@ -640,3 +687,6 @@ class Controls extends FlxActionSet
 		return input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID);
 	}
 }
+
+
+typedef SaveInputLists = {?keys:Array<Int>, ?pad:Array<Int>};
\ No newline at end of file
diff --git a/source/OptionsMenu_old.hx b/source/OptionsMenu_old.hx
index 3acd212a3..4ec844323 100644
--- a/source/OptionsMenu_old.hx
+++ b/source/OptionsMenu_old.hx
@@ -82,7 +82,7 @@ class OptionsMenu_old extends MusicBeatState
 	{
 		if (FlxG.keys.getIsDown().length > 0)
 		{
-			PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxG.keys.getIsDown()[0].ID, null);
+			// PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxG.keys.getIsDown()[0].ID, null);
 		}
 		// PlayerSettings.player1.controls.replaceBinding(Control)
 	}
diff --git a/source/PlayerSettings.hx b/source/PlayerSettings.hx
index df7586c44..a0ed2429a 100644
--- a/source/PlayerSettings.hx
+++ b/source/PlayerSettings.hx
@@ -1,8 +1,11 @@
 package;
 
 import Controls;
+
 import flixel.FlxCamera;
 import flixel.FlxG;
+import flixel.input.actions.FlxActionInput;
+import flixel.input.gamepad.FlxGamepad;
 import flixel.util.FlxSignal;
 
 // import ui.DeviceManager;
@@ -24,124 +27,223 @@ class PlayerSettings
 	// public var avatar:Player;
 	// public var camera(get, never):PlayCamera;
 
-	function new(id, scheme)
+	function new(id)
 	{
 		this.id = id;
-		this.controls = new Controls('player$id', scheme);
+		this.controls = new Controls('player$id', None);
+		
+		#if CLEAR_INPUT_SAVE
+		FlxG.save.data.controls = null;
+		FlxG.save.flush();
+		#end
+		
+		var useDefault = true;
+		var controlData = FlxG.save.data.controls;
+		if (controlData != null)
+		{
+			var keyData:Dynamic = null;
+			if (id == 0 && controlData.p1 != null && controlData.p1.keys != null)
+				keyData = controlData.p1.keys;
+			else if (id == 1 && controlData.p2 != null && controlData.p2.keys != null)
+				keyData = controlData.p2.keys;
+			
+			if (keyData != null)
+			{
+				useDefault = false;
+				trace("loaded key data: " + haxe.Json.stringify(keyData));
+				controls.fromSaveData(keyData, Keys);
+			}
+		}
+		
+		if (useDefault)
+			controls.setKeyboardScheme(Solo);
 	}
+	
+	function addGamepad(gamepad:FlxGamepad)
+	{
+		var useDefault = true;
+		var controlData = FlxG.save.data.controls;
+		if (controlData != null)
+		{
+			var padData:Dynamic = null;
+			if (id == 0 && controlData.p1 != null && controlData.p1.pad != null)
+				padData = controlData.p1.pad;
+			else if (id == 1 && controlData.p2 != null && controlData.p2.pad != null)
+				padData = controlData.p2.pad;
+			
+			if (padData != null)
+			{
+				useDefault = false;
+				trace("loaded pad data: " + haxe.Json.stringify(padData));
+				controls.addGamepadWithSaveData(gamepad.id, padData);
+			}
+		}
+		
+		if (useDefault)
+			controls.addDefaultGamepad(gamepad.id);
+	}
+	
+	public function saveControls()
+	{
+		if (FlxG.save.data.controls == null)
+			FlxG.save.data.controls = {};
+		
+		var playerData:{ ?keys:Dynamic, ?pad:Dynamic }
+		if (id == 0)
+		{
+			if (FlxG.save.data.controls.p1 == null)
+				FlxG.save.data.controls.p1 = {};
+			playerData = FlxG.save.data.controls.p1;
+		}
+		else
+		{
+			if (FlxG.save.data.controls.p2 == null)
+				FlxG.save.data.controls.p2 = {};
+			playerData = FlxG.save.data.controls.p2;
+		}
+		
+		var keyData = controls.createSaveData(Keys);
+		if (keyData != null)
+		{
+			playerData.keys = keyData;
+			trace("saving key data: " + haxe.Json.stringify(keyData));
+		}
+		
+		if (controls.gamepadsAdded.length > 0)
+		{
+			var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0]));
+			if (padData != null)
+			{
+				trace("saving pad data: " + haxe.Json.stringify(padData));
+				playerData.pad = padData;
+			}
+		}
+		
+		FlxG.save.flush();
+	}
+	
+	static public function init():Void
+	{
+		if (player1 == null)
+		{
+			player1 = new PlayerSettings(0);
+			++numPlayers;
+		}
+		
+		FlxG.gamepads.deviceConnected.add(onGamepadAdded);
 
+		var numGamepads = FlxG.gamepads.numActiveGamepads;
+		for (i in 0...numGamepads)
+		{
+			var gamepad = FlxG.gamepads.getByID(i);
+			if (gamepad != null)
+				onGamepadAdded(gamepad);
+		}
+
+		// 	player1.controls.addDefaultGamepad(0);
+		// }
+
+		// if (numGamepads > 1)
+		// {
+		// 	if (player2 == null)
+		// 	{
+		// 		player2 = new PlayerSettings(1, None);
+		// 		++numPlayers;
+		// 	}
+
+		// 	var gamepad = FlxG.gamepads.getByID(1);
+		// 	if (gamepad == null)
+		// 		throw 'Unexpected null gamepad. id:0';
+
+		// 	player2.controls.addDefaultGamepad(1);
+		// }
+
+		// DeviceManager.init();
+	}
+	
+	static function onGamepadAdded(gamepad:FlxGamepad)
+	{
+		player1.addGamepad(gamepad);
+	}
+	
+
+	/*
 	public function setKeyboardScheme(scheme)
 	{
 		controls.setKeyboardScheme(scheme);
 	}
 
-	/* 
-		static public function addAvatar(avatar:Player):PlayerSettings
-		{
-			var settings:PlayerSettings;
-
-			if (player1 == null)
-			{
-				player1 = new PlayerSettings(0, Solo);
-				++numPlayers;
-			}
-
-			if (player1.avatar == null)
-				settings = player1;
-			else
-			{
-				if (player2 == null)
-				{
-					if (player1.controls.keyboardScheme.match(Duo(true)))
-						player2 = new PlayerSettings(1, Duo(false));
-					else
-						player2 = new PlayerSettings(1, None);
-					++numPlayers;
-				}
-
-				if (player2.avatar == null)
-					settings = player2;
-				else
-					throw throw 'Invalid number of players: ${numPlayers + 1}';
-			}
-			++numAvatars;
-			settings.avatar = avatar;
-			avatar.settings = settings;
-
-			splitCameras();
-
-			onAvatarAdd.dispatch(settings);
-
-			return settings;
-		}
-
-		static public function removeAvatar(avatar:Player):Void
-		{
-			var settings:PlayerSettings;
-
-			if (player1 != null && player1.avatar == avatar)
-				settings = player1;
-			else if (player2 != null && player2.avatar == avatar)
-			{
-				settings = player2;
-				if (player1.controls.keyboardScheme.match(Duo(_)))
-					player1.setKeyboardScheme(Solo);
-			}
-			else
-				throw "Cannot remove avatar that is not for a player";
-
-			settings.avatar = null;
-			while (settings.controls.gamepadsAdded.length > 0)
-			{
-				final id = settings.controls.gamepadsAdded.shift();
-				settings.controls.removeGamepad(id);
-				DeviceManager.releaseGamepad(FlxG.gamepads.getByID(id));
-			}
-
-			--numAvatars;
-
-			splitCameras();
-
-			onAvatarRemove.dispatch(avatar.settings);
-		}
-
-	 */
-	static public function init():Void
+	static public function addAvatar(avatar:Player):PlayerSettings
 	{
+		var settings:PlayerSettings;
+
 		if (player1 == null)
 		{
 			player1 = new PlayerSettings(0, Solo);
 			++numPlayers;
 		}
 
-		var numGamepads = FlxG.gamepads.numActiveGamepads;
-		if (numGamepads > 0)
-		{
-			var gamepad = FlxG.gamepads.getByID(0);
-			if (gamepad == null)
-				throw 'Unexpected null gamepad. id:0';
-
-			player1.controls.addDefaultGamepad(0);
-		}
-
-		if (numGamepads > 1)
+		if (player1.avatar == null)
+			settings = player1;
+		else
 		{
 			if (player2 == null)
 			{
-				player2 = new PlayerSettings(1, None);
+				if (player1.controls.keyboardScheme.match(Duo(true)))
+					player2 = new PlayerSettings(1, Duo(false));
+				else
+					player2 = new PlayerSettings(1, None);
 				++numPlayers;
 			}
 
-			var gamepad = FlxG.gamepads.getByID(1);
-			if (gamepad == null)
-				throw 'Unexpected null gamepad. id:0';
+			if (player2.avatar == null)
+				settings = player2;
+			else
+				throw throw 'Invalid number of players: ${numPlayers + 1}';
+		}
+		++numAvatars;
+		settings.avatar = avatar;
+		avatar.settings = settings;
 
-			player2.controls.addDefaultGamepad(1);
+		splitCameras();
+
+		onAvatarAdd.dispatch(settings);
+
+		return settings;
+	}
+
+	static public function removeAvatar(avatar:Player):Void
+	{
+		var settings:PlayerSettings;
+
+		if (player1 != null && player1.avatar == avatar)
+			settings = player1;
+		else if (player2 != null && player2.avatar == avatar)
+		{
+			settings = player2;
+			if (player1.controls.keyboardScheme.match(Duo(_)))
+				player1.setKeyboardScheme(Solo);
+		}
+		else
+			throw "Cannot remove avatar that is not for a player";
+
+		settings.avatar = null;
+		while (settings.controls.gamepadsAdded.length > 0)
+		{
+			final id = settings.controls.gamepadsAdded.shift();
+			settings.controls.removeGamepad(id);
+			DeviceManager.releaseGamepad(FlxG.gamepads.getByID(id));
 		}
 
-		// DeviceManager.init();
+		--numAvatars;
+
+		splitCameras();
+
+		onAvatarRemove.dispatch(avatar.settings);
 	}
 
+	 */
+
 	static public function reset()
 	{
 		player1 = null;
diff --git a/source/TitleState.hx b/source/TitleState.hx
index 01668d72c..eff43f823 100644
--- a/source/TitleState.hx
+++ b/source/TitleState.hx
@@ -41,8 +41,6 @@ class TitleState extends MusicBeatState
 
 		FlxG.sound.muteKeys = [ZERO];
 
-		PlayerSettings.init();
-
 		curWacky = FlxG.random.getObject(getIntroTextShit());
 
 		// DEBUG BULLSHIT
@@ -50,6 +48,7 @@ class TitleState extends MusicBeatState
 		super.create();
 
 		FlxG.save.bind('funkin', 'ninjamuffin99');
+		PlayerSettings.init();
 		Highscore.load();
 		
 		#if newgrounds
diff --git a/source/ui/ControlsMenu.hx b/source/ui/ControlsMenu.hx
index ca923af29..629a89914 100644
--- a/source/ui/ControlsMenu.hx
+++ b/source/ui/ControlsMenu.hx
@@ -184,7 +184,12 @@ class ControlsMenu extends ui.OptionsState.Page
 		
 		var inputName = device == Keys ? "key" : "button";
 		var cancel = device == Keys ? "Escape" : "Back";
-		prompt.setText('\nPress any $inputName to rebind\n\n\n\n    $cancel to cancel');
+		//todo: alignment
+		if (device == Keys)
+			prompt.setText('\nPress any key to rebind\n\n\n\n    $cancel to cancel');
+		else
+			prompt.setText('\nPress any button\n   to rebind\n\n\n $cancel to cancel');
+			
 		
 		controlGrid.selectedItem.select();
 		labels.members[Std.int(controlGrid.selectedIndex / COLUMNS)].alpha = 1.0;
@@ -265,13 +270,16 @@ class ControlsMenu extends ui.OptionsState.Page
 		// Don't use resetItem() since items share names/labels
 		item.input = input;
 		item.label.text = item.getLabel(input);
+		
+		PlayerSettings.player1.saveControls();
 	}
 	
 	function closePrompt()
 	{
-		controlGrid.enabled = true;
-		canExit = true;
 		prompt.exists = false;
+		controlGrid.enabled = true;
+		if (deviceList == null)
+			canExit = true;
 	}
 	
 	override function destroy()