mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 14:29:25 +00:00
[OriginShift] Initial Fuckup
This commit is contained in:
parent
14ab184db7
commit
2375678a59
33 changed files with 2107 additions and 29 deletions
135
OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs
Normal file
135
OriginShift/Integrations/BTKUI/BtkUiAddon_CAT_OriginShiftMod.cs
Normal file
|
@ -0,0 +1,135 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using NAK.OriginShift;
|
||||
|
||||
namespace NAK.OriginShiftMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static Category _ourCategory;
|
||||
|
||||
private static Button _ourMainButton;
|
||||
private static bool _isForcedMode;
|
||||
|
||||
private static ToggleButton _ourToggle;
|
||||
|
||||
private static void Setup_OriginShiftModCategory(Page page)
|
||||
{
|
||||
// dear category
|
||||
_ourCategory = AddMelonCategory(ref page, ModSettings.OSM_SettingsCategory);
|
||||
|
||||
// the button
|
||||
_ourMainButton = _ourCategory.AddButton(string.Empty, string.Empty, string.Empty, ButtonStyle.TextOnly);
|
||||
_ourMainButton.OnPress += OnMainButtonClick;
|
||||
SetButtonState(OriginShiftManager.OriginShiftState.Inactive); // default state
|
||||
|
||||
// compatibility mode
|
||||
_ourToggle = _ourCategory.AddToggle(ModSettings.EntryCompatibilityMode.DisplayName,
|
||||
ModSettings.EntryCompatibilityMode.Description, ModSettings.EntryCompatibilityMode.Value);
|
||||
_ourToggle.OnValueUpdated += OnCompatibilityModeToggle;
|
||||
|
||||
// listen for state changes
|
||||
OriginShiftManager.OnStateChanged += OnOriginShiftStateChanged;
|
||||
}
|
||||
|
||||
#region Category Actions
|
||||
|
||||
private static void UpdateCategoryModUserCount()
|
||||
{
|
||||
int modUsers = 1; // we are always here :3
|
||||
int playerCount = CVRPlayerManager.Instance.NetworkPlayers.Count + 1; // +1 for us :3
|
||||
_ourCategory.CategoryName = $"{ModSettings.OSM_SettingsCategory} ({modUsers}/{playerCount})";
|
||||
}
|
||||
|
||||
#endregion Category Actions
|
||||
|
||||
#region Button Actions
|
||||
|
||||
private static void SetButtonState(OriginShiftManager.OriginShiftState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
default:
|
||||
case OriginShiftManager.OriginShiftState.Inactive:
|
||||
_ourMainButton.ButtonText = "Inactive";
|
||||
_ourMainButton.ButtonIcon = "OSM_Icon_OriginShiftConfig";
|
||||
_ourMainButton.ButtonTooltip = "World does not use Origin Shift.";
|
||||
_ourMainButton.ButtonIcon = "OriginShift-Icon-Inactive";
|
||||
break;
|
||||
case OriginShiftManager.OriginShiftState.Active:
|
||||
_ourMainButton.ButtonText = "Active";
|
||||
_ourMainButton.ButtonIcon = "OSM_Icon_OriginShiftConfig";
|
||||
_ourMainButton.ButtonTooltip = "World uses Origin Shift.";
|
||||
_ourMainButton.ButtonIcon = "OriginShift-Icon-Active";
|
||||
break;
|
||||
case OriginShiftManager.OriginShiftState.Forced:
|
||||
_ourMainButton.ButtonText = "Forced";
|
||||
_ourMainButton.ButtonIcon = "OSM_Icon_OriginShiftCopy";
|
||||
_ourMainButton.ButtonTooltip = "World is forced to use Origin Shift.";
|
||||
_ourMainButton.ButtonIcon = "OriginShift-Icon-Forced";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnMainButtonClick()
|
||||
{
|
||||
// if active, return as world is using Origin Shift
|
||||
if (OriginShiftManager.Instance.CurrentState
|
||||
is OriginShiftManager.OriginShiftState.Active)
|
||||
return;
|
||||
|
||||
if (_isForcedMode)
|
||||
{
|
||||
OriginShiftManager.Instance.ResetManager();
|
||||
}
|
||||
else
|
||||
{
|
||||
QuickMenuAPI.ShowConfirm("Force Origin Shift",
|
||||
"Are you sure you want to force Origin Shift for this world? " +
|
||||
"This is a highly experimental feature that may break the world in unexpected ways!",
|
||||
() =>
|
||||
{
|
||||
OriginShiftManager.Instance.ForceManager();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state)
|
||||
{
|
||||
_isForcedMode = state == OriginShiftManager.OriginShiftState.Forced;
|
||||
SetButtonState(state);
|
||||
SetToggleLocked(_isForcedMode);
|
||||
}
|
||||
|
||||
#endregion Button Actions
|
||||
|
||||
#region Toggle Actions
|
||||
|
||||
private static void SetToggleLocked(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
// lock the toggle
|
||||
_ourToggle.ToggleTooltip = "This setting is locked while Origin Shift is forced in Public instances.";
|
||||
_ourToggle.ToggleValue = true;
|
||||
_ourToggle.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unlock the toggle
|
||||
_ourToggle.ToggleValue = ModSettings.EntryCompatibilityMode.Value;
|
||||
_ourToggle.ToggleTooltip = ModSettings.EntryCompatibilityMode.Description;
|
||||
_ourToggle.Disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCompatibilityModeToggle(bool value)
|
||||
{
|
||||
ModSettings.EntryCompatibilityMode.Value = value;
|
||||
}
|
||||
|
||||
#endregion Toggle Actions
|
||||
}
|
||||
}
|
64
OriginShift/Integrations/BTKUI/BtkuiAddon.cs
Normal file
64
OriginShift/Integrations/BTKUI/BtkuiAddon.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using NAK.OriginShift;
|
||||
|
||||
namespace NAK.OriginShiftMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static Page _miscTabPage;
|
||||
private static string _miscTabElementID;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Prepare_Icons();
|
||||
Setup_OriginShiftTab();
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
private static void Prepare_Icons()
|
||||
{
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Active",
|
||||
GetIconStream("OriginShift-Icon-Active.png"));
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Inactive",
|
||||
GetIconStream("OriginShift-Icon-Inactive.png"));
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "OriginShift-Icon-Forced",
|
||||
GetIconStream("OriginShift-Icon-Forced.png"));
|
||||
}
|
||||
|
||||
private static void Setup_OriginShiftTab()
|
||||
{
|
||||
_miscTabPage = QuickMenuAPI.MiscTabPage;
|
||||
_miscTabElementID = _miscTabPage.ElementID;
|
||||
QuickMenuAPI.UserJoin += OnUserJoinLeave;
|
||||
QuickMenuAPI.UserLeave += OnUserJoinLeave;
|
||||
QuickMenuAPI.OnWorldLeave += OnWorldLeave;
|
||||
|
||||
// // Origin Shift Mod
|
||||
Setup_OriginShiftModCategory(_miscTabPage);
|
||||
//
|
||||
// // Origin Shift Tool
|
||||
// Setup_OriginShiftToolCategory(_miscTabPage);
|
||||
//
|
||||
// // Universal Shifting Settings
|
||||
// Setup_UniversalShiftingSettings(_miscTabPage);
|
||||
//
|
||||
// // Debug Options
|
||||
// Setup_DebugOptionsCategory(_miscTabPage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Player Count Display
|
||||
|
||||
private static void OnWorldLeave()
|
||||
=> UpdateCategoryModUserCount();
|
||||
|
||||
private static void OnUserJoinLeave(CVRPlayerEntity _)
|
||||
=> UpdateCategoryModUserCount();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
70
OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs
Normal file
70
OriginShift/Integrations/BTKUI/BtkuiAddon_Utils.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System.Reflection;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShiftMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
#region Melon Preference Helpers
|
||||
|
||||
private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry<bool> entry)
|
||||
{
|
||||
ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value);
|
||||
toggle.OnValueUpdated += b => entry.Value = b;
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry<float> entry, float min,
|
||||
float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
{
|
||||
SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description,
|
||||
Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
slider.OnValueUpdated += f => entry.Value = f;
|
||||
return slider;
|
||||
}
|
||||
|
||||
private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry<string> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s);
|
||||
return button;
|
||||
}
|
||||
|
||||
private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry<float> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f);
|
||||
return button;
|
||||
}
|
||||
|
||||
private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry<bool> entry, bool showHeader = true)
|
||||
{
|
||||
Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value);
|
||||
category.OnCollapse += b => entry.Value = b;
|
||||
return category;
|
||||
}
|
||||
|
||||
private static Category AddMelonCategory(ref Page page, string displayName, bool showHeader = true)
|
||||
{
|
||||
Category category = page.AddCategory(displayName, showHeader);
|
||||
return category;
|
||||
}
|
||||
|
||||
#endregion Melon Preference Helpers
|
||||
|
||||
#region Icon Utils
|
||||
|
||||
private static Stream GetIconStream(string iconName)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
return assembly.GetManifestResourceStream($"{assemblyName}.Resources.{iconName}");
|
||||
}
|
||||
|
||||
#endregion Icon Utils
|
||||
}
|
||||
}
|
103
OriginShift/Main.cs
Normal file
103
OriginShift/Main.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
#if !UNITY_EDITOR
|
||||
using System.Globalization;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI_RC.Core.Util.AssetFiltering;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using MelonLoader;
|
||||
using NAK.OriginShift.Components;
|
||||
using NAK.OriginShiftMod.Integrations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift;
|
||||
|
||||
// Links I looked at:
|
||||
// Controller/Event Listener Setup: https://manuel-rauber.com/2022/04/06/floating-origin-in-unity/amp/
|
||||
// Move Scene Roots: https://gist.github.com/brihernandez/9ebbaf35070181fa1ee56f9e702cc7a5
|
||||
// Looked cool but didn't really find anything to use: https://docs.coherence.io/coherence-sdk-for-unity/world-origin-shifting
|
||||
// One Day when we move to 2022: https://docs.unity3d.com/6000.0/Documentation/Manual/LightProbes-Moving.html
|
||||
|
||||
public class OriginShiftMod : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
|
||||
#region Melon Mod Overrides
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
|
||||
ModSettings.Initialize();
|
||||
|
||||
ApplyPatches(typeof(Patches.BetterBetterCharacterControllerPatches)); // origin shift monitor
|
||||
ApplyPatches(typeof(Patches.CVRSpawnablePatches)); // components & remote spawnable pos
|
||||
|
||||
// Compatibility Mode
|
||||
ApplyPatches(typeof(Patches.PlayerSetupPatches)); // net ik, camera occlusion culling
|
||||
ApplyPatches(typeof(Patches.Comms_ClientPatches)); // voice pos
|
||||
ApplyPatches(typeof(Patches.CVRSyncHelperPatches)); // spawnable pos
|
||||
ApplyPatches(typeof(Patches.PuppetMasterPatches)); // remote player pos
|
||||
ApplyPatches(typeof(Patches.CVRObjectSyncPatches)); // remote object pos
|
||||
|
||||
ApplyPatches(typeof(Patches.DbJobsAvatarManagerPatches)); // dynamic bones
|
||||
ApplyPatches(typeof(Patches.CVRPortalManagerPatches)); // portals
|
||||
|
||||
ApplyPatches(typeof(Patches.PortableCameraPatches)); // camera occlusion culling
|
||||
ApplyPatches(typeof(Patches.PathingCameraPatches)); // camera occlusion culling
|
||||
|
||||
// add our components to the world whitelist
|
||||
WorldFilter._Base.Add(typeof(OriginShiftController)); // base component
|
||||
WorldFilter._Base.Add(typeof(OriginShiftEventReceiver)); // generic event listener
|
||||
|
||||
WorldFilter._Base.Add(typeof(OriginShiftParticleSystemReceiver)); // particle system
|
||||
WorldFilter._Base.Add(typeof(OriginShiftRigidbodyReceiver)); // rigidbody
|
||||
WorldFilter._Base.Add(typeof(OriginShiftTrailRendererReceiver)); // trail renderer
|
||||
WorldFilter._Base.Add(typeof(OriginShiftTransformReceiver)); // transform
|
||||
|
||||
InitializeIntegration("BTKUILib", BtkUiAddon.Initialize);
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (BetterBetterCharacterController.Instance == null
|
||||
|| !BetterBetterCharacterController.Instance.IsFlying()
|
||||
|| Input.GetKey(KeyCode.Mouse2)
|
||||
|| Cursor.lockState != CursorLockMode.Locked)
|
||||
return;
|
||||
|
||||
BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier = Math.Max(0f,
|
||||
BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier + Input.mouseScrollDelta.y);
|
||||
if (Input.mouseScrollDelta.y != 0f)
|
||||
CohtmlHud.Instance.ViewDropTextImmediate("(Local) ScrollFlight",
|
||||
BetterBetterCharacterController.Instance.worldFlightSpeedMultiplier.ToString(CultureInfo
|
||||
.InvariantCulture), "Speed multiplier");
|
||||
}
|
||||
|
||||
#endregion Melon Mod Overrides
|
||||
|
||||
#region Melon Mod Utilities
|
||||
|
||||
private static void InitializeIntegration(string modName, Action integrationAction)
|
||||
{
|
||||
if (RegisteredMelons.All(it => it.Info.Name != modName))
|
||||
return;
|
||||
|
||||
Logger.Msg($"Initializing {modName} integration.");
|
||||
integrationAction.Invoke();
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Melon Mod Utilities
|
||||
}
|
||||
#endif
|
41
OriginShift/ModSettings.cs
Normal file
41
OriginShift/ModSettings.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using MelonLoader;
|
||||
|
||||
namespace NAK.OriginShift;
|
||||
|
||||
internal static class ModSettings
|
||||
{
|
||||
#region Constants
|
||||
|
||||
internal const string ModName = nameof(OriginShift);
|
||||
internal const string OSM_SettingsCategory = "Origin Shift Mod";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Melon Preferences
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(ModName);
|
||||
|
||||
internal static readonly MelonPreferences_Entry<bool> EntryCompatibilityMode =
|
||||
Category.CreateEntry("EntryCompatibilityMode", true,
|
||||
"Compatibility Mode", description: "Origin Shifts locally, but modifies outbound network messages to be compatible with non-Origin Shifted clients.");
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
#region Settings Managment
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
foreach (MelonPreferences_Entry setting in Category.Entries)
|
||||
setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged);
|
||||
|
||||
OnSettingsChanged();
|
||||
}
|
||||
|
||||
private static void OnSettingsChanged(object oldValue = null, object newValue = null)
|
||||
{
|
||||
OriginShiftManager.CompatibilityMode = EntryCompatibilityMode.Value;
|
||||
}
|
||||
|
||||
#endregion Settings Managment
|
||||
}
|
151
OriginShift/Networking/ModNetwork.cs
Normal file
151
OriginShift/Networking/ModNetwork.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
// using ABI_RC.Core.Networking;
|
||||
// using ABI_RC.Systems.ModNetwork;
|
||||
// using DarkRift;
|
||||
// using UnityEngine;
|
||||
//
|
||||
// namespace NAK.OriginShift.Networking;
|
||||
//
|
||||
// public static class ModNetwork
|
||||
// {
|
||||
// public static bool Debug_NetworkInbound = false;
|
||||
// public static bool Debug_NetworkOutbound = false;
|
||||
//
|
||||
// private static bool _isSubscribedToModNetwork;
|
||||
//
|
||||
// private struct MovementParentSyncData
|
||||
// {
|
||||
// public bool HasSyncedThisData;
|
||||
// public int MarkerHash;
|
||||
// public Vector3 RootPosition;
|
||||
// public Vector3 RootRotation;
|
||||
// // public Vector3 HipPosition;
|
||||
// // public Vector3 HipRotation;
|
||||
// }
|
||||
//
|
||||
// private static MovementParentSyncData _latestMovementParentSyncData;
|
||||
//
|
||||
// #region Constants
|
||||
//
|
||||
// private const string ModId = "MelonMod.NAK.RelativeSync";
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Enums
|
||||
//
|
||||
// private enum MessageType : byte
|
||||
// {
|
||||
// MovementParentOrChair = 0
|
||||
// //RelativePickup = 1,
|
||||
// //RelativeAttachment = 2,
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Mod Network Internals
|
||||
//
|
||||
// internal static void Subscribe()
|
||||
// {
|
||||
// ModNetworkManager.Subscribe(ModId, OnMessageReceived);
|
||||
//
|
||||
// _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId);
|
||||
// if (!_isSubscribedToModNetwork)
|
||||
// Debug.LogError("Failed to subscribe to Mod Network!");
|
||||
// }
|
||||
//
|
||||
// // Called right after NetworkRootDataUpdate.Submit()
|
||||
// internal static void SendRelativeSyncUpdate()
|
||||
// {
|
||||
// if (!_isSubscribedToModNetwork)
|
||||
// return;
|
||||
//
|
||||
// if (_latestMovementParentSyncData.HasSyncedThisData)
|
||||
// return;
|
||||
//
|
||||
// SendMessage(MessageType.MovementParentOrChair, _latestMovementParentSyncData.MarkerHash,
|
||||
// _latestMovementParentSyncData.RootPosition, _latestMovementParentSyncData.RootRotation);
|
||||
//
|
||||
// _latestMovementParentSyncData.HasSyncedThisData = true;
|
||||
// }
|
||||
//
|
||||
// public static void SetLatestRelativeSync(
|
||||
// int markerHash,
|
||||
// Vector3 position, Vector3 rotation)
|
||||
// {
|
||||
// // check if the data has changed
|
||||
// if (_latestMovementParentSyncData.MarkerHash == markerHash
|
||||
// && _latestMovementParentSyncData.RootPosition == position
|
||||
// && _latestMovementParentSyncData.RootRotation == rotation)
|
||||
// return; // no need to update (shocking)
|
||||
//
|
||||
// _latestMovementParentSyncData.HasSyncedThisData = false; // reset
|
||||
// _latestMovementParentSyncData.MarkerHash = markerHash;
|
||||
// _latestMovementParentSyncData.RootPosition = position;
|
||||
// _latestMovementParentSyncData.RootRotation = rotation;
|
||||
// }
|
||||
//
|
||||
// private static void SendMessage(MessageType messageType, int markerHash, Vector3 position, Vector3 rotation)
|
||||
// {
|
||||
// if (!IsConnectedToGameNetwork())
|
||||
// return;
|
||||
//
|
||||
// using ModNetworkMessage modMsg = new(ModId);
|
||||
// modMsg.Write((byte)messageType);
|
||||
// modMsg.Write(markerHash);
|
||||
// modMsg.Write(position);
|
||||
// modMsg.Write(rotation);
|
||||
// modMsg.Send();
|
||||
//
|
||||
// if (Debug_NetworkOutbound)
|
||||
// Debug.Log(
|
||||
// $"[Outbound] MessageType: {messageType}, MarkerHash: {markerHash}, Position: {position}, " +
|
||||
// $"Rotation: {rotation}");
|
||||
// }
|
||||
//
|
||||
// private static void OnMessageReceived(ModNetworkMessage msg)
|
||||
// {
|
||||
// msg.Read(out byte msgTypeRaw);
|
||||
//
|
||||
// if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw))
|
||||
// return;
|
||||
//
|
||||
// switch ((MessageType)msgTypeRaw)
|
||||
// {
|
||||
// case MessageType.MovementParentOrChair:
|
||||
// msg.Read(out int markerHash);
|
||||
// msg.Read(out Vector3 receivedPosition);
|
||||
// msg.Read(out Vector3 receivedRotation);
|
||||
// // msg.Read(out Vector3 receivedHipPosition);
|
||||
// // msg.Read(out Vector3 receivedHipRotation);
|
||||
//
|
||||
// OnNetworkPositionUpdateReceived(msg.Sender, markerHash, receivedPosition, receivedRotation);
|
||||
//
|
||||
// if (Debug_NetworkInbound)
|
||||
// Debug.Log($"[Inbound] Sender: {msg.Sender}, MarkerHash: {markerHash}, " +
|
||||
// $"Position: {receivedPosition}, Rotation: {receivedRotation}");
|
||||
// break;
|
||||
// default:
|
||||
// Debug.LogError($"Invalid message type received from: {msg.Sender}");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Private Methods
|
||||
//
|
||||
// private static bool IsConnectedToGameNetwork()
|
||||
// {
|
||||
// return NetworkManager.Instance != null
|
||||
// && NetworkManager.Instance.GameNetwork != null
|
||||
// && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
|
||||
// }
|
||||
//
|
||||
// private static void OnNetworkPositionUpdateReceived(
|
||||
// string sender, int markerHash,
|
||||
// Vector3 position, Vector3 rotation)
|
||||
// {
|
||||
// RelativeSyncManager.ApplyRelativeSync(sender, markerHash, position, rotation);
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
// }
|
19
OriginShift/OriginShift.csproj
Normal file
19
OriginShift/OriginShift.csproj
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Sprays</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BTKUILib">
|
||||
<HintPath>..\.ManagedLibs\BTKUILib.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\origin-shift-inactive.png" />
|
||||
<EmbeddedResource Include="Resources\OriginShift-Icon-Inactive.png" />
|
||||
<None Remove="Resources\origin-shift-forced.png" />
|
||||
<EmbeddedResource Include="Resources\OriginShift-Icon-Forced.png" />
|
||||
<None Remove="Resources\origin-shift-active.png" />
|
||||
<EmbeddedResource Include="Resources\OriginShift-Icon-Active.png" />
|
||||
</ItemGroup>
|
||||
</Project>
|
133
OriginShift/OriginShift/Components/OriginShiftController.cs
Normal file
133
OriginShift/OriginShift/Components/OriginShiftController.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using UnityEngine;
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
using UnityEngine.SceneManagement;
|
||||
using NAK.OriginShift.Utility;
|
||||
#endif
|
||||
|
||||
// Creator Exposed component
|
||||
|
||||
namespace NAK.OriginShift.Components
|
||||
{
|
||||
public class OriginShiftController : MonoBehaviour
|
||||
{
|
||||
public static OriginShiftController Instance { get; private set; }
|
||||
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Config / Shift Params")]
|
||||
|
||||
[SerializeField] private bool _shiftVertical = true;
|
||||
[SerializeField] [Range(10f, 2500f)] private float _shiftThreshold = 15f;
|
||||
|
||||
[Header("Config / Scene Objects")]
|
||||
|
||||
[SerializeField] private bool _autoMoveSceneRoots = true;
|
||||
[SerializeField] private Transform[] _toShiftTransforms = Array.Empty<Transform>();
|
||||
|
||||
[Header("Config / Additive Objects")]
|
||||
|
||||
[SerializeField] private bool _shiftRemotePlayers = true;
|
||||
[SerializeField] private bool _shiftSpawnedObjects = true;
|
||||
|
||||
#endregion Serialized Fields
|
||||
|
||||
#region Internal Fields
|
||||
|
||||
internal bool IsForced { get; set; }
|
||||
|
||||
#endregion Internal Fields
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
|
||||
public static float ORIGIN_SHIFT_THRESHOLD = 15f;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null
|
||||
&& Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
OriginShiftMod.Logger.Error("Only one OriginShiftController can exist in a scene.");
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// set threshold (we can not support dynamic threshold change)
|
||||
ORIGIN_SHIFT_THRESHOLD = _shiftThreshold;
|
||||
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
OriginShiftManager.Instance.SetupManager(IsForced);
|
||||
|
||||
// if auto, we will just move everything :)
|
||||
if (_autoMoveSceneRoots)
|
||||
GetAllSceneRootTransforms();
|
||||
|
||||
// if we have scene roots, we will anchor all static renderers
|
||||
if (_toShiftTransforms.Length != 0)
|
||||
AnchorAllStaticRenderers();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
OriginShiftManager.Instance.ResetManager();
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void GetAllSceneRootTransforms()
|
||||
{
|
||||
Scene scene = gameObject.scene;
|
||||
var sceneRoots = scene.GetRootGameObjects();
|
||||
_toShiftTransforms = new Transform[sceneRoots.Length + 1]; // +1 for the static batch anchor
|
||||
for (var i = 0; i < sceneRoots.Length; i++) _toShiftTransforms[i] = sceneRoots[i].transform;
|
||||
}
|
||||
|
||||
private void AnchorAllStaticRenderers()
|
||||
{
|
||||
// create an anchor object at 0,0,0
|
||||
Transform anchor = new GameObject("NAK.StaticBatchAnchor").transform;
|
||||
anchor.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
anchor.localScale = Vector3.one;
|
||||
|
||||
// add to end of root transforms
|
||||
_toShiftTransforms[^1] = anchor;
|
||||
|
||||
// crawl all children and find Renderers part of static batch
|
||||
foreach (Transform toShiftTransform in _toShiftTransforms)
|
||||
{
|
||||
var renderers = toShiftTransform.GetComponentsInChildren<Renderer>(true);
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
if (renderer.isPartOfStaticBatch) // access staticBatchRootTransform using reflection and override it
|
||||
RendererReflectionUtility.SetStaticBatchRootTransform(renderer, anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShifted(Vector3 shift)
|
||||
{
|
||||
foreach (Transform toShiftTransform in _toShiftTransforms)
|
||||
{
|
||||
if (toShiftTransform == null) continue; // skip nulls
|
||||
toShiftTransform.position += shift;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
using ABI_RC.Core.Util.AssetFiltering;
|
||||
#endif
|
||||
|
||||
namespace NAK.OriginShift.Components
|
||||
{
|
||||
public class OriginShiftEventReceiver : MonoBehaviour
|
||||
{
|
||||
#region Serialized Fields
|
||||
|
||||
[SerializeField] private UnityEvent _onOriginShifted = new();
|
||||
[SerializeField] private bool _filterChunkBoundary;
|
||||
[SerializeField] private Vector3 _chunkBoundaryMin = Vector3.zero;
|
||||
[SerializeField] private Vector3 _chunkBoundaryMax = Vector3.one;
|
||||
|
||||
#endregion Serialized Fields
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
SharedFilter.SanitizeUnityEvents("OriginShiftEventReceiver", _onOriginShifted);
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
OriginShiftManager.OnOriginShifted += HandleOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= HandleOriginShifted;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void HandleOriginShifted(Vector3 shift)
|
||||
{
|
||||
if (_filterChunkBoundary && !IsWithinChunkBoundary(shift))
|
||||
return;
|
||||
|
||||
// wrap user-defined event because the user can't be trusted
|
||||
try
|
||||
{
|
||||
_onOriginShifted.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("OriginShiftEventReceiver: Exception invoking OnOriginShifted event: " + e, this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsWithinChunkBoundary(Vector3 shift)
|
||||
{
|
||||
return shift.x >= _chunkBoundaryMin.x && shift.x <= _chunkBoundaryMax.x &&
|
||||
shift.y >= _chunkBoundaryMin.y && shift.y <= _chunkBoundaryMax.y &&
|
||||
shift.z >= _chunkBoundaryMin.z && shift.z <= _chunkBoundaryMax.z;
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Components
|
||||
{
|
||||
public class OriginShiftParticleSystemReceiver : MonoBehaviour
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
|
||||
// max particles count cause i said so 2
|
||||
private static readonly ParticleSystem.Particle[] _tempParticles = new ParticleSystem.Particle[10000];
|
||||
|
||||
private ParticleSystem[] _particleSystems;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_particleSystems = GetComponentsInChildren<ParticleSystem>(true);
|
||||
if (_particleSystems.Length == 0)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("OriginShiftParticleSystemReceiver: No ParticleSystems found on GameObject: " + gameObject.name, this);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShifted(Vector3 offset)
|
||||
{
|
||||
foreach (ParticleSystem particleSystem in _particleSystems)
|
||||
ShiftParticleSystem(particleSystem, offset);
|
||||
}
|
||||
|
||||
private static void ShiftParticleSystem(ParticleSystem particleSystem, Vector3 offset)
|
||||
{
|
||||
int particleCount = particleSystem.GetParticles(_tempParticles);
|
||||
for (int i = 0; i < particleCount; i++) _tempParticles[i].position += offset;
|
||||
particleSystem.SetParticles(_tempParticles, particleCount);
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Components
|
||||
{
|
||||
public class OriginShiftRigidbodyReceiver : MonoBehaviour
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
|
||||
private Rigidbody _rigidbody;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_rigidbody = GetComponentInChildren<Rigidbody>();
|
||||
if (_rigidbody == null)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("OriginShiftRigidbodyReceiver: No Rigidbody found on GameObject: " + gameObject.name, this);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShifted(Vector3 shift)
|
||||
{
|
||||
_rigidbody.position += shift;
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Components
|
||||
{
|
||||
public class OriginShiftTrailRendererReceiver : MonoBehaviour
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
|
||||
// max positions count cause i said so
|
||||
private static readonly Vector3[] _tempPositions = new Vector3[10000];
|
||||
|
||||
private TrailRenderer[] _trailRenderers;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_trailRenderers = GetComponentsInChildren<TrailRenderer>(true);
|
||||
if (_trailRenderers.Length == 0)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("OriginShiftTrailRendererReceiver: No TrailRenderers found on GameObject: " + gameObject.name, this);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShifted(Vector3 offset)
|
||||
{
|
||||
foreach (TrailRenderer trailRenderer in _trailRenderers)
|
||||
ShiftTrailRenderer(trailRenderer, offset);
|
||||
}
|
||||
|
||||
private static void ShiftTrailRenderer(TrailRenderer trailRenderer, Vector3 offset)
|
||||
{
|
||||
trailRenderer.GetPositions(_tempPositions);
|
||||
for (var i = 0; i < _tempPositions.Length; i++) _tempPositions[i] += offset;
|
||||
trailRenderer.SetPositions(_tempPositions);
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Components
|
||||
{
|
||||
public class OriginShiftTransformReceiver : MonoBehaviour
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShifted(Vector3 shift)
|
||||
{
|
||||
transform.position += shift;
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Extensions;
|
||||
|
||||
public static class BetterBetterCharacterControllerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Offsets the player by the given vector.
|
||||
/// This is a simple move operation that does not affect velocity, grounded state, movement parent, ect.
|
||||
/// </summary>
|
||||
/// <param name="controller"></param>
|
||||
/// <param name="offset"></param>
|
||||
public static void OffsetBy(this BetterBetterCharacterController controller, Vector3 offset)
|
||||
{
|
||||
controller.MoveTo(PlayerSetup.Instance.GetPlayerPosition() + offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the player to the target position while keeping velocity, grounded state, movement parent, ect.
|
||||
/// Allows moving the player in a way that is meant to be seamless, unlike TeleportTo or SetPosition.
|
||||
/// </summary>
|
||||
/// <param name="controller"></param>
|
||||
/// <param name="targetPos"></param>
|
||||
/// <param name="interpolate"></param>
|
||||
public static void MoveTo(this BetterBetterCharacterController controller, Vector3 targetPos,
|
||||
bool interpolate = false)
|
||||
{
|
||||
// character controller is not built to account for the player's VR offset
|
||||
Vector3 vector = targetPos - PlayerSetup.Instance.GetPlayerPosition();
|
||||
Vector3 vector2 = controller.GetPosition() + vector;
|
||||
|
||||
if (!CVRTools.IsWithinMaxBounds(vector2))
|
||||
{
|
||||
// yeah, ill play your game
|
||||
CommonTools.LogAuto(CommonTools.LogLevelType_t.Warning,
|
||||
"Attempted to move player further than the maximum allowed bounds.", "",
|
||||
"OriginShift/Extensions/BetterBetterCharacterControllerExtensions.cs",
|
||||
"MoveTo", 19);
|
||||
return;
|
||||
}
|
||||
|
||||
controller.TeleportPosition(vector2, interpolate); // move player
|
||||
controller.SetVelocity(controller.characterMovement.velocity); // keep velocity
|
||||
controller.UpdateColliderCenter(vector2, true); // update collider center
|
||||
controller.characterMovement.UpdateCurrentPlatform(); // recalculate stored local offset
|
||||
|
||||
// invoke event so ik can update
|
||||
BetterBetterCharacterController.OnMovementParentMove.Invoke(
|
||||
new BetterBetterCharacterController.PlayerMoveOffset(
|
||||
PlayerSetup.Instance.GetPlayerPosition(),
|
||||
vector,
|
||||
Quaternion.identity));
|
||||
}
|
||||
}
|
19
OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs
Normal file
19
OriginShift/OriginShift/Extensions/PlayerSetupExtensions.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Extensions;
|
||||
|
||||
public static class PlayerSetupExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility method to offset the player's currently stored avatar movement data.
|
||||
/// Needs to be called, otherwise outbound net ik will be one-frame behind on teleport events.
|
||||
/// </summary>
|
||||
/// <param name="playerSetup"></param>
|
||||
/// <param name="offset"></param>
|
||||
public static void OffsetAvatarMovementData(this PlayerSetup playerSetup, Vector3 offset)
|
||||
{
|
||||
playerSetup._playerAvatarMovementData.RootPosition += offset;
|
||||
playerSetup._playerAvatarMovementData.BodyPosition += offset; // why in world space -_-
|
||||
}
|
||||
}
|
50
OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs
Normal file
50
OriginShift/OriginShift/Hacks/OcclusionCullingHack.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Hacks;
|
||||
|
||||
#region Harmony Patches
|
||||
|
||||
internal static class OcclusionCullingPatches
|
||||
{
|
||||
// i wish i had this when working on the head hiding & shadow clones... would have had a much better
|
||||
// method of hiding mesh that wouldn't have broken magica cloth :< (one day: make test mod to do that)
|
||||
|
||||
[HarmonyPostfix] // after all onprecull listeners
|
||||
[HarmonyPatch(typeof(Camera), "FireOnPreCull")]
|
||||
private static void OnPreCullPostfix(Camera cam)
|
||||
{
|
||||
OcclusionCullingHack hackInstance = cam.GetComponent<OcclusionCullingHack>();
|
||||
if (hackInstance != null) hackInstance.OnPostFirePreCull(cam);
|
||||
}
|
||||
|
||||
[HarmonyPrefix] // before all onprerender listeners
|
||||
[HarmonyPatch(typeof(Camera), "FireOnPreRender")]
|
||||
private static void OnPreRenderPrefix(Camera cam)
|
||||
{
|
||||
OcclusionCullingHack hackInstance = cam.GetComponent<OcclusionCullingHack>();
|
||||
if (hackInstance != null) hackInstance.OnPreFirePreRender(cam);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Harmony Patches
|
||||
|
||||
/// <summary>
|
||||
/// Attempted hack to fix occlusion culling for *static* objects. This does not fix dynamic objects, they will be culled
|
||||
/// by the camera's frustum & original baked occlusion culling data. Nothing can be done about that. :>
|
||||
/// </summary>
|
||||
public class OcclusionCullingHack : MonoBehaviour
|
||||
{
|
||||
private Vector3 originalPosition;
|
||||
|
||||
internal void OnPostFirePreCull(Camera cam)
|
||||
{
|
||||
originalPosition = cam.transform.position;
|
||||
cam.transform.position = OriginShiftManager.GetAbsolutePosition(originalPosition);
|
||||
}
|
||||
|
||||
internal void OnPreFirePreRender(Camera cam)
|
||||
{
|
||||
cam.transform.position = originalPosition;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Hacks
|
||||
{
|
||||
public class OriginShiftOcclusionCullingDisabler : MonoBehaviour
|
||||
{
|
||||
private Camera _camera;
|
||||
private bool _originalCullingState;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_camera = GetComponent<Camera>();
|
||||
if (_camera == null)
|
||||
{
|
||||
Debug.LogError("OriginShiftOcclusionCullingDisabler requires a Camera component on the same GameObject.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
_originalCullingState = _camera.useOcclusionCulling;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnStateChanged += OnOriginShiftStateChanged;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnStateChanged -= OnOriginShiftStateChanged;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShiftStateChanged(OriginShiftManager.OriginShiftState state)
|
||||
{
|
||||
_camera.useOcclusionCulling = state != OriginShiftManager.OriginShiftState.Forced && _originalCullingState;
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
}
|
||||
}
|
254
OriginShift/OriginShift/OriginShiftManager.cs
Normal file
254
OriginShift/OriginShift/OriginShiftManager.cs
Normal file
|
@ -0,0 +1,254 @@
|
|||
using System;
|
||||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI.CCK.Components;
|
||||
using JetBrains.Annotations;
|
||||
using MagicaCloth;
|
||||
using NAK.OriginShift.Components;
|
||||
using NAK.OriginShift.Utility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift;
|
||||
|
||||
public class OriginShiftManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static OriginShiftManager _instance;
|
||||
|
||||
public static OriginShiftManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance) return _instance;
|
||||
_instance = new GameObject("NAKOriginShiftManager").AddComponent<OriginShiftManager>();
|
||||
DontDestroyOnLoad(_instance.gameObject);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _useOriginShift;
|
||||
|
||||
public static bool UseOriginShift
|
||||
{
|
||||
get => _useOriginShift;
|
||||
set
|
||||
{
|
||||
if (_useOriginShift == value)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
PlayerSetup.Instance.gameObject.AddComponentIfMissing<OriginShiftMonitor>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance.ResetOrigin();
|
||||
if (PlayerSetup.Instance.TryGetComponent(out OriginShiftMonitor originShiftMonitor))
|
||||
DestroyImmediate(originShiftMonitor);
|
||||
}
|
||||
|
||||
_useOriginShift = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _compatibilityMode = true;
|
||||
public static bool CompatibilityMode
|
||||
{
|
||||
get => _useOriginShift && _compatibilityMode;
|
||||
set => _compatibilityMode = value;
|
||||
}
|
||||
|
||||
#endregion Singleton
|
||||
|
||||
#region Shader Globals
|
||||
|
||||
private static readonly int s_OriginShiftChunkOffset = Shader.PropertyToID("_OriginShiftChunkOffset"); // Vector3
|
||||
private static readonly int s_OriginShiftChunkPosition = Shader.PropertyToID("_OriginShiftChunkPosition"); // Vector3
|
||||
private static readonly int s_OriginShiftChunkThreshold = Shader.PropertyToID("_OriginShiftChunkThreshold"); // float
|
||||
|
||||
private static void SetShaderGlobals(Vector3 chunk, float threshold)
|
||||
{
|
||||
Shader.SetGlobalVector(s_OriginShiftChunkOffset, chunk);
|
||||
Shader.SetGlobalFloat(s_OriginShiftChunkThreshold, threshold);
|
||||
Shader.SetGlobalVector(s_OriginShiftChunkPosition, chunk * threshold);
|
||||
}
|
||||
|
||||
private static void ResetShaderGlobals()
|
||||
{
|
||||
Shader.SetGlobalVector(s_OriginShiftChunkOffset, Vector3.zero);
|
||||
Shader.SetGlobalFloat(s_OriginShiftChunkThreshold, -1f);
|
||||
Shader.SetGlobalVector(s_OriginShiftChunkPosition, Vector3.zero);
|
||||
}
|
||||
|
||||
#endregion Shader Globals
|
||||
|
||||
#region Actions
|
||||
|
||||
public static Action<OriginShiftState> OnStateChanged = delegate { };
|
||||
|
||||
public static Action<Vector3> OnOriginShifted = delegate { };
|
||||
public static Action<Vector3> OnPostOriginShifted = delegate { };
|
||||
|
||||
#endregion Actions
|
||||
|
||||
#region Public Properties
|
||||
|
||||
[PublicAPI] public bool IsOriginShifted => ChunkOffset != Vector3.zero;
|
||||
[PublicAPI] public Vector3 ChunkOffset { get; internal set; } = Vector3.zero;
|
||||
[PublicAPI] public Vector3 ChunkPosition => ChunkOffset * OriginShiftController.ORIGIN_SHIFT_THRESHOLD;
|
||||
|
||||
public enum OriginShiftState
|
||||
{
|
||||
Inactive, // world is not using Origin Shift
|
||||
Active, // world is using Origin Shift
|
||||
Forced // temp for this session, force world to use Origin Shift
|
||||
}
|
||||
|
||||
private OriginShiftState _currentState = OriginShiftState.Inactive;
|
||||
[PublicAPI] public OriginShiftState CurrentState
|
||||
{
|
||||
get => _currentState;
|
||||
private set
|
||||
{
|
||||
if (_currentState == value)
|
||||
return;
|
||||
|
||||
_currentState = value;
|
||||
OnStateChanged.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Manager Lifecycle
|
||||
|
||||
private OriginShiftController _forceController;
|
||||
|
||||
public void ForceManager()
|
||||
{
|
||||
if (CVRWorld.Instance == null)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("Cannot force Origin Shift without a world.");
|
||||
return;
|
||||
}
|
||||
OriginShiftMod.Logger.Msg("Forcing Origin Shift...");
|
||||
|
||||
_forceController = CVRWorld.Instance.gameObject.AddComponentIfMissing<OriginShiftController>();
|
||||
_forceController.IsForced = true;
|
||||
}
|
||||
|
||||
public void SetupManager(bool isForced = false)
|
||||
{
|
||||
if (CVRWorld.Instance == null)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("Cannot setup Origin Shift without a world.");
|
||||
return;
|
||||
}
|
||||
OriginShiftMod.Logger.Msg("Setting up Origin Shift...");
|
||||
|
||||
CurrentState = isForced ? OriginShiftState.Forced : OriginShiftState.Active;
|
||||
UseOriginShift = true;
|
||||
}
|
||||
|
||||
public void ResetManager()
|
||||
{
|
||||
OriginShiftMod.Logger.Msg("Resetting Origin Shift...");
|
||||
|
||||
if (_forceController) Destroy(_forceController);
|
||||
|
||||
CurrentState = OriginShiftState.Inactive;
|
||||
|
||||
ResetOrigin();
|
||||
ResetShaderGlobals();
|
||||
UseOriginShift = false;
|
||||
}
|
||||
|
||||
#endregion Manager Lifecycle
|
||||
|
||||
#region Public Methods
|
||||
|
||||
// Called by OriginShiftMonitor when the local player needs to shit
|
||||
public void ShiftOrigin(Vector3 rawPosition)
|
||||
{
|
||||
if (!_useOriginShift) return;
|
||||
|
||||
// create stopwatch
|
||||
StopWatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
// normalize
|
||||
float halfThreshold = (OriginShiftController.ORIGIN_SHIFT_THRESHOLD / 2);
|
||||
rawPosition += new Vector3(halfThreshold, halfThreshold, halfThreshold);
|
||||
|
||||
// add to chunk
|
||||
Vector3 chunkDifference;
|
||||
Vector3 calculatedChunk = chunkDifference = ChunkOffset;
|
||||
|
||||
calculatedChunk.x += Mathf.FloorToInt(rawPosition.x / OriginShiftController.ORIGIN_SHIFT_THRESHOLD);
|
||||
calculatedChunk.y += Mathf.FloorToInt(rawPosition.y / OriginShiftController.ORIGIN_SHIFT_THRESHOLD);
|
||||
calculatedChunk.z += Mathf.FloorToInt(rawPosition.z / OriginShiftController.ORIGIN_SHIFT_THRESHOLD);
|
||||
|
||||
// get offset
|
||||
chunkDifference = (ChunkOffset - calculatedChunk) * OriginShiftController.ORIGIN_SHIFT_THRESHOLD;
|
||||
|
||||
// store & invoke
|
||||
ChunkOffset = calculatedChunk;
|
||||
OnOriginShifted.Invoke(chunkDifference);
|
||||
OnPostOriginShifted.Invoke(chunkDifference);
|
||||
|
||||
// set shader globals
|
||||
SetShaderGlobals(ChunkOffset, OriginShiftController.ORIGIN_SHIFT_THRESHOLD);
|
||||
|
||||
// log
|
||||
stopwatch.Stop();
|
||||
OriginShiftMod.Logger.Msg($"Shifted Origin: {chunkDifference} in {stopwatch.ElapsedMilliseconds:F11}ms");
|
||||
}
|
||||
|
||||
public void ResetOrigin()
|
||||
{
|
||||
if (!_useOriginShift) return;
|
||||
|
||||
ShiftOrigin(-ChunkPosition);
|
||||
ChunkOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
#endregion Origin Shift Implementation
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
public static Vector3 GetAbsolutePosition(Vector3 localizedPosition)
|
||||
{
|
||||
// absolute coordinates can be reconstructed using current chunk and threshold
|
||||
localizedPosition += Instance.ChunkOffset * OriginShiftController.ORIGIN_SHIFT_THRESHOLD;
|
||||
return localizedPosition;
|
||||
}
|
||||
|
||||
public static Vector3 GetLocalizedPosition(Vector3 absolutePosition)
|
||||
{
|
||||
return absolutePosition - (Instance.ChunkOffset * OriginShiftController.ORIGIN_SHIFT_THRESHOLD);
|
||||
}
|
||||
|
||||
#endregion Utility Methods
|
||||
|
||||
#region Unity Events
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.P)) // press p to print chunk
|
||||
OriginShiftMod.Logger.Msg($"Current Chunk: {ChunkOffset}");
|
||||
|
||||
// press o to toggle debug
|
||||
if (Input.GetKeyDown(KeyCode.O))
|
||||
{
|
||||
if (TryGetComponent(out DebugTextDisplay debugTextDisplay))
|
||||
Destroy(debugTextDisplay);
|
||||
else
|
||||
gameObject.AddComponent<DebugTextDisplay>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion Unity Events
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using Zettai;
|
||||
|
||||
namespace NAK.OriginShift;
|
||||
|
||||
public class OriginShiftDbAvatarReceiver : MonoBehaviour
|
||||
{
|
||||
private DbJobsAvatarManager _avatarManager;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
_avatarManager = GetComponent<DbJobsAvatarManager>();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
}
|
||||
|
||||
private void OnOriginShifted(Vector3 shift)
|
||||
{
|
||||
// get all particles
|
||||
var particles = _avatarManager.particlesArray;
|
||||
for (var index = 0; index < particles.Length; index++)
|
||||
{
|
||||
ParticleStruct particle = particles[index];
|
||||
|
||||
float3 position = particle.m_Position;
|
||||
position.x += shift.x;
|
||||
position.y += shift.y;
|
||||
position.z += shift.z;
|
||||
particle.m_Position = position;
|
||||
|
||||
position = particle.m_PrevPosition;
|
||||
position.x += shift.x;
|
||||
position.y += shift.y;
|
||||
position.z += shift.z;
|
||||
particle.m_PrevPosition = position;
|
||||
|
||||
particles[index] = particle;
|
||||
}
|
||||
_avatarManager.particlesArray = particles;
|
||||
|
||||
// get all transforminfo
|
||||
var transformInfos = _avatarManager.transformInfoArray;
|
||||
for (var index = 0; index < transformInfos.Length; index++)
|
||||
{
|
||||
TransformInfo transformInfo = transformInfos[index];
|
||||
|
||||
float3 position = transformInfo.position;
|
||||
position.x += shift.x;
|
||||
position.y += shift.y;
|
||||
position.z += shift.z;
|
||||
transformInfo.position = position;
|
||||
|
||||
position = transformInfo.prevPosition;
|
||||
position.x += shift.x;
|
||||
position.y += shift.y;
|
||||
position.z += shift.z;
|
||||
transformInfo.prevPosition = position;
|
||||
|
||||
transformInfos[index] = transformInfo;
|
||||
}
|
||||
_avatarManager.transformInfoArray = transformInfos;
|
||||
}
|
||||
}
|
93
OriginShift/OriginShift/Player/OriginShiftMonitor.cs
Normal file
93
OriginShift/OriginShift/Player/OriginShiftMonitor.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System.Collections;
|
||||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using NAK.OriginShift.Components;
|
||||
using NAK.OriginShift.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
using ABI_RC.Systems.Movement;
|
||||
#endif
|
||||
|
||||
namespace NAK.OriginShift
|
||||
{
|
||||
[DefaultExecutionOrder(int.MaxValue)]
|
||||
public class OriginShiftMonitor : MonoBehaviour
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
private PlayerSetup _playerSetup;
|
||||
private BetterBetterCharacterController _characterController;
|
||||
#endif
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
_playerSetup = GetComponent<PlayerSetup>();
|
||||
_characterController = GetComponent<BetterBetterCharacterController>();
|
||||
#endif
|
||||
OriginShiftManager.OnPostOriginShifted += OnPostOriginShifted;
|
||||
StartCoroutine(FixedUpdateCoroutine());
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
OriginShiftManager.OnPostOriginShifted -= OnPostOriginShifted;
|
||||
StopAllCoroutines();
|
||||
}
|
||||
|
||||
private void LateFixedUpdate()
|
||||
{
|
||||
// in CVR use GetPlayerPosition to account for VR offset
|
||||
Vector3 position = PlayerSetup.Instance.GetPlayerPosition();
|
||||
|
||||
// respawn height check
|
||||
Vector3 absPosition = OriginShiftManager.GetAbsolutePosition(position);
|
||||
if (absPosition.y < BetterBetterCharacterController.Instance.respawnHeight)
|
||||
{
|
||||
RootLogic.Instance.Respawn();
|
||||
return;
|
||||
}
|
||||
|
||||
float halfThreshold = OriginShiftController.ORIGIN_SHIFT_THRESHOLD / 2; // i keep forgetting this
|
||||
if (Mathf.Abs(position.x) > halfThreshold
|
||||
|| Mathf.Abs(position.y) > halfThreshold
|
||||
|| Mathf.Abs(position.z) > halfThreshold)
|
||||
OriginShiftManager.Instance.ShiftOrigin(position);
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnPostOriginShifted(Vector3 shift)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// shift our transform back
|
||||
transform.position += shift;
|
||||
#else
|
||||
_characterController.OffsetBy(shift);
|
||||
_playerSetup.OffsetAvatarMovementData(shift);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
|
||||
#region LateFixedUpdate Implementation
|
||||
|
||||
private readonly YieldInstruction _fixedUpdateYield = new WaitForFixedUpdate();
|
||||
|
||||
private IEnumerator FixedUpdateCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return _fixedUpdateYield;
|
||||
LateFixedUpdate(); // we need to run after all physics (specifically, character controller)
|
||||
}
|
||||
// ReSharper disable once IteratorNeverReturns
|
||||
}
|
||||
|
||||
#endregion LateFixedUpdate Implementation
|
||||
}
|
||||
}
|
47
OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs
Normal file
47
OriginShift/OriginShift/Player/OriginShiftNetIkReceiver.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift;
|
||||
|
||||
public class OriginShiftNetIkReceiver : MonoBehaviour
|
||||
{
|
||||
private PuppetMaster _puppetMaster;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_puppetMaster = GetComponent<PuppetMaster>();
|
||||
if (_puppetMaster == null)
|
||||
{
|
||||
OriginShiftMod.Logger.Error("OriginShiftNetIkReceiver: No PuppetMaster found on GameObject: " + gameObject.name, this);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted += OnOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnOriginShifted -= OnOriginShifted;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnOriginShifted(Vector3 shift)
|
||||
{
|
||||
// would be nice if these were relative positions like sub-syncs :)
|
||||
_puppetMaster._playerAvatarMovementDataCurrent.RootPosition += shift;
|
||||
_puppetMaster._playerAvatarMovementDataCurrent.BodyPosition += shift;
|
||||
_puppetMaster._playerAvatarMovementDataPast.RootPosition += shift;
|
||||
_puppetMaster._playerAvatarMovementDataPast.BodyPosition += shift;
|
||||
// later in frame puppetmaster will update remote player position
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
}
|
99
OriginShift/OriginShift/Utility/DebugTextDisplay.cs
Normal file
99
OriginShift/OriginShift/Utility/DebugTextDisplay.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
#if !UNITY_EDITOR
|
||||
using ABI_RC.Core.Player;
|
||||
using NAK.OriginShift.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Utility;
|
||||
|
||||
public class DebugTextDisplay : MonoBehaviour
|
||||
{
|
||||
#region Private Variables
|
||||
|
||||
private string _debugText = "Initializing...";
|
||||
private Color _textColor = Color.white;
|
||||
|
||||
private bool _originShiftEventOccurred;
|
||||
private const float _blendDuration = 1.0f;
|
||||
private float _blendTime;
|
||||
|
||||
#endregion Private Variables
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OriginShiftManager.OnPostOriginShifted += OnPostOriginShifted;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OriginShiftManager.OnPostOriginShifted -= OnPostOriginShifted;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUIStyle style = new()
|
||||
{
|
||||
fontSize = 25,
|
||||
normal = { textColor = _textColor },
|
||||
alignment = TextAnchor.UpperRight
|
||||
};
|
||||
|
||||
float screenWidth = Screen.width;
|
||||
var xPosition = screenWidth - 10;
|
||||
float yPosition = 10;
|
||||
|
||||
GUI.Label(new Rect(xPosition - 490, yPosition, 500, 150), _debugText, style);
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void UpdateDebugText(string newText)
|
||||
{
|
||||
_debugText = newText;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
private void Update()
|
||||
{
|
||||
Vector3 currentChunk = OriginShiftManager.Instance.ChunkOffset;
|
||||
Vector3 localCoordinates = PlayerSetup.Instance.GetPlayerPosition();
|
||||
Vector3 absoluteCoordinates = localCoordinates;
|
||||
|
||||
// absolute coordinates can be reconstructed using current chunk and threshold
|
||||
absoluteCoordinates += currentChunk * OriginShiftController.ORIGIN_SHIFT_THRESHOLD;
|
||||
|
||||
// Update the debug text with the current coordinates
|
||||
UpdateDebugText($"Local Coordinates:\n{localCoordinates}\n\n" +
|
||||
$"Absolute Coordinates:\n{absoluteCoordinates}\n\n" +
|
||||
$"Current Chunk:\n{currentChunk}");
|
||||
|
||||
// Blend back to white if the origin shift event occurred
|
||||
if (_originShiftEventOccurred)
|
||||
{
|
||||
_blendTime += Time.deltaTime;
|
||||
_textColor = Color.Lerp(Color.red, Color.white, _blendTime / _blendDuration);
|
||||
if (_blendTime >= _blendDuration)
|
||||
{
|
||||
_originShiftEventOccurred = false;
|
||||
_blendTime = 0.0f;
|
||||
_textColor = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Origin Shift Events
|
||||
|
||||
private void OnPostOriginShifted(Vector3 _)
|
||||
{
|
||||
_originShiftEventOccurred = true;
|
||||
_textColor = Color.green;
|
||||
_blendTime = 0.0f;
|
||||
}
|
||||
|
||||
#endregion Origin Shift Events
|
||||
}
|
||||
#endif
|
33
OriginShift/OriginShift/Utility/RendererReflectionUtility.cs
Normal file
33
OriginShift/OriginShift/Utility/RendererReflectionUtility.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.OriginShift.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Dumb little utility class to access the private staticBatchRootTransform property in the Renderer class.
|
||||
/// Using this we can move static batched objects with the scene! :)
|
||||
/// </summary>
|
||||
public static class RendererReflectionUtility
|
||||
{
|
||||
private static readonly PropertyInfo _staticBatchRootTransformProperty;
|
||||
|
||||
static RendererReflectionUtility()
|
||||
{
|
||||
Type rendererType = typeof(Renderer);
|
||||
_staticBatchRootTransformProperty = rendererType.GetProperty("staticBatchRootTransform", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (_staticBatchRootTransformProperty == null) OriginShiftMod.Logger.Error("Property staticBatchRootTransform not found in Renderer class.");
|
||||
}
|
||||
|
||||
public static void SetStaticBatchRootTransform(Renderer renderer, Transform newTransform)
|
||||
{
|
||||
if (_staticBatchRootTransformProperty != null)
|
||||
_staticBatchRootTransformProperty.SetValue(renderer, newTransform);
|
||||
}
|
||||
|
||||
public static Transform GetStaticBatchRootTransform(Renderer renderer)
|
||||
{
|
||||
if (_staticBatchRootTransformProperty != null)
|
||||
return (Transform)_staticBatchRootTransformProperty.GetValue(renderer);
|
||||
return null;
|
||||
}
|
||||
}
|
276
OriginShift/Patches.cs
Normal file
276
OriginShift/Patches.cs
Normal file
|
@ -0,0 +1,276 @@
|
|||
#if !UNITY_EDITOR
|
||||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Util;
|
||||
using ABI_RC.Systems.Camera;
|
||||
using ABI_RC.Systems.Communications.Networking;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI.CCK.Components;
|
||||
using DarkRift;
|
||||
using HarmonyLib;
|
||||
using NAK.OriginShift.Components;
|
||||
using NAK.OriginShift.Hacks;
|
||||
using UnityEngine;
|
||||
using Zettai;
|
||||
|
||||
namespace NAK.OriginShift.Patches;
|
||||
|
||||
internal static class BetterBetterCharacterControllerPatches
|
||||
{
|
||||
internal static bool PreventNextClearMovementParent;
|
||||
internal static bool PreventNextClearAccumulatedForces;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.Start))]
|
||||
private static void Postfix_BetterCharacterController_Start(ref BetterBetterCharacterController __instance)
|
||||
{
|
||||
__instance.AddComponentIfMissing<OriginShiftMonitor>();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.ClearLastMovementParent))]
|
||||
private static bool Prefix_BetterCharacterController_ClearLastMovementParent()
|
||||
{
|
||||
if (!PreventNextClearMovementParent)
|
||||
return true;
|
||||
|
||||
// skip this call if we are preventing it
|
||||
PreventNextClearMovementParent = false;
|
||||
Debug.Log("Prevented ClearLastMovementParent");
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.ClearAccumulatedForces))]
|
||||
private static bool Prefix_BetterCharacterController_ClearAccumulatedForces()
|
||||
{
|
||||
if (!PreventNextClearAccumulatedForces)
|
||||
return true;
|
||||
|
||||
// skip this call if we are preventing it
|
||||
PreventNextClearAccumulatedForces = false;
|
||||
Debug.Log("Prevented ClearAccumulatedForces");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRSpawnablePatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.Start))]
|
||||
private static void Postfix_CVRSpawnable_Start(ref CVRSpawnable __instance)
|
||||
{
|
||||
Transform wrapper = __instance.transform.parent;
|
||||
|
||||
// test adding to the wrapper of the spawnable
|
||||
wrapper.AddComponentIfMissing<OriginShiftTransformReceiver>();
|
||||
wrapper.AddComponentIfMissing<OriginShiftParticleSystemReceiver>();
|
||||
wrapper.AddComponentIfMissing<OriginShiftTrailRendererReceiver>();
|
||||
wrapper.AddComponentIfMissing<OriginShiftRigidbodyReceiver>();
|
||||
}
|
||||
|
||||
[HarmonyPrefix] // inbound spawnable
|
||||
[HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.UpdateFromNetwork))]
|
||||
private static void Prefix_CVRSpawnable_UpdateFromNetwork(ref CVRSyncHelper.PropData propData)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position
|
||||
{
|
||||
Vector3 position = new(propData.PositionX, propData.PositionY, propData.PositionZ); // imagine not using Vector3
|
||||
position = OriginShiftManager.GetLocalizedPosition(position);
|
||||
propData.PositionX = position.x;
|
||||
propData.PositionY = position.y;
|
||||
propData.PositionZ = position.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class RCC_SkidmarksManagerPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(RCC_SkidmarksManager), nameof(RCC_SkidmarksManager.Start))]
|
||||
private static void Postfix_RCC_SkidMarkManager_Start(ref RCC_SkidmarksManager __instance)
|
||||
{
|
||||
__instance.gameObject.AddComponentIfMissing<OriginShiftTransformReceiver>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PlayerSetupPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||
private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
|
||||
{
|
||||
__instance.desktopCam.AddComponentIfMissing<OriginShiftOcclusionCullingDisabler>();
|
||||
__instance.vrCam.AddComponentIfMissing<OriginShiftOcclusionCullingDisabler>();
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.GetPlayerMovementData))]
|
||||
private static void Postfix_PlayerSetup_GetPlayerMovementData(ref PlayerAvatarMovementData __result)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode)
|
||||
{
|
||||
// adjust root position back to absolute world position
|
||||
__result.RootPosition = OriginShiftManager.GetAbsolutePosition(__result.RootPosition); // player root
|
||||
__result.BodyPosition = OriginShiftManager.GetAbsolutePosition(__result.BodyPosition); // player hips (pls fix, why in world space?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PortableCameraPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.Start))]
|
||||
private static void Postfix_PortableCamera_Start(ref PortableCamera __instance)
|
||||
{
|
||||
__instance.cameraComponent.AddComponentIfMissing<OriginShiftOcclusionCullingDisabler>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PathingCameraPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRPathCamController), nameof(CVRPathCamController.Start))]
|
||||
private static void Postfix_CVRPathCamController_Start(ref CVRPathCamController __instance)
|
||||
{
|
||||
__instance.cam.AddComponentIfMissing<OriginShiftOcclusionCullingDisabler>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Comms_ClientPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(Comms_Client), nameof(Comms_Client.SetPosition))]
|
||||
private static void Prefix_Comms_Client_GetPlayerMovementData(ref Vector3 listenerPosition)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position
|
||||
listenerPosition = OriginShiftManager.GetAbsolutePosition(listenerPosition);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRSyncHelperPatches
|
||||
{
|
||||
[HarmonyPrefix] // outbound spawnable
|
||||
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.UpdatePropValues))]
|
||||
private static void Prefix_CVRSyncHelper_UpdatePropValues(ref Vector3 position)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position
|
||||
position = OriginShiftManager.GetAbsolutePosition(position);
|
||||
}
|
||||
|
||||
[HarmonyPrefix] // outbound object sync
|
||||
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.MoveObject))]
|
||||
private static void Prefix_CVRSyncHelper_MoveObject(ref float PosX, ref float PosY, ref float PosZ)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position
|
||||
{
|
||||
Vector3 position = new(PosX, PosY, PosZ); // imagine not using Vector3
|
||||
position = OriginShiftManager.GetAbsolutePosition(position);
|
||||
PosX = position.x;
|
||||
PosY = position.y;
|
||||
PosZ = position.z;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix] // outbound spawn prop
|
||||
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.SpawnProp))]
|
||||
private static void Prefix_CVRSyncHelper_SpawnProp(ref float posX, ref float posY, ref float posZ)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // adjust root position back to absolute world position
|
||||
{
|
||||
Vector3 position = new(posX, posY, posZ); // imagine not using Vector3
|
||||
position = OriginShiftManager.GetAbsolutePosition(position);
|
||||
posX = position.x;
|
||||
posY = position.y;
|
||||
posZ = position.z;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.SpawnPropFromNetwork))]
|
||||
private static void Postfix_CVRSyncHelper_SpawnPropFromNetwork(Message message)
|
||||
{
|
||||
if (!OriginShiftManager.CompatibilityMode)
|
||||
return;
|
||||
|
||||
using DarkRiftReader reader = message.GetReader();
|
||||
reader.ReadString(); // objectId, don't care
|
||||
|
||||
string instanceId = reader.ReadString();
|
||||
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find((match) => match.InstanceId == instanceId);
|
||||
if (propData == null)
|
||||
return; // uh oh
|
||||
|
||||
Vector3 position = new(propData.PositionX, propData.PositionY, propData.PositionZ); // imagine not using Vector3
|
||||
position = OriginShiftManager.GetLocalizedPosition(position);
|
||||
propData.PositionX = position.x;
|
||||
propData.PositionY = position.y;
|
||||
propData.PositionZ = position.z;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRObjectSyncPatches
|
||||
{
|
||||
[HarmonyPrefix] // inbound object sync
|
||||
[HarmonyPatch(typeof(CVRObjectSync), nameof(CVRObjectSync.receiveNetworkData))]
|
||||
[HarmonyPatch(typeof(CVRObjectSync), nameof(CVRObjectSync.receiveNetworkDataJoin))]
|
||||
private static void Prefix_CVRObjectSync_Update(ref Vector3 position)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // adjust root position back to localized world position
|
||||
position = OriginShiftManager.GetLocalizedPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PuppetMasterPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.Start))]
|
||||
private static void Postfix_PuppetMaster_Start(ref PuppetMaster __instance)
|
||||
{
|
||||
__instance.gameObject.AddComponentIfMissing<OriginShiftNetIkReceiver>();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.CycleData))]
|
||||
private static void Prefix_PuppetMaster_CycleData(ref PlayerAvatarMovementData ___PlayerAvatarMovementDataInput)
|
||||
{
|
||||
if (OriginShiftManager.CompatibilityMode) // && if user is not using OriginShift
|
||||
{
|
||||
// adjust root position back to absolute world position
|
||||
___PlayerAvatarMovementDataInput.RootPosition = OriginShiftManager.GetLocalizedPosition(___PlayerAvatarMovementDataInput.RootPosition); // player root
|
||||
___PlayerAvatarMovementDataInput.BodyPosition = OriginShiftManager.GetLocalizedPosition(___PlayerAvatarMovementDataInput.BodyPosition); // player hips (pls fix, why in world space?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//OriginShiftDbAvatarReceiver
|
||||
internal static class DbJobsAvatarManagerPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(DbJobsAvatarManager), nameof(DbJobsAvatarManager.Awake))]
|
||||
private static void Postfix_DbJobsAvatarManager_Start(ref DbJobsAvatarManager __instance)
|
||||
{
|
||||
__instance.gameObject.AddComponentIfMissing<OriginShiftDbAvatarReceiver>();
|
||||
}
|
||||
}
|
||||
|
||||
// CVRPortalManager
|
||||
internal static class CVRPortalManagerPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRPortalManager), nameof(CVRPortalManager.Start))]
|
||||
private static void Postfix_CVRPortalManager_Start(ref CVRPortalManager __instance)
|
||||
{
|
||||
// parent portal to the object below it using a physics cast
|
||||
Transform portalTransform = __instance.transform;
|
||||
Vector3 origin = portalTransform.position;
|
||||
Vector3 direction = Vector3.down;
|
||||
if (Physics.Raycast(origin, direction, out RaycastHit hit, 0.5f))
|
||||
portalTransform.SetParent(hit.transform);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
34
OriginShift/Properties/AssemblyInfo.cs
Normal file
34
OriginShift/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
#if !UNITY_EDITOR
|
||||
using NAK.OriginShift.Properties;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.OriginShift))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.OriginShift))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.OriginShift.OriginShiftMod),
|
||||
nameof(NAK.OriginShift),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/OriginShift"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 125, 126, 129)]
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.OriginShift.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
||||
#endif
|
14
OriginShift/README.md
Normal file
14
OriginShift/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# OriginShift
|
||||
|
||||
Experimental mod that allows world origin to be shifted to prevent floating point precision issues.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
|
||||
|
||||
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||
|
||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
BIN
OriginShift/Resources/OriginShift-Icon-Active.png
Normal file
BIN
OriginShift/Resources/OriginShift-Icon-Active.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
OriginShift/Resources/OriginShift-Icon-Forced.png
Normal file
BIN
OriginShift/Resources/OriginShift-Icon-Forced.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
OriginShift/Resources/OriginShift-Icon-Inactive.png
Normal file
BIN
OriginShift/Resources/OriginShift-Icon-Inactive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
23
OriginShift/format.json
Normal file
23
OriginShift/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": 211,
|
||||
"name": "RelativeSync",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2024r175",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.\n\nProvides some Experimental settings to also fix local jitter on movement parents.",
|
||||
"searchtags": [
|
||||
"relative",
|
||||
"sync",
|
||||
"movement",
|
||||
"chair"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r29/RelativeSync.dll",
|
||||
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/RelativeSync/",
|
||||
"changelog": "- Enabled the Experimental settings to fix **local** jitter on Movement Parents by default\n- Adjusted BBCC No Interpolation fix to account for potential native fix (now respects initial value)",
|
||||
"embedcolor": "#507e64"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue