diff --git a/DevourClient/ClientMain.cs b/DevourClient/ClientMain.cs index 0640e32..b4f0903 100644 --- a/DevourClient/ClientMain.cs +++ b/DevourClient/ClientMain.cs @@ -51,6 +51,7 @@ namespace DevourClient public static float exp = 1000f; public static bool _walkInLobby = false; public static bool infinite_mirrors = false; + static bool radialMenuEnabled = true; static bool player_esp = false; static bool player_skel_esp = false; static bool player_snapline = false; @@ -188,11 +189,9 @@ namespace DevourClient fly = !fly; } - // Recall to base position (B key, only if enabled) - if (recallEnabled && Input.GetKeyDown(KeyCode.B) && Player.IsInGameOrLobby()) - { - Helpers.RecallHelper.RecallToBase(); - } + // Z-key radial menu logic is managed by RadialMenuManager + RadialMenuManager.Enabled = radialMenuEnabled; + RadialMenuManager.HandleUpdate(); if (Player.IsInGameOrLobby()) { @@ -607,12 +606,15 @@ namespace DevourClient } } - if (crosshair && in_game_cache) - { - const float crosshairSize = 4; + // End of EventType.Repaint branch + } - float xMin = (Settings.Settings.width) - (crosshairSize / 2); - float yMin = (Settings.Settings.height) - (crosshairSize / 2); + if (crosshair && in_game_cache) + { + const float crosshairSize = 4f; + + float xMin = Settings.Settings.width - (crosshairSize / 2f); + float yMin = Settings.Settings.height - (crosshairSize / 2f); if (crosshairTexture == null) { @@ -622,7 +624,8 @@ namespace DevourClient GUI.DrawTexture(new Rect(xMin, yMin, crosshairSize, crosshairSize), crosshairTexture); } - } + // Radial menu rendering + RadialMenuManager.HandleOnGUI(); if (Settings.Settings.menu_enable) { @@ -2027,15 +2030,15 @@ namespace DevourClient _walkInLobby = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 155, 140, 20), _walkInLobby, MultiLanguageSystem.Translate("Walk In Lobby")); _IsAutoRespawn = GUI.Toggle(new Rect(Settings.Settings.x + 160, Settings.Settings.y + 155, 140, 20), _IsAutoRespawn, MultiLanguageSystem.Translate("Auto Respawn")); - - fly = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 190, 100, 20), fly, MultiLanguageSystem.Translate("Fly")); - if (GUI.Button(new Rect(Settings.Settings.x + 120, Settings.Settings.y + 190, 60, 20), Settings.Settings.flyKey.ToString())) + + fly = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 180, 100, 20), fly, MultiLanguageSystem.Translate("Fly")); + if (GUI.Button(new Rect(Settings.Settings.x + 120, Settings.Settings.y + 180, 60, 20), Settings.Settings.flyKey.ToString())) { Settings.Settings.flyKey = Settings.Settings.GetKey(); } - GUI.Label(new Rect(Settings.Settings.x + 20, Settings.Settings.y + 215, 80, 20), MultiLanguageSystem.Translate("Fly Speed") + ":"); - fly_speed = GUI.HorizontalSlider(new Rect(Settings.Settings.x + 100, Settings.Settings.y + 220, 150, 10), fly_speed, 5f, 20f); - GUI.Label(new Rect(Settings.Settings.x + 260, Settings.Settings.y + 215, 50, 20), ((int)fly_speed).ToString()); + GUI.Label(new Rect(Settings.Settings.x + 20, Settings.Settings.y + 205, 80, 20), MultiLanguageSystem.Translate("Fly Speed") + ":"); + fly_speed = GUI.HorizontalSlider(new Rect(Settings.Settings.x + 100, Settings.Settings.y + 210, 150, 10), fly_speed, 5f, 20f); + GUI.Label(new Rect(Settings.Settings.x + 260, Settings.Settings.y + 205, 50, 20), ((int)fly_speed).ToString()); spoofLevel = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 250, 200, 20), spoofLevel, MultiLanguageSystem.Translate("Spoof Level")); GUI.Label(new Rect(Settings.Settings.x + 20, Settings.Settings.y + 275, 80, 20), MultiLanguageSystem.Translate("Level") + ":"); @@ -2053,8 +2056,8 @@ namespace DevourClient GUI.Label(new Rect(Settings.Settings.x + 260, Settings.Settings.y + 395, 50, 20), ((int)_PlayerSpeedMultiplier).ToString()); showCoordinates = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 430, 200, 20), showCoordinates, MultiLanguageSystem.Translate("Show Coordinates")); - - recallEnabled = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 455, 200, 20), recallEnabled, MultiLanguageSystem.Translate("Recall (B)")); + + radialMenuEnabled = GUI.Toggle(new Rect(Settings.Settings.x + 10, Settings.Settings.y + 455, 200, 20), radialMenuEnabled, MultiLanguageSystem.Translate("Radial Menu (Z)")); // Display player coordinates at the bottom of Misc tab if (showCoordinates && Player.IsInGameOrLobby()) diff --git a/DevourClient/ESP/ItemESPConfig.cs b/DevourClient/ESP/ItemESPConfig.cs index 3d85e24..85ca850 100644 --- a/DevourClient/ESP/ItemESPConfig.cs +++ b/DevourClient/ESP/ItemESPConfig.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; namespace DevourClient.ESP { - /// - /// Item ESP configuration management class - dynamically display different item types based on map - /// + // Item ESP configuration management class - dynamically display different item types based on map public static class ItemESPConfig { // ESP type enumeration @@ -260,9 +258,6 @@ namespace DevourClient.ESP { ESPType.Collectables, false } }; - /// - /// Get list of ESP types supported by specified map - /// public static List GetMapESPTypes(string sceneName) { // If in menu, return empty list @@ -284,35 +279,23 @@ namespace DevourClient.ESP }; } - /// - /// Get ESP type enable status - /// public static bool GetESPState(ESPType type) { return espStates.ContainsKey(type) ? espStates[type] : false; } - /// - /// Set ESP type enable status - /// public static void SetESPState(ESPType type, bool enabled) { if (espStates.ContainsKey(type)) espStates[type] = enabled; } - /// - /// Toggle ESP type enable status - /// public static void ToggleESPState(ESPType type) { if (espStates.ContainsKey(type)) espStates[type] = !espStates[type]; } - /// - /// Get ESP type display name (for translation key) - /// public static string GetESPTypeName(ESPType type) { switch (type) @@ -420,9 +403,6 @@ namespace DevourClient.ESP } } - /// - /// Get corresponding ESP type based on item name - /// public static ESPType? GetESPTypeByItemName(string itemName) { if (string.IsNullOrEmpty(itemName)) @@ -617,9 +597,6 @@ namespace DevourClient.ESP } } - /// - /// Check if ESP should be shown for specified item - /// public static bool ShouldShowESP(string itemName) { ESPType? espType = GetESPTypeByItemName(itemName); @@ -629,9 +606,6 @@ namespace DevourClient.ESP return GetESPState(espType.Value); } - /// - /// Reset all ESP states - /// public static void ResetAllStates() { var keys = new List(espStates.Keys); diff --git a/DevourClient/Helpers/RecallHelper.cs b/DevourClient/Helpers/RecallHelper.cs deleted file mode 100644 index a46aa93..0000000 --- a/DevourClient/Helpers/RecallHelper.cs +++ /dev/null @@ -1,74 +0,0 @@ -using UnityEngine; -using MelonLoader; - -namespace DevourClient.Helpers -{ - public static class RecallHelper - { - /// - /// Teleports the local player to the base coordinates of the current map - /// - public static void RecallToBase() - { - try - { - Il2Cpp.NolanBehaviour nb = Player.GetPlayer(); - if (nb == null) - { - MelonLogger.Warning("Player not found!"); - return; - } - - string sceneName = Map.GetActiveScene(); - Vector3 targetPos = Vector3.zero; - string mapName = ""; - - // Map coordinates based on scene name - switch (sceneName) - { - case "Devour": - case "Anna": // Farmhouse - targetPos = new Vector3(5.03f, 4.20f, -50.02f); - mapName = "Farm"; - break; - case "Molly": // Asylum - targetPos = new Vector3(17.52f, 1.38f, 7.04f); - mapName = "Asylum"; - break; - case "Inn": - targetPos = new Vector3(3.53f, 0.84f, 2.47f); - mapName = "Inn"; - break; - case "Town": - targetPos = new Vector3(-63.51f, 10.88f, -12.32f); - mapName = "Town"; - break; - case "Slaughterhouse": - targetPos = new Vector3(6.09f, 0.70f, -17.58f); - mapName = "Slaughterhouse"; - break; - case "Manor": - targetPos = new Vector3(3.67f, 1.32f, -23.34f); - mapName = "Manor"; - break; - case "Carnival": - targetPos = new Vector3(-91.46f, 8.13f, -24.51f); - mapName = "Carnival"; - break; - default: - MelonLogger.Warning($"Teleport not available for scene: {sceneName}"); - return; - } - - // Teleport player to target position - nb.locomotion.SetPosition(targetPos, false); - MelonLogger.Msg($"Teleported to {mapName} coordinates: X:{targetPos.x:F2} Y:{targetPos.y:F2} Z:{targetPos.z:F2}"); - } - catch (System.Exception ex) - { - MelonLogger.Error($"Failed to teleport: {ex.Message}"); - } - } - } -} - diff --git a/DevourClient/Helpers/ReviveHelper.cs b/DevourClient/Helpers/ReviveHelper.cs index 998d563..2a3360f 100644 --- a/DevourClient/Helpers/ReviveHelper.cs +++ b/DevourClient/Helpers/ReviveHelper.cs @@ -5,17 +5,13 @@ using UnityEngine; namespace DevourClient.Helpers { - /// - /// Shared revive utilities. When running as host we mirror DevourX's revive flow. - /// + // Shared revive utilities. When running as host we mirror DevourX's revive flow. public static class ReviveHelper { private static readonly Vector3 HostFallbackPosition = new Vector3(0f, -150f, 0f); - /// - /// Try to revive the provided NolanBehaviour using host-specific logic first, - /// then fall back to the standard interactable flow. - /// + // Try to revive the provided NolanBehaviour using host-specific logic first, + // then fall back to the standard interactable flow. public static bool TryRevive(Il2Cpp.NolanBehaviour target) { if (target == null || target.gameObject == null) @@ -88,11 +84,9 @@ namespace DevourClient.Helpers return false; } - /// - /// Legacy revive method migrated from StateHelper.BasePlayer.Revive(). - /// This method preserves the original implementation from StateHelper. - /// - /// The GameObject of the player to revive + // Legacy revive method migrated from StateHelper.BasePlayer.Revive(). + // This method preserves the original implementation from StateHelper. + // targetGameObject: The GameObject of the player to revive. public static void ReviveLegacy(GameObject targetGameObject) { if (targetGameObject == null) diff --git a/DevourClient/Localization/Translations/ChineseTranslations.cs b/DevourClient/Localization/Translations/ChineseTranslations.cs index 171264e..6a2cdcd 100644 --- a/DevourClient/Localization/Translations/ChineseTranslations.cs +++ b/DevourClient/Localization/Translations/ChineseTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "老鼠透视" }, { "Region", "区域" }, { "Revive", "复活" }, + { "Radial Menu (Z)", "轮盘菜单 (Z)" }, { "Ritual Book", "仪式书" }, { "Rose", "玫瑰" }, { "Ritual Book ESP", "仪式书透视" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "传送到 Azazel" }, { "TV", "电视" }, { "Teleport Keys", "传送钥匙" }, - { "Recall (B)", "回城 (B)" }, { "Teleport to", "传送至" }, + { "TP Base", "传送至基地" }, + { "TP Altar", "传送至祭坛" }, + { "TP Basin", "传送至水池" }, + { "TP Fountain", "传送至喷泉" }, { "Ticket", "票券" }, { "Town", "小镇" }, { "TownDoor", "小镇门" }, diff --git a/DevourClient/Localization/Translations/EnglishTranslations.cs b/DevourClient/Localization/Translations/EnglishTranslations.cs index 81e2652..8b757cc 100644 --- a/DevourClient/Localization/Translations/EnglishTranslations.cs +++ b/DevourClient/Localization/Translations/EnglishTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "Rat ESP" }, { "Region", "Region" }, { "Revive", "Revive" }, + { "Radial Menu (Z)", "Radial Menu (Z)" }, { "Ritual Book", "Ritual Book" }, { "Rose", "Rose" }, { "Ritual Book ESP", "Ritual Book ESP" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP to Azazel" }, { "TV", "TV" }, { "Teleport Keys", "Teleport Keys" }, - { "Recall (B)", "Recall (B)" }, { "Teleport to", "Teleport to" }, + { "TP Base", "TP Base" }, + { "TP Altar", "TP Altar" }, + { "TP Basin", "TP Basin" }, + { "TP Fountain", "TP Fountain" }, { "Ticket", "Ticket" }, { "Town", "Town" }, { "TownDoor", "TownDoor" }, diff --git a/DevourClient/Localization/Translations/FrenchTranslations.cs b/DevourClient/Localization/Translations/FrenchTranslations.cs index 7cd018d..aec9591 100644 --- a/DevourClient/Localization/Translations/FrenchTranslations.cs +++ b/DevourClient/Localization/Translations/FrenchTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "ESP rat" }, { "Region", "Région" }, { "Revive", "Réanimer" }, + { "Radial Menu (Z)", "Menu radial (Z)" }, { "Ritual Book", "Livre rituel" }, { "Rose", "Rose" }, { "Ritual Book ESP", "ESP livre rituel" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP vers Azazel" }, { "TV", "Télévision" }, { "Teleport Keys", "Téléporter clés" }, - { "Recall (B)", "Retour (B)" }, { "Teleport to", "Téléporter" }, + { "TP Base", "TP base" }, + { "TP Altar", "TP autel" }, + { "TP Basin", "TP bassin" }, + { "TP Fountain", "TP fontaine" }, { "Ticket", "Billet" }, { "Town", "Ville" }, { "TownDoor", "Porte de ville" }, diff --git a/DevourClient/Localization/Translations/GermanTranslations.cs b/DevourClient/Localization/Translations/GermanTranslations.cs index ce92af4..da22c2b 100644 --- a/DevourClient/Localization/Translations/GermanTranslations.cs +++ b/DevourClient/Localization/Translations/GermanTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "Ratte-ESP" }, { "Region", "Region" }, { "Revive", "Wiederbeleben" }, + { "Radial Menu (Z)", "Radiales Menü (Z)" }, { "Ritual Book", "Ritualbuch" }, { "Rose", "Rose" }, { "Ritual Book ESP", "Ritualbuch-ESP" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP zu Azazel" }, { "TV", "Fernseher" }, { "Teleport Keys", "Schlüssel teleportieren" }, - { "Recall (B)", "Zurückrufen (B)" }, { "Teleport to", "Teleportieren" }, + { "TP Base", "TP Basis" }, + { "TP Altar", "TP Altar" }, + { "TP Basin", "TP Becken" }, + { "TP Fountain", "TP Brunnen" }, { "Ticket", "Ticket" }, { "Town", "Stadt" }, { "TownDoor", "Stadttür" }, diff --git a/DevourClient/Localization/Translations/ItalianTranslations.cs b/DevourClient/Localization/Translations/ItalianTranslations.cs index 0fc98d0..246a6f7 100644 --- a/DevourClient/Localization/Translations/ItalianTranslations.cs +++ b/DevourClient/Localization/Translations/ItalianTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "ESP ratto" }, { "Region", "Regione" }, { "Revive", "Rianima" }, + { "Radial Menu (Z)", "Menu radiale (Z)" }, { "Ritual Book", "Libro rituale" }, { "Rose", "Rosa" }, { "Ritual Book ESP", "ESP libro rituale" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP ad Azazel" }, { "TV", "Televisione" }, { "Teleport Keys", "Teletrasporta chiavi" }, - { "Recall (B)", "Richiama (B)" }, { "Teleport to", "Teletrasporta" }, + { "TP Base", "TP Base" }, + { "TP Altar", "TP Altare" }, + { "TP Basin", "TP Bacino" }, + { "TP Fountain", "TP Fontana" }, { "Ticket", "Biglietto" }, { "Town", "Città" }, { "TownDoor", "Porta della città" }, diff --git a/DevourClient/Localization/Translations/JapaneseTranslations.cs b/DevourClient/Localization/Translations/JapaneseTranslations.cs index 549f423..3af4799 100644 --- a/DevourClient/Localization/Translations/JapaneseTranslations.cs +++ b/DevourClient/Localization/Translations/JapaneseTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "ネズミESP" }, { "Region", "地域" }, { "Revive", "蘇生" }, + { "Radial Menu (Z)", "ラジアルメニュー (Z)" }, { "Ritual Book", "儀式の本" }, { "Rose", "バラ" }, { "Ritual Book ESP", "儀式の本ESP" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "Azazelへテレポート" }, { "TV", "テレビ" }, { "Teleport Keys", "鍵をテレポート" }, - { "Recall (B)", "リコール (B)" }, { "Teleport to", "テレポート" }, + { "TP Base", "ベースTP" }, + { "TP Altar", "祭壇TP" }, + { "TP Basin", "水盤TP" }, + { "TP Fountain", "噴水TP" }, { "Ticket", "チケット" }, { "Town", "町" }, { "TownDoor", "町のドア" }, diff --git a/DevourClient/Localization/Translations/KoreanTranslations.cs b/DevourClient/Localization/Translations/KoreanTranslations.cs index c540ead..13ab649 100644 --- a/DevourClient/Localization/Translations/KoreanTranslations.cs +++ b/DevourClient/Localization/Translations/KoreanTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "쥐 ESP" }, { "Region", "지역" }, { "Revive", "부활" }, + { "Radial Menu (Z)", "방사형 메뉴 (Z)" }, { "Ritual Book", "의식서" }, { "Rose", "장미" }, { "Ritual Book ESP", "의식서 ESP" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "Azazel로 텔레포트" }, { "TV", "TV" }, { "Teleport Keys", "열쇠 텔레포트" }, - { "Recall (B)", "리콜 (B)" }, { "Teleport to", "텔레포트" }, + { "TP Base", "기지 TP" }, + { "TP Altar", "제단 TP" }, + { "TP Basin", "대야 TP" }, + { "TP Fountain", "분수 TP" }, { "Ticket", "티켓" }, { "Town", "마을" }, { "TownDoor", "마을 문" }, diff --git a/DevourClient/Localization/Translations/PortugueseTranslations.cs b/DevourClient/Localization/Translations/PortugueseTranslations.cs index bd32b53..b5fae02 100644 --- a/DevourClient/Localization/Translations/PortugueseTranslations.cs +++ b/DevourClient/Localization/Translations/PortugueseTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "ESP rato" }, { "Region", "Região" }, { "Revive", "Reviver" }, + { "Radial Menu (Z)", "Menu radial (Z)" }, { "Ritual Book", "Livro ritual" }, { "Rose", "Rosa" }, { "Ritual Book ESP", "ESP livro ritual" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP para Azazel" }, { "TV", "Televisão" }, { "Teleport Keys", "Teletransportar chaves" }, - { "Recall (B)", "Recuar (B)" }, { "Teleport to", "Teletransportar" }, + { "TP Base", "TP base" }, + { "TP Altar", "TP altar" }, + { "TP Basin", "TP bacia" }, + { "TP Fountain", "TP fonte" }, { "Ticket", "Bilhete" }, { "Town", "Cidade" }, { "TownDoor", "Porta da cidade" }, diff --git a/DevourClient/Localization/Translations/RussianTranslations.cs b/DevourClient/Localization/Translations/RussianTranslations.cs index 4ffcf38..f7e036c 100644 --- a/DevourClient/Localization/Translations/RussianTranslations.cs +++ b/DevourClient/Localization/Translations/RussianTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "ESP крысы" }, { "Region", "Регион" }, { "Revive", "Воскресить" }, + { "Radial Menu (Z)", "Радиальное меню (Z)" }, { "Ritual Book", "Ритуальная книга" }, { "Rose", "Роза" }, { "Ritual Book ESP", "ESP ритуальной книги" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "ТП к Azazel" }, { "TV", "Телевизор" }, { "Teleport Keys", "Телепорт ключей" }, - { "Recall (B)", "Возврат (B)" }, { "Teleport to", "Телепорт" }, + { "TP Base", "ТП база" }, + { "TP Altar", "ТП алтарь" }, + { "TP Basin", "ТП чаша" }, + { "TP Fountain", "ТП фонтан" }, { "Ticket", "Билет" }, { "Town", "Город" }, { "TownDoor", "Дверь города" }, diff --git a/DevourClient/Localization/Translations/SpanishTranslations.cs b/DevourClient/Localization/Translations/SpanishTranslations.cs index 2592a09..0571f84 100644 --- a/DevourClient/Localization/Translations/SpanishTranslations.cs +++ b/DevourClient/Localization/Translations/SpanishTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "ESP rata" }, { "Region", "Región" }, { "Revive", "Revivir" }, + { "Radial Menu (Z)", "Menú radial (Z)" }, { "Ritual Book", "Libro ritual" }, { "Rose", "Rosa" }, { "Ritual Book ESP", "ESP libro ritual" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP a Azazel" }, { "TV", "Televisión" }, { "Teleport Keys", "Teletransportar llaves" }, - { "Recall (B)", "Regresar (B)" }, { "Teleport to", "Teletransportar" }, + { "TP Base", "TP base" }, + { "TP Altar", "TP altar" }, + { "TP Basin", "TP pila" }, + { "TP Fountain", "TP fuente" }, { "Ticket", "Boleto" }, { "Town", "Pueblo" }, { "TownDoor", "Puerta de pueblo" }, diff --git a/DevourClient/Localization/Translations/VietnameseTranslations.cs b/DevourClient/Localization/Translations/VietnameseTranslations.cs index 1ad95d5..b58dc59 100644 --- a/DevourClient/Localization/Translations/VietnameseTranslations.cs +++ b/DevourClient/Localization/Translations/VietnameseTranslations.cs @@ -207,6 +207,7 @@ namespace DevourClient.Localization.Translations { "Rat ESP", "Chuột ESP" }, { "Region", "Khu vực" }, { "Revive", "Hồi sinh" }, + { "Radial Menu (Z)", "Menu vòng tròn (Z)" }, { "Ritual Book", "Sách nghi lễ" }, { "Rose", "Hoa hồng" }, { "Ritual Book ESP", "Sách nghi lễ ESP" }, @@ -253,8 +254,11 @@ namespace DevourClient.Localization.Translations { "TP to Azazel", "TP đến Azazel" }, { "TV", "TV" }, { "Teleport Keys", "Phím dịch chuyển" }, - { "Recall (B)", "Triệu hồi (B)" }, { "Teleport to", "Dịch chuyển đến" }, + { "TP Base", "TP căn cứ" }, + { "TP Altar", "TP bàn thờ" }, + { "TP Basin", "TP bể nước" }, + { "TP Fountain", "TP đài phun nước" }, { "Ticket", "Vé" }, { "Town", "Town" }, { "TownDoor", "TownDoor" }, diff --git a/DevourClient/Network/LobbyManager.cs b/DevourClient/Network/LobbyManager.cs index ee35181..747e5b6 100644 --- a/DevourClient/Network/LobbyManager.cs +++ b/DevourClient/Network/LobbyManager.cs @@ -8,17 +8,13 @@ using UnityEngine; namespace DevourClient.Network { - /// - /// Lobby creation and management class - /// + // Lobby creation and management class public static class LobbyManager { - /// - /// Create game lobby/room - /// - /// Region code (e.g.: "eu", "us", "asia", "usw", "sa", "jp", "au", "ru", "in", "kr") - /// Maximum player limit (1-64) - /// Whether this is a private room + // Create game lobby/room. + // regionCode: Region code (e.g.: "eu", "us", "asia", "usw", "sa", "jp", "au", "ru", "in", "kr"). + // lobbyLimit: Maximum player limit (1-64). + // isPrivate: Whether this is a private room. public static void CreateLobby(string regionCode, int lobbyLimit, bool isPrivate) { try @@ -96,9 +92,6 @@ namespace DevourClient.Network } } - /// - /// Get PhotonRegion object for specified region - /// private static PhotonRegion GetPhotonRegion(string regionCode) { try @@ -113,9 +106,6 @@ namespace DevourClient.Network } } - /// - /// Find Menu controller - /// private static Il2CppHorror.Menu FindMenuController() { try @@ -142,9 +132,6 @@ namespace DevourClient.Network } } - /// - /// Check if in game - /// private static bool IsInGame() { try @@ -159,9 +146,6 @@ namespace DevourClient.Network } } - /// - /// Force start lobby game (host only) - /// public static void ForceLobbyStart() { try @@ -189,9 +173,6 @@ namespace DevourClient.Network } } - /// - /// Show message box - /// public static void ShowMessageBox(string message) { try diff --git a/DevourClient/RadialMenuManager.cs b/DevourClient/RadialMenuManager.cs new file mode 100644 index 0000000..9eda661 --- /dev/null +++ b/DevourClient/RadialMenuManager.cs @@ -0,0 +1,698 @@ +using System; +using System.Collections.Generic; +using MelonLoader; +using UnityEngine; +using DevourClient.Helpers; +using DevourClient.Localization; +using System.Globalization; + +namespace DevourClient +{ + // Manages the Z-key radial menu: building options, drawing the UI, and executing actions. + // Public static methods are called from the appropriate ClientMain lifecycle hooks. + internal static class RadialMenuManager + { + private static bool _active; + private static int _selectedIndex = -1; + private static readonly List _options = new List(); + private static bool _cursorStateStored; + private static bool _prevCursorVisible; + private static CursorLockMode _prevCursorLockState; + private static bool _enabled = true; // Controls whether the radial menu is enabled + + // Material used to draw radial arcs (GL immediate mode) + private static Material _radialMaterial; + + // Public property to enable/disable the radial menu + public static bool Enabled + { + get => _enabled; + set => _enabled = value; + } + + private enum RadialActionType + { + None, + SpawnItem, + TeleportBase, + TeleportFixedPoint + } + + private class RadialOption + { + public string Label = string.Empty; + public RadialActionType ActionType = RadialActionType.None; + public string Payload = string.Empty; + } + + // Called from Update: handles input, builds / closes the radial menu and executes actions. + public static void HandleUpdate() + { + if (!_enabled || !Player.IsInGameOrLobby()) + return; + + if (Input.GetKeyDown(KeyCode.Z)) + { + BuildForCurrentScene(); + } + + if (Input.GetKeyUp(KeyCode.Z) && _active) + { + if (_selectedIndex >= 0 && _selectedIndex < _options.Count) + { + ExecuteOption(_options[_selectedIndex]); + } + + _active = false; + _selectedIndex = -1; + _options.Clear(); + + RestoreCursorState(); + } + } + + // Called from OnGUI: draws the radial menu if it is active. + public static void HandleOnGUI() + { + if (!_enabled || !_active || !Player.IsInGameOrLobby()) + return; + + Draw(); + } + + private static void BuildForCurrentScene() + { + _options.Clear(); + _selectedIndex = -1; + + string sceneName = Helpers.Map.GetActiveScene(); + if (string.IsNullOrEmpty(sceneName) || sceneName == "Menu") + { + _active = false; + return; + } + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("First aid"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalFirstAid" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Battery"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalBattery" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Base"), + ActionType = RadialActionType.TeleportBase, + Payload = string.Empty + }); + + switch (sceneName) + { + case "Devour": + case "Anna": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Altar"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "8.57 0.01 -65.19" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Hay"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalHay" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Gasoline"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalGasoline" + }); + break; + + case "Molly": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Altar"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "18.12 -8.80 21.06" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Fuse"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalFuse" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("RottenFood"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalRottenFood" + }); + break; + + case "Inn": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Fountain"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "-3.43 0.06 24.31" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Bleach"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalBleach" + }); + break; + + case "Town": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Altar"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "-56.88 7.17 -34.51" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Matchbox"), + ActionType = RadialActionType.SpawnItem, + Payload = "Matchbox-3" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Gasoline"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalGasoline" + }); + + break; + + case "Slaughterhouse": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Altar"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "26.68 4.01 -9.27" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Bone"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalBone" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Gasoline"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalGasoline" + }); + break; + + case "Manor": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Basin"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "38.93 -4.62 -3.86" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Bleach"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalBleach" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Cake"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalCake" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Spade"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalSpade" + }); + break; + + case "Carnival": + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("TP Altar"), + ActionType = RadialActionType.TeleportFixedPoint, + Payload = "-114.65 4.07 -4.12" + }); + + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("Coin"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalCoin" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("MusicBox"), + ActionType = RadialActionType.SpawnItem, + Payload = "MusicBox-Idle" + }); + _options.Add(new RadialOption + { + Label = MultiLanguageSystem.Translate("DollHead"), + ActionType = RadialActionType.SpawnItem, + Payload = "SurvivalDollHead" + }); + break; + } + + if (_options.Count == 0) + { + _active = false; + return; + } + + _active = true; + HideCursorForRadial(); + } + + private static void Draw() + { + Event e = Event.current; + if (e == null) + return; + + bool isRepaint = e.type == EventType.Repaint; + + float centerX = Screen.width / 2f; + float centerY = Screen.height / 2f; + Vector2 center = new Vector2(centerX, centerY); + + float radiusOuter = 140f; + float radiusInner = 40f; + + Vector2 mouse = e.mousePosition; + Vector2 dir = mouse - center; + float dist = dir.magnitude; + + _selectedIndex = -1; + + if (dist >= radiusInner && dist <= radiusOuter && _options.Count > 0) + { + // Normalize angles: convert GUI coordinates (y down) to math coordinates (y up) + // and treat "up" as 0° increasing clockwise. + Vector2 upDir = new Vector2(dir.x, -dir.y); + float mathAngle = Mathf.Atan2(upDir.y, upDir.x); // [-PI, PI], 0 is on the right, counterclockwise is positive + + float logicalAngle = (Mathf.PI / 2f) - mathAngle; + if (logicalAngle < 0f) + { + logicalAngle += Mathf.PI * 2f; + } + + float logicalSectorAngle = (Mathf.PI * 2f) / _options.Count; + int index = Mathf.Clamp(Mathf.FloorToInt(logicalAngle / logicalSectorAngle), 0, _options.Count - 1); + _selectedIndex = index; + } + + int count = _options.Count; + if (count == 0) + return; + + if (isRepaint) + { + EnsureRadialMaterial(); + DrawFilledCircle(center, radiusOuter + 6f, new Color(0f, 0f, 0f, 0.55f)); + } + + // Sector layout and label radius + float logicalAnglePerSector = (Mathf.PI * 2f) / count; + float radiusFactor; + if (count <= 6) + radiusFactor = 0.55f; + else if (count == 7) + radiusFactor = 0.48f; + else if (count <= 8) + radiusFactor = 0.5f; + else + radiusFactor = 0.45f; + float labelRadius = radiusInner + (radiusOuter - radiusInner) * radiusFactor; + + if (isRepaint) + { + for (int i = 0; i < count; i++) + { + float logicalStart = logicalAnglePerSector * i; + float logicalEnd = logicalAnglePerSector * (i + 1); + + float displayStart = (Mathf.PI / 2f) - logicalStart; + float displayEnd = (Mathf.PI / 2f) - logicalEnd; + + Color sectorColor = (i == _selectedIndex) + ? new Color(0.15f, 0.7f, 0.3f, 0.8f) + : new Color(0.1f, 0.1f, 0.1f, 0.7f); + + DrawFilledSector(center, radiusInner, radiusOuter, displayStart, displayEnd, sectorColor); + } + } + + GUIStyle labelStyle = new GUIStyle(GUI.skin.label) + { + alignment = TextAnchor.MiddleCenter, + normal = { textColor = Color.white }, + fontSize = (count <= 6) ? 14 : (count <= 8 ? 12 : 10), + wordWrap = true + }; + + for (int i = 0; i < count; i++) + { + float logicalMid = logicalAnglePerSector * (i + 0.5f); + float displayMid = (Mathf.PI / 2f) - logicalMid; + + float lx = centerX + Mathf.Cos(displayMid) * labelRadius; + float ly = centerY - Mathf.Sin(displayMid) * labelRadius; + + float arcLength = logicalAnglePerSector * labelRadius; + float arcFactor; + float minWidth; + float maxWidth; + + if (count <= 6) + { + arcFactor = 0.8f; + minWidth = 60f; + maxWidth = 120f; + } + else if (count == 7) + { + arcFactor = 0.6f; + minWidth = 45f; + maxWidth = 75f; + } + else if (count <= 8) + { + arcFactor = 0.65f; + minWidth = 45f; + maxWidth = 80f; + } + else + { + arcFactor = 0.55f; + minWidth = 40f; + maxWidth = 70f; + } + + float baseWidth = arcLength * arcFactor; + + float labelWidth = Mathf.Clamp(baseWidth, minWidth, maxWidth); + float labelHeight = 32f; + + Rect labelRect = new Rect(lx - labelWidth / 2f, ly - labelHeight / 2f, labelWidth, labelHeight); + GUI.Label(labelRect, _options[i].Label, labelStyle); + } + } + + private static void EnsureRadialMaterial() + { + if (_radialMaterial != null) + return; + + Shader shader = Shader.Find("Hidden/Internal-Colored"); + if (shader == null) + return; + + _radialMaterial = new Material(shader) + { + hideFlags = HideFlags.HideAndDontSave + }; + _radialMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + _radialMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + _radialMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off); + _radialMaterial.SetInt("_ZWrite", 0); + } + + private static void DrawFilledCircle(Vector2 center, float radius, Color color) + { + if (_radialMaterial == null) + return; + + _radialMaterial.SetPass(0); + GL.PushMatrix(); + GL.LoadPixelMatrix(0, Screen.width, Screen.height, 0); + + GL.Begin(GL.TRIANGLES); + GL.Color(color); + + const int steps = 64; + for (int i = 0; i < steps; i++) + { + float t0 = (float)i / steps; + float t1 = (float)(i + 1) / steps; + + float ang0 = t0 * Mathf.PI * 2f; + float ang1 = t1 * Mathf.PI * 2f; + + float x0 = center.x + Mathf.Cos(ang0) * radius; + float y0 = center.y - Mathf.Sin(ang0) * radius; + float x1 = center.x + Mathf.Cos(ang1) * radius; + float y1 = center.y - Mathf.Sin(ang1) * radius; + + GL.Vertex3(center.x, center.y, 0f); + GL.Vertex3(x0, y0, 0f); + GL.Vertex3(x1, y1, 0f); + } + + GL.End(); + GL.PopMatrix(); + } + + private static void DrawFilledSector(Vector2 center, float innerRadius, float outerRadius, + float startAngle, float endAngle, Color color) + { + if (_radialMaterial == null) + return; + + _radialMaterial.SetPass(0); + GL.PushMatrix(); + GL.LoadPixelMatrix(0, Screen.width, Screen.height, 0); + + GL.Begin(GL.TRIANGLES); + GL.Color(color); + + int steps = Mathf.Max(8, Mathf.CeilToInt(Mathf.Abs(endAngle - startAngle) / (Mathf.PI / 24f))); + float delta = (endAngle - startAngle) / steps; + + for (int i = 0; i < steps; i++) + { + float a0 = startAngle + delta * i; + float a1 = startAngle + delta * (i + 1); + + Vector2 o0 = new Vector2( + center.x + Mathf.Cos(a0) * outerRadius, + center.y - Mathf.Sin(a0) * outerRadius); + Vector2 o1 = new Vector2( + center.x + Mathf.Cos(a1) * outerRadius, + center.y - Mathf.Sin(a1) * outerRadius); + + Vector2 i0 = new Vector2( + center.x + Mathf.Cos(a0) * innerRadius, + center.y - Mathf.Sin(a0) * innerRadius); + Vector2 i1 = new Vector2( + center.x + Mathf.Cos(a1) * innerRadius, + center.y - Mathf.Sin(a1) * innerRadius); + + GL.Vertex3(o0.x, o0.y, 0f); + GL.Vertex3(o1.x, o1.y, 0f); + GL.Vertex3(i1.x, i1.y, 0f); + + GL.Vertex3(o0.x, o0.y, 0f); + GL.Vertex3(i1.x, i1.y, 0f); + GL.Vertex3(i0.x, i0.y, 0f); + } + + GL.End(); + GL.PopMatrix(); + } + + private static void ExecuteOption(RadialOption option) + { + if (option == null || option.ActionType == RadialActionType.None) + return; + + try + { + switch (option.ActionType) + { + case RadialActionType.SpawnItem: + if (string.IsNullOrEmpty(option.Payload)) + return; + + ClientMain_HandleItemCarry(option.Payload); + break; + + case RadialActionType.TeleportBase: + TeleportToBase(); + break; + + case RadialActionType.TeleportFixedPoint: + TeleportToFixedPoint(option.Payload); + break; + } + } + catch (Exception ex) + { + MelonLogger.Error($"Radial option execution failed: {ex.Message}"); + } + } + + private static void HideCursorForRadial() + { + if (!_cursorStateStored) + { + _prevCursorVisible = Cursor.visible; + _prevCursorLockState = Cursor.lockState; + _cursorStateStored = true; + } + + Cursor.lockState = CursorLockMode.None; + Cursor.visible = true; + } + + private static void RestoreCursorState() + { + if (!_cursorStateStored) + return; + + Cursor.lockState = _prevCursorLockState; + Cursor.visible = _prevCursorVisible; + _cursorStateStored = false; + } + + // Calls ClientMain.HandleItemCarry via reflection to avoid tight coupling, + // and falls back to Hacks.Misc.CarryObject if that fails. + private static void ClientMain_HandleItemCarry(string payload) + { + try + { + var type = typeof(ClientMain); + var method = type.GetMethod("HandleItemCarry", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + if (method != null) + { + method.Invoke(null, new object[] { payload }); + return; + } + } + catch + { + // ignore and fallback + } + + Hacks.Misc.CarryObject(payload); + } + + // Teleports the player to a fixed world position. + // Payload format: "x y z" using '.' as decimal separator. + private static void TeleportToFixedPoint(string payload) + { + if (string.IsNullOrWhiteSpace(payload)) + return; + + try + { + string[] parts = payload.Split(new[] { ' ', '\t', ',' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 3) + return; + + float x = float.Parse(parts[0], CultureInfo.InvariantCulture); + float y = float.Parse(parts[1], CultureInfo.InvariantCulture); + float z = float.Parse(parts[2], CultureInfo.InvariantCulture); + + Il2Cpp.NolanBehaviour nb = Player.GetPlayer(); + if (nb == null) + return; + + Vector3 target = new Vector3(x, y, z); + nb.TeleportTo(target, Quaternion.identity); + } + catch (Exception ex) + { + MelonLogger.Error($"TeleportToFixedPoint failed: {ex.Message}"); + } + } + + // Teleports the player to the map-specific base coordinates (former RecallToBase logic). + private static void TeleportToBase() + { + try + { + Il2Cpp.NolanBehaviour nb = Player.GetPlayer(); + if (nb == null) + { + MelonLogger.Warning("Player not found!"); + return; + } + + string sceneName = Helpers.Map.GetActiveScene(); + Vector3 targetPos = Vector3.zero; + string mapName = ""; + + switch (sceneName) + { + case "Devour": + case "Anna": // Farmhouse + targetPos = new Vector3(5.03f, 4.20f, -50.02f); + mapName = "Farm"; + break; + case "Molly": // Asylum + targetPos = new Vector3(17.52f, 1.38f, 7.04f); + mapName = "Asylum"; + break; + case "Inn": + targetPos = new Vector3(3.53f, 0.84f, 2.47f); + mapName = "Inn"; + break; + case "Town": + targetPos = new Vector3(-63.51f, 10.88f, -12.32f); + mapName = "Town"; + break; + case "Slaughterhouse": + targetPos = new Vector3(6.09f, 0.70f, -17.58f); + mapName = "Slaughterhouse"; + break; + case "Manor": + targetPos = new Vector3(3.67f, 1.32f, -23.34f); + mapName = "Manor"; + break; + case "Carnival": + targetPos = new Vector3(-91.46f, 8.13f, -24.51f); + mapName = "Carnival"; + break; + default: + MelonLogger.Warning($"Teleport not available for scene: {sceneName}"); + return; + } + + nb.locomotion.SetPosition(targetPos, false); + MelonLogger.Msg($"Teleported to {mapName} coordinates: X:{targetPos.x:F2} Y:{targetPos.y:F2} Z:{targetPos.z:F2}"); + } + catch (Exception ex) + { + MelonLogger.Error($"Failed to teleport to base: {ex.Message}"); + } + } + } +} + + diff --git a/README.md b/README.md index a09d529..5fa30b1 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,9 @@ If you want to modify and develop the code, please follow the [Building from sou 4、运行devour → 如果安装成功,你会看到一个windows窗口进行各类安装提示后,自动进入游戏。点击insert键即可打开和关闭devourclient窗口 -**注意:**有些电脑在安装melonloader之后,会出现fatal error的提示,这个我目前并没有碰到过。但是出现这个提示的主要原因,基本是melonloader安装过程中,提取到devour根目录的melonloader文件夹里的文件出现了问题,比较简单的解决办法就是(1)在别人的同系统同位宽(x86,x32)的电脑里拷贝出来他的melonloader文件夹,然后直接粘贴到自己的电脑里。(2)将melonloader文件夹完全删除,然后重装。(3)在直到游戏完全运行,菜单正常工作之前,保持VPN线路通畅。 +注意:有些电脑在安装melonloader之后,会出现fatal error的提示,这个我目前并没有碰到过。但是出现这个提示的主要原因,基本是melonloader安装过程中,提取到devour根目录的melonloader文件夹里的文件出现了问题,比较简单的解决办法就是(1)在别人的同系统同位宽(x86,x32)的电脑里拷贝出来他的melonloader文件夹,然后直接粘贴到自己的电脑里。(2)将melonloader文件夹完全删除,然后重装。(3)在直到游戏完全运行,菜单正常工作之前,保持VPN线路通畅。 -**注意:**如果在加载时提示 “0 mod”,请检查你的dll文件是否正常,是否已经将dll文件放置到devour的“mods”文件夹中。 +注意:如果在加载时提示 “0 mod”,请检查你的dll文件是否正常,是否已经将dll文件放置到devour的“mods”文件夹中。 如果你想要对代码进行修改和开发,请按照下面的 [building from source](#building-from-source) 的步骤,逐步进行