700 lines
25 KiB
C#
700 lines
25 KiB
C#
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<RadialOption> _options = new List<RadialOption>();
|
|
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;
|
|
|
|
KeyCode key = Settings.Settings.radialMenuKey;
|
|
if (key != KeyCode.None && Input.GetKeyDown(key))
|
|
{
|
|
BuildForCurrentScene();
|
|
}
|
|
|
|
if (key != KeyCode.None && Input.GetKeyUp(key) && _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}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|