Skip to content

Commit

Permalink
[OSC] Replaced OSC library. Refactored and fixed bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
kafeijao committed Sep 15, 2022
1 parent 92a3dd4 commit abc811d
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 277 deletions.
6 changes: 0 additions & 6 deletions Kafe_CVR_Mods.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CCK.Debugger", "CCK.Debugge
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OSC", "OSC\OSC.csproj", "{81E84FD7-1C2F-48C2-B28F-B5045BDD0380}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpOSC", "Libs\SharpOSC\SharpOSC\SharpOSC.csproj", "{9B922B0B-5595-4E05-8270-F63FAAA6C299}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -32,9 +30,5 @@ Global
{81E84FD7-1C2F-48C2-B28F-B5045BDD0380}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81E84FD7-1C2F-48C2-B28F-B5045BDD0380}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81E84FD7-1C2F-48C2-B28F-B5045BDD0380}.Release|Any CPU.Build.0 = Release|Any CPU
{9B922B0B-5595-4E05-8270-F63FAAA6C299}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B922B0B-5595-4E05-8270-F63FAAA6C299}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B922B0B-5595-4E05-8270-F63FAAA6C299}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B922B0B-5595-4E05-8270-F63FAAA6C299}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
28 changes: 20 additions & 8 deletions OSC/Events/Avatar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static class Avatar {
// Configs cache
private static bool _triggersEnabled;
private static bool _setAvatarEnabled;
private static bool _debugConfigWarnings;

// Misc
private static readonly Stopwatch AvatarSetStopwatch = new();
Expand All @@ -49,6 +50,10 @@ static Avatar() {
// Handle the set avatar enabled configuration
_setAvatarEnabled = OSC.Instance.meOSCAvatarModuleSetAvatar.Value;
OSC.Instance.meOSCAvatarModuleSetAvatar.OnValueChanged += (_, enabled) => _setAvatarEnabled = enabled;

// Handle the warning when blocked osc command by config
_debugConfigWarnings = OSC.Instance.meOSCDebugConfigWarnings.Value;
OSC.Instance.meOSCDebugConfigWarnings.OnValueChanged += (_, enabled) => _debugConfigWarnings = enabled;
}

// Callers
Expand Down Expand Up @@ -107,25 +112,29 @@ internal static async void OnAnimatorManagerUpdate(CVRAnimatorManager animatorMa
AnimatorManagerUpdated?.Invoke(animatorManager);
}

internal static void OnAvatarSet(string uuid) {
internal static void OnAvatarSet(string guid) {
if (!_setAvatarEnabled) {
MelonLogger.Msg("[Info] Attempted to set the avatar via OSC, but that option is disabled on the mod configuration.");
if (_debugConfigWarnings) {
MelonLogger.Msg("[Config] Attempted to change the avatar via OSC, but that option is disabled on the mod configuration.");
}
return;
}

// Ignore malformed guids
if (!Guid.TryParse(uuid, out _)) return;
if (!Guid.TryParse(guid, out var guidValue)) return;
var parsedGuid = guidValue.ToString("D");

// Timer to prevent spamming this (since it's an API call
if (!AvatarSetStopwatch.IsRunning) AvatarSetStopwatch.Start();
else if (AvatarSetStopwatch.Elapsed < TimeSpan.FromSeconds(30)) {
MelonLogger.Msg($"[Info] Attempted to change avatar to {uuid}, but changing avatar is still on cooldown (30 secs)...");
else if (AvatarSetStopwatch.Elapsed < TimeSpan.FromSeconds(10)) {
MelonLogger.Msg($"[Info] Attempted to change avatar to {parsedGuid}, but changing avatar is still on cooldown " +
$"(10 secs)...");
return;
}

AvatarSetStopwatch.Restart();
MelonLogger.Msg($"[Command] Received OSC command to change avatar to {uuid}. Changing...");
AssetManagement.Instance.LoadLocalAvatar(uuid);
MelonLogger.Msg($"[Command] Received OSC command to change avatar to {parsedGuid}. Changing...");
AssetManagement.Instance.LoadLocalAvatar(parsedGuid);
}

// Callers parameters changed
Expand Down Expand Up @@ -176,7 +185,10 @@ internal static void OnParameterSetBool(string name, bool value) {

internal static void OnParameterSetTrigger(string name) {
if (!_triggersEnabled) {
MelonLogger.Msg("[Info] Attempted to set a trigger parameter, but that option is disabled in the mod configuration.");
if (_debugConfigWarnings) {
MelonLogger.Msg("[Config] Attempted to set a trigger parameter, but that option is disabled in " +
"the mod configuration.");
}
return;
}
_localPlayerAnimatorManager?.SetAnimatorParameterTrigger(name);
Expand Down
7 changes: 4 additions & 3 deletions OSC/Events/Scene.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using MelonLoader;

namespace OSC.Events;
namespace OSC.Events;

public static class Scene {

Expand All @@ -25,5 +23,8 @@ internal static void ResetAll() {

// Re-initialize spawnables
Spawnable.Reset();

// Clear devices connection status
Tracking.Reset();
}
}
121 changes: 78 additions & 43 deletions OSC/Events/Spawnable.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
using ABI_RC.Core.Util;
using ABI_RC.Core.Player;
using ABI_RC.Core.Util;
using ABI.CCK.Components;
using Assets.ABI_RC.Systems.Safety.AdvancedSafety;
using MelonLoader;
using UnityEngine;

namespace OSC.Events;

public static class Spawnable {

private static bool _debugMode;

// Caches for spawnable output (because some parameters might get spammed like hell)
private static readonly Dictionary<string, CVRSyncHelper.PropData> PropCache = new();
private static readonly Dictionary<string, Dictionary<string, float>> PropParametersCacheOutFloat = new();
private static readonly Dictionary<CVRSyncHelper.PropData, bool> PropAvailabilityCache = new();
private static readonly Dictionary<string, CVRSyncHelper.PropData> PropCache;
private static readonly Dictionary<string, Dictionary<string, float>> SpawnableParametersCacheOutFloat;
private static readonly Dictionary<CVRSyncHelper.PropData, bool> PropAvailabilityCache;

public static event Action<CVRSyncHelper.PropData> SpawnableCreated;
public static event Action<CVRSyncHelper.PropData> SpawnableDeleted;
public static event Action<CVRSpawnable> SpawnableDeleted;
public static event Action<CVRSpawnable, CVRSpawnableValue> SpawnableParameterChanged;
public static event Action<CVRSpawnable, bool> SpawnableAvailable;
public static event Action<CVRSpawnable> SpawnableLocationTrackingTicked;

static Spawnable() {

// Handle config debug value and changes
_debugMode = OSC.Instance.meOSCDebug.Value;
OSC.Instance.meOSCDebug.OnValueChanged += (_, newValue) => _debugMode = newValue;

// Instantiate caches
PropCache = new Dictionary<string, CVRSyncHelper.PropData>();
SpawnableParametersCacheOutFloat = new Dictionary<string, Dictionary<string, float>>();
PropAvailabilityCache = new Dictionary<CVRSyncHelper.PropData, bool>();
}

// Events from the game

internal static void Reset() {
Expand All @@ -29,41 +45,38 @@ internal static void Reset() {
internal static void OnSpawnableCreated(CVRSyncHelper.PropData propData) {
if (propData?.Spawnable == null || !propData.Spawnable.IsMine()) return;

// Add prop to caches
// Add prop data to caches
if (!PropCache.ContainsKey(propData.InstanceId)) {
PropCache.Add(propData.InstanceId, propData);
}
if (!PropParametersCacheOutFloat.ContainsKey(propData.InstanceId)) {
PropParametersCacheOutFloat.Add(propData.InstanceId, new Dictionary<string, float>());
if (!SpawnableParametersCacheOutFloat.ContainsKey(propData.InstanceId)) {
SpawnableParametersCacheOutFloat.Add(propData.InstanceId, new Dictionary<string, float>());
}

//MelonLogger.Msg($"[Spawnable] Spawnable {propData.Spawnable.instanceId} was created!");

SpawnableCreated?.Invoke(propData);

// Update availability because spawning doesn't trigger UpdateFromNetwork
OnSpawnableUpdateFromNetwork(propData, propData.Spawnable);
}

internal static void OnSpawnableDeleted(CVRSyncHelper.PropData propData) {
internal static void OnSpawnableDestroyed(CVRSpawnable spawnable) {
if (spawnable == null || !PropCache.ContainsKey(spawnable.instanceId)) return;

// Remove prop from caches
if (PropCache.ContainsKey(propData.InstanceId)) {
PropCache.Remove(propData.InstanceId);
// Remove spawnable from caches
if (PropCache.ContainsKey(spawnable.instanceId)) {
PropCache.Remove(spawnable.instanceId);
}
if (PropParametersCacheOutFloat.ContainsKey(propData.InstanceId)) {
PropParametersCacheOutFloat.Remove(propData.InstanceId);
if (SpawnableParametersCacheOutFloat.ContainsKey(spawnable.instanceId)) {
SpawnableParametersCacheOutFloat.Remove(spawnable.instanceId);
}

//MelonLogger.Msg($"[Spawnable] Spawnable {propData.Spawnable.instanceId} was deleted!");

SpawnableDeleted?.Invoke(propData);
SpawnableDeleted?.Invoke(spawnable);
}

internal static void OnSpawnableParameterChanged(CVRSpawnable spawnable, CVRSpawnableValue spawnableValue) {
if (spawnable == null || spawnableValue == null || !spawnable.IsMine() || !PropParametersCacheOutFloat.ContainsKey(spawnable.instanceId)) return;
if (spawnable == null || spawnableValue == null || !spawnable.IsMine() || !SpawnableParametersCacheOutFloat.ContainsKey(spawnable.instanceId)) return;

var cache = PropParametersCacheOutFloat[spawnable.instanceId];
var cache = SpawnableParametersCacheOutFloat[spawnable.instanceId];

// Value already exists and it's updated
if (cache.ContainsKey(spawnableValue.name) && Mathf.Approximately(cache[spawnableValue.name], spawnableValue.currentValue)) return;
Expand Down Expand Up @@ -101,33 +114,51 @@ internal static void OnTrackingTick() {

internal static void OnSpawnableParameterSet(string spawnableInstanceId, string spawnableParamName, float spawnableParamValue) {
if (!PropCache.ContainsKey(spawnableInstanceId) || spawnableParamName == "" || spawnableParamValue.IsAbsurd()) return;
var spawnable = PropCache[spawnableInstanceId].Spawnable;
var spawnable = PropCache[spawnableInstanceId]?.Spawnable;

// Prevent NullReferenceException when we're setting the location of a prop that was just deleted
if (spawnable == null) return;


var spawnableValueIndex = spawnable.syncValues.FindIndex( match => match.name == spawnableParamName);
if (spawnableValueIndex == -1) return;

//MelonLogger.Msg($"[Spawnable] Setting spawnable prop {spawnableInstanceId} {spawnableParamName} parameter to {spawnableParamValue}!");

// Value is already up to date -> Ignore
if (Mathf.Approximately(spawnable.syncValues[spawnableValueIndex].currentValue, spawnableParamValue)) return;

if (!ShouldControl(spawnable, true)) return;

spawnable.SetValue(spawnableValueIndex, spawnableParamValue);
if (_debugMode) {
MelonLogger.Msg($"[Debug] Set p+{spawnable.guid}~{spawnableInstanceId} {spawnableParamName} parameter" +
$" to {spawnableParamValue}");
}

//SpawnableParameterSet?.Invoke(spawnable, spawnable.syncValues[spawnableValueIndex]);
spawnable.SetValue(spawnableValueIndex, spawnableParamValue);
}


internal static void OnSpawnableLocationSet(string spawnableInstanceId, Vector3 pos, Vector3 rot, int? subIndex = null) {
if (!PropCache.ContainsKey(spawnableInstanceId) || pos.IsAbsurd() || pos.IsBad() || rot.IsAbsurd() || rot.IsBad()) return;
var spawnable = PropCache[spawnableInstanceId].Spawnable;

if (_debugMode) MelonLogger.Msg($"[Debug] Attempting to set {spawnableInstanceId} [{(subIndex.HasValue ? subIndex.Value : "Main")}] location...");

if (!PropCache.ContainsKey(spawnableInstanceId) || pos.IsAbsurd() || pos.IsBad() || rot.IsAbsurd() || rot.IsBad()) {
if (_debugMode) {
MelonLogger.Msg($"[Debug] Attempted to fetch {spawnableInstanceId} from cache. But it was missing or" +
$"the location was borked! InCache: {PropCache.ContainsKey(spawnableInstanceId)}" +
$"\n\t\t\tpos: {pos.ToString()}, rot: {rot.ToString()}");
}
return;
}
var spawnable = PropCache[spawnableInstanceId]?.Spawnable;

// Prevent NullReferenceException when we're setting the location of a prop that was just deleted
if (spawnable == null) return;
if (spawnable == null) {
if (_debugMode) {
MelonLogger.Msg($"[Debug] Attempted to fetch {spawnableInstanceId} from cache. But the associated " +
$"spawnable was null...");
}
return;
}

Transform transformToSet;
// The transform is a subSync of the spawnable
Expand All @@ -141,22 +172,33 @@ internal static void OnSpawnableLocationSet(string spawnableInstanceId, Vector3
transformToSet = spawnable.transform;
}

//MelonLogger.Msg($"[Spawnable] Setting spawnable prop {spawnableInstanceId} {spawnableParamName} parameter to {spawnableParamValue}!");

if (!ShouldControl(spawnable)) return;
if (!ShouldControl(spawnable)) {
if (_debugMode) {
MelonLogger.Msg($"[Debug] Attempted to control {spawnableInstanceId} but got refused! " +
$"Sync: {spawnable.SyncType} IsPhysics: {spawnable.isPhysicsSynced}");
}
return;
}

// Update location
transformToSet.position = pos;
transformToSet.eulerAngles = rot;
spawnable.ForceUpdate();

//SpawnableLocationSet?.Invoke();
if (_debugMode) MelonLogger.Msg($"[Debug] \t{spawnableInstanceId} [{(subIndex.HasValue ? subIndex.Value : "Main")}] location set!");
}


internal static void OnSpawnableCreate(string propGuid, float posX = 0f, float posY = 0f, float posZ = 0f) {
if (Guid.TryParse(propGuid, out _) && !posX.IsAbsurd() && !posY.IsAbsurd() && !posZ.IsAbsurd()) {
CVRSyncHelper.SpawnProp(propGuid, posX, posY, posZ);
internal static void OnSpawnableCreate(string propGuid, float? posX = null, float? posY = null, float? posZ = null) {
if (Guid.TryParse(propGuid, out _)) {
if (posX.HasValue && posX.Value.IsAbsurd() && posY.HasValue && posY.Value.IsAbsurd() && posZ.HasValue && posZ.Value.IsAbsurd()) {
// Spawn prop with the local coordinates provided
CVRSyncHelper.SpawnProp(propGuid, posX.Value, posY.Value, posZ.Value);
}
else {
// Spawn prop without coordinates -> spawns in front of the player
PlayerSetup.Instance.DropProp(propGuid);
}
}
}

Expand All @@ -172,13 +214,6 @@ private static bool ShouldControl(CVRSpawnable spawnable, bool allowWhenLocalPla
// Spawned by other people -> Ignore
if (!spawnable.IsMine()) return false;

// var pickup = Traverse.Create(spawnable).Field<CVRPickupObject>("pickup").Value;
// var attachments = Traverse.Create(spawnable).Field<List<CVRAttachment>>("_attachments").Value;

// Ignore prop if we're not grabbing it nor it is attached to us
//if ((pickup == null || pickup.grabbedBy != MetaPort.Instance.ownerId) &&
// (attachments.Count <= 0 || !attachments.Any(a => a.IsAttached()))) return;

// Other people are syncing it (grabbing/telegrabbing/attatched) -> Ignore
if (spawnable.SyncType != 0) return false;

Expand Down
46 changes: 33 additions & 13 deletions OSC/Events/Tracking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public static class Tracking {
private static Transform _playerSpaceTransform;
private static Transform _playerHmdTransform;

private static readonly Dictionary<VRTracker, bool> TrackerLastState = new();

public static event Action<bool, TrackingDataSource, int, string> TrackingDeviceConnected;
public static event Action<TrackingDataSource, int, string, Vector3, Vector3, float> TrackingDataDeviceUpdated;
public static event Action<Vector3, Vector3> TrackingDataPlaySpaceUpdated;

Expand All @@ -41,8 +44,8 @@ static Tracking() {
};

// Set the play space transform when it loads
Events.Scene.PlayerSetup += () => _playerSpaceTransform = PlayerSetup.Instance.transform;
Events.Scene.PlayerSetup += () => _playerHmdTransform = PlayerSetup.Instance.vrCamera.transform;
Scene.PlayerSetup += () => _playerSpaceTransform = PlayerSetup.Instance.transform;
Scene.PlayerSetup += () => _playerHmdTransform = PlayerSetup.Instance.vrCamera.transform;
}

public static void OnTrackingDataDeviceUpdated(VRTrackerManager trackerManager) {
Expand All @@ -56,21 +59,17 @@ public static void OnTrackingDataDeviceUpdated(VRTrackerManager trackerManager)
// Handle trackers
foreach (var vrTracker in trackerManager.trackers) {

// Manage Connected/Disconnected trackers
if ((!TrackerLastState.ContainsKey(vrTracker) && vrTracker.active) || (TrackerLastState.ContainsKey(vrTracker) && TrackerLastState[vrTracker] != vrTracker.active)) {
TrackingDeviceConnected?.Invoke(vrTracker.active, GetSource(vrTracker), GetIndex(vrTracker), vrTracker.deviceName);
TrackerLastState[vrTracker] = vrTracker.active;
}

// Ignore inactive trackers
if (!vrTracker.active) continue;

var index = (int) Traverse.Create(vrTracker).Field<SteamVR_TrackedObject>("_trackedObject").Value.index;
var transform = vrTracker.transform;

var source = vrTracker.role switch {
ETrackedControllerRole.Invalid => vrTracker.deviceName == "" ? TrackingDataSource.base_station : TrackingDataSource.unknown,
ETrackedControllerRole.LeftHand => TrackingDataSource.left_controller,
ETrackedControllerRole.RightHand => TrackingDataSource.right_controller,
ETrackedControllerRole.OptOut => TrackingDataSource.tracker,
_ => TrackingDataSource.unknown
};

TrackingDataDeviceUpdated?.Invoke(source, index, vrTracker.deviceName, transform.position, transform.rotation.eulerAngles, vrTracker.batteryStatus);
TrackingDataDeviceUpdated?.Invoke(GetSource(vrTracker), GetIndex(vrTracker), vrTracker.deviceName, transform.position, transform.rotation.eulerAngles, vrTracker.batteryStatus);
}

// Handle HMD
Expand All @@ -87,4 +86,25 @@ public static void OnTrackingDataDeviceUpdated(VRTrackerManager trackerManager)
Spawnable.OnTrackingTick();
}
}

internal static void Reset() {
// Clear the cache to force an update to the connected devices
TrackerLastState.Clear();
}

private static int GetIndex(VRTracker vrTracker) {
return (int)Traverse.Create(vrTracker).Field<SteamVR_TrackedObject>("_trackedObject").Value.index;
}

private static TrackingDataSource GetSource(VRTracker vrTracker) {
return vrTracker.role switch {
ETrackedControllerRole.Invalid => vrTracker.deviceName == ""
? TrackingDataSource.base_station
: TrackingDataSource.unknown,
ETrackedControllerRole.LeftHand => TrackingDataSource.left_controller,
ETrackedControllerRole.RightHand => TrackingDataSource.right_controller,
ETrackedControllerRole.OptOut => TrackingDataSource.tracker,
_ => TrackingDataSource.unknown
};
}
}
Loading

0 comments on commit abc811d

Please sign in to comment.