From 17693651b3534284beb29f19eea65b089202b05f Mon Sep 17 00:00:00 2001 From: Timotej Leginus Date: Sun, 11 Jun 2023 20:12:53 +0200 Subject: [PATCH] Added multiplayer settings, now can direct connect to a room --- .../src/ea/res/drawable/ic_multiplayer.xml | 10 ++ .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 11 ++ .../java/org/yuzu/yuzu_emu/YuzuApplication.kt | 2 + .../features/settings/model/Settings.kt | 7 + .../settings/ui/SettingsFragmentPresenter.kt | 134 ++++++++++++++++++ .../fragments/HomeSettingsFragment.kt | 5 + .../yuzu/yuzu_emu/utils/MultiplayerHelper.kt | 21 +++ .../org/yuzu/yuzu_emu/utils/NetworkHelper.kt | 1 - src/android/app/src/main/jni/native.cpp | 66 +++++++++ .../app/src/main/res/values/strings.xml | 9 ++ src/yuzu/multiplayer/direct_connect.cpp | 1 + 11 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 src/android/app/src/ea/res/drawable/ic_multiplayer.xml create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MultiplayerHelper.kt diff --git a/src/android/app/src/ea/res/drawable/ic_multiplayer.xml b/src/android/app/src/ea/res/drawable/ic_multiplayer.xml new file mode 100644 index 0000000000..6457435a41 --- /dev/null +++ b/src/android/app/src/ea/res/drawable/ic_multiplayer.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index c11b6bc169..022dd28fcd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -464,6 +464,17 @@ object NativeLibrary { */ external fun submitInlineKeyboardInput(key_code: Int) + /** + * Connects to a room (similar with desktop version's "direct connect") + */ + external fun connectToRoom(nickname: String, server_addr: String, server_port: Int, password: String) + + /** + * Returns the state of the room member (client) + * @return The state as a string + */ + external fun getRoomMemberState(): String + /** * Button type for use in onTouchEvent */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt index 9fd9add6de..2e358cf8fa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt @@ -10,6 +10,7 @@ import android.content.Context import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DocumentsTree import org.yuzu.yuzu_emu.utils.GpuDriverHelper +import org.yuzu.yuzu_emu.utils.MultiplayerHelper import org.yuzu.yuzu_emu.utils.NetworkHelper import java.io.File @@ -48,6 +49,7 @@ class YuzuApplication : Application() { DirectoryInitialization.start(applicationContext) GpuDriverHelper.initializeDriverParameters(applicationContext) NetworkHelper.setRoutes(applicationContext) + MultiplayerHelper.initRoom(applicationContext) NativeLibrary.logDeviceInfo() createNotificationChannels(); diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 4eefa0bcce..858c103fba 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -109,6 +109,7 @@ class Settings { const val SECTION_CPU = "Cpu" const val SECTION_NETWORK = "Network" const val SECTION_THEME = "Theme" + const val SECTION_MULTIPLAYER = "Multiplayer" const val SECTION_DEBUG = "Debug" const val PREF_OVERLAY_INIT = "OverlayInit" @@ -131,6 +132,12 @@ class Settings { const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13" const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14" + const val PREF_ROOM_ADDRESS = "MultiplayerRoom_ServerAddress" + const val PREF_ROOM_PORT = "MultiplayerRoom_ServerPort" + const val PREF_ROOM_NICKNAME = "MultiplayerRoom_Nickname" + const val PREF_ROOM_PASSWORD = "MultiplayerRoom_Password" + const val PREF_ROOM_CONNECT_ON_START = "MultiplayerRoom_ConnectOnStart" + const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index e64f3760b9..9f12261726 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -12,6 +12,7 @@ import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.Settings @@ -67,6 +68,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) Settings.SECTION_RENDERER -> addGraphicsSettings(sl) Settings.SECTION_AUDIO -> addAudioSettings(sl) Settings.SECTION_NETWORK -> addNetworkSettings(sl) + Settings.SECTION_MULTIPLAYER -> addMultiplayerSettings(sl) Settings.SECTION_THEME -> addThemeSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl) else -> { @@ -460,6 +462,138 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } } + private fun addMultiplayerSettings(sl: ArrayList) { + settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_multiplayer)) + + sl.apply { + val serverAddress: AbstractStringSetting = object : AbstractStringSetting { + override var string: String + get() = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: "" + set(value) { + preferences.edit() + .putString(Settings.PREF_ROOM_ADDRESS, value) + .apply() + } + override val key: String? = null + override val section: String? = null + override val isRuntimeEditable: Boolean = false + override val valueAsString: String + get() = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: "" + override val defaultValue: Any = -1 + } + + val serverPort: AbstractStringSetting = object : AbstractStringSetting { + override var string: String + get() = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872" + set(value) { + preferences.edit() + .putString(Settings.PREF_ROOM_PORT, value) + .apply() + } + override val key: String? = null + override val section: String? = null + override val isRuntimeEditable: Boolean = false + override val valueAsString: String + get() = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872" + override val defaultValue: Any = "24872" + } + + val nickname: AbstractStringSetting = object : AbstractStringSetting { + override var string: String + get() = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: "" + set(value) { + preferences.edit() + .putString(Settings.PREF_ROOM_NICKNAME, value) + .apply() + } + override val key: String? = null + override val section: String? = null + override val isRuntimeEditable: Boolean = false + override val valueAsString: String + get() = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: "" + override val defaultValue: Any = -1 + } + + val password: AbstractStringSetting = object : AbstractStringSetting { + override var string: String + get() = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: "" + set(value) { + preferences.edit() + .putString(Settings.PREF_ROOM_PASSWORD, value) + .apply() + } + override val key: String? = null + override val section: String? = null + override val isRuntimeEditable: Boolean = false + override val valueAsString: String + get() = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: "" + override val defaultValue: Any = -1 + } + + val connectOnStart: AbstractBooleanSetting = object : AbstractBooleanSetting { + override var boolean: Boolean + get() = preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false) + set(value) { + preferences.edit() + .putBoolean(Settings.PREF_ROOM_CONNECT_ON_START, value) + .apply() + } + override val key: String? = null + override val section: String? = null + override val isRuntimeEditable: Boolean = false + override val valueAsString: String + get() = preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false).toString() + override val defaultValue: Any = -1 + } + + add( + TextSetting( + serverAddress, + R.string.multiplayer_room_server_address, + 0, + "", + "" + ) + ) + add( + TextSetting( + serverPort, + R.string.multiplayer_room_server_port, + 0, + "", + "" + ) + ) + add( + TextSetting( + nickname, + R.string.multiplayer_room_nickname, + 0, + "", + "" + ) + ) + add( + TextSetting( + password, + R.string.multiplayer_room_password, + 0, + "", + "" + ) + ) + add( + SwitchSetting( + connectOnStart, + R.string.multiplayer_room_connect_on_start, + 0, + "", + false + ) + ) + } + } + private fun addDebugSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) sl.apply { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index bdc3375016..addbdd81d8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -84,6 +84,11 @@ class HomeSettingsFragment : Fragment() { R.string.theme_and_color_description, R.drawable.ic_palette ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }, + HomeSetting( + R.string.preferences_multiplayer, + R.string.multiplayer_description, + R.drawable.ic_multiplayer + ) { SettingsActivity.launch(requireContext(), Settings.SECTION_MULTIPLAYER, "") }, HomeSetting( R.string.install_gpu_driver, R.string.install_gpu_driver_description, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MultiplayerHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MultiplayerHelper.kt new file mode 100644 index 0000000000..167305593c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MultiplayerHelper.kt @@ -0,0 +1,21 @@ +package org.yuzu.yuzu_emu.utils + +import android.content.Context +import androidx.preference.PreferenceManager +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.features.settings.model.Settings + +object MultiplayerHelper { + fun initRoom(context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + + if(preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false)) { + val addr = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: "" + val port = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872" + val nickname = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: "" + val password = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: "" + + NativeLibrary.connectToRoom(nickname, addr, port.toIntOrNull() ?: 0, password) + } + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NetworkHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NetworkHelper.kt index bfb80b5014..0e7ca5ba79 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NetworkHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NetworkHelper.kt @@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.utils import android.content.Context import android.net.ConnectivityManager -import androidx.preference.PreferenceManager object NetworkHelper { fun setRoutes(context: Context) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index b87e04b3d8..8a91c562f7 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -13,6 +13,8 @@ #include #include +#include +#include #include "common/detached_tasks.h" #include "common/dynamic_library.h" @@ -344,6 +346,29 @@ public: return m_software_keyboard; } + void DirectConnectToRoom(const std::string& nickname, const char* server_addr = "127.0.0.1", + u16 server_port = Network::DefaultRoomPort, + const std::string& password = "") { + auto room_network = m_system.GetRoomNetwork(); + + if (const auto member = room_network.GetRoomMember().lock()) { + // Prevent the user from trying to join a room while they are already joining. + if (member->GetState() == Network::RoomMember::State::Joining || member->IsConnected()) { + return; + } else { + member->Join(nickname, server_addr, server_port); + } + } + } + + Network::RoomMember::State GetRoomMemberState() { + if (const auto member = m_system.GetRoomNetwork().GetRoomMember().lock()) { + return member->GetState(); + } else { + return Network::RoomMember::State::Idle; + } + } + private: struct RomMetadata { std::string title; @@ -771,4 +796,45 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code); } +void Java_org_yuzu_yuzu_1emu_NativeLibrary_connectToRoom(JNIEnv* env, jclass clazz, + jstring nickname, + jstring server_addr, + jint server_port, + jstring password) { + EmulationSession::GetInstance().DirectConnectToRoom( + GetJString(env, nickname), + GetJString(env, server_addr).c_str(), + server_port, + GetJString(env, password) + ); +} + +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRoomMemberState(JNIEnv* env, jclass clazz) { + auto state = EmulationSession::GetInstance().GetRoomMemberState(); + + std::string state_str{}; + + switch(state) { + using State = Network::RoomMember::State; + + case State::Uninitialized: + state_str = "Uninitialized"; + break; + case State::Idle: + state_str = "Idle"; + break; + case State::Joining: + state_str = "Joining"; + break; + case State::Joined: + state_str = "Joined"; + break; + case State::Moderator: + state_str = "Moderator"; + break; + } + + return env->NewStringUTF(state_str.c_str()); +} + } // extern "C" diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a2358a93ab..350eb1dedb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -84,6 +84,7 @@ Open yuzu folder Manage yuzu\'s internal files Modify the look of the app + Play with your friends! No file manager found Could not open yuzu directory Please locate the user folder with the file manager\'s side panel manually. @@ -155,6 +156,13 @@ Sets the default network route Set network route + + Server address + Port + Nickname + Password + Connect on start + API Accuracy level @@ -211,6 +219,7 @@ Graphics Audio Theme and color + Multiplayer Debug diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index d71cc23a72..7ffe68bf2b 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -81,6 +81,7 @@ void DirectConnectWindow::Connect() { } } } + if (!ui->ip->hasAcceptableInput()) { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID); return;