mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-01 05:49:23 +00:00
[YouAreMyPropNowWeAreHavingSoftTacosLater] Initial push
This commit is contained in:
parent
ba26a1faae
commit
392390cde7
6 changed files with 565 additions and 0 deletions
|
@ -54,6 +54,8 @@ EndProject
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", "RCCVirtualSteeringWheel\RCCVirtualSteeringWheel.csproj", "{4A378F81-3805-41E8-9565-A8A89A8C00D6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouAreMyPropNowWeAreHavingSoftTacosLater", "YouAreMyPropNowWeAreHavingSoftTacosLater\YouAreMyPropNowWeAreHavingSoftTacosLater.csproj", "{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}"
|
||||
EndProject
|
||||
EndProject
|
||||
EndProject
|
||||
EndProject
|
||||
|
@ -289,6 +291,10 @@ Global
|
|||
{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
479
YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs
Normal file
479
YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs
Normal file
|
@ -0,0 +1,479 @@
|
|||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Networking.API.Responses;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Util;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI.CCK.Components;
|
||||
using DarkRift;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater;
|
||||
|
||||
public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
||||
{
|
||||
#region Melon Events
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
#region CVRPickupObject Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRPickupObject).GetMethod(nameof(CVRPickupObject.OnGrab),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRPickupObjectOnGrab),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRPickupObject).GetMethod(nameof(CVRPickupObject.OnDrop), BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRPickupObjectOnDrop),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion CVRPickupObject Patches
|
||||
|
||||
#region CVRAttachment Patches
|
||||
|
||||
HarmonyInstance.Patch( // Cannot compile when using nameof
|
||||
typeof(CVRAttachment).GetMethod("\u003CAttachInternal\u003Eg__DoAttachmentSetup\u007C43_0",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRAttachmentAttachInternal),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRAttachment).GetMethod(nameof(CVRAttachment.DeAttach)),
|
||||
prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRAttachmentDeAttach),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion CVRAttachment Patches
|
||||
|
||||
#region CVRSyncHelper Patches
|
||||
|
||||
HarmonyInstance.Patch( // Replaces method, original needlessly ToArray???
|
||||
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.ClearProps),
|
||||
BindingFlags.Public | BindingFlags.Static),
|
||||
prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRSyncHelperClearProps),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion CVRSyncHelper Patches
|
||||
|
||||
#region CVRDownloadManager Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRDownloadManager).GetMethod(nameof(CVRDownloadManager.QueueTask),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRDownloadManagerQueueTask),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion CVRDownloadManager Patches
|
||||
|
||||
#region BetterBetterCharacterController Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(BetterBetterCharacterController).GetMethod(nameof(BetterBetterCharacterController.SetSitting),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnBetterBetterCharacterControllerSetSitting),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion BetterBetterCharacterController Patches
|
||||
|
||||
#region CVRWorld Game Events
|
||||
|
||||
CVRGameEventSystem.World.OnLoad.AddListener(OnWorldLoad);
|
||||
CVRGameEventSystem.World.OnUnload.AddListener(OnWorldUnload);
|
||||
|
||||
#endregion CVRWorld Game Events
|
||||
|
||||
#region Instances Game Events
|
||||
|
||||
CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected);
|
||||
|
||||
#endregion Instances Game Events
|
||||
}
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
#region Harmony Patches
|
||||
|
||||
private static readonly List<CVRSyncHelper.PropData> _heldPropData = new();
|
||||
private static GameObject _persistantPropsContainer;
|
||||
private static GameObject GetOrCreatePropsContainer()
|
||||
{
|
||||
if (_persistantPropsContainer != null) return _persistantPropsContainer;
|
||||
_persistantPropsContainer = new("[NAK] PersistantProps");
|
||||
_persistantPropsContainer.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
_persistantPropsContainer.transform.localScale = Vector3.one;
|
||||
Object.DontDestroyOnLoad(_persistantPropsContainer);
|
||||
return _persistantPropsContainer;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Vector3, CVRSyncHelper.PropData> _keyToPropData = new();
|
||||
private static readonly Stack<SpawnablePositionContainer> _spawnablePositionStack = new();
|
||||
private static bool _ignoreNextSeatExit;
|
||||
private static float _heightOffset;
|
||||
|
||||
private static void OnCVRPickupObjectOnGrab(CVRPickupObject __instance)
|
||||
{
|
||||
if (!GetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData);
|
||||
}
|
||||
|
||||
private static void OnCVRPickupObjectOnDrop(CVRPickupObject __instance)
|
||||
{
|
||||
if (!GetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData);
|
||||
}
|
||||
|
||||
private static void OnCVRAttachmentAttachInternal(CVRAttachment __instance)
|
||||
{
|
||||
if (!GetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData);
|
||||
}
|
||||
|
||||
private static void OnCVRAttachmentDeAttach(CVRAttachment __instance)
|
||||
{
|
||||
if (!__instance._isAttached) return; // Can invoke DeAttach without being attached
|
||||
if (!GetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData);
|
||||
}
|
||||
|
||||
// ReSharper disable UnusedParameter.Local
|
||||
private static bool OnCVRDownloadManagerQueueTask(string assetId, DownloadTask.ObjectType type, string assetUrl, string fileId, long fileSize, string fileKey, string toAttach,
|
||||
string fileHash = null, UgcTagsData tagsData = null, CVRLoadingAvatarController loadingAvatarController = null,
|
||||
bool joinOnComplete = false, bool isHomeRequested = false, int compatibilityVersion = 0, int encryptionAlgorithm = 0,
|
||||
string spawnerId = null)
|
||||
{
|
||||
if (type != DownloadTask.ObjectType.Prop) return true; // Only care about props
|
||||
|
||||
// toAttach is our instanceId, lets find the propData
|
||||
if (!GetPropDataById(toAttach, out CVRSyncHelper.PropData newPropData)) return true;
|
||||
|
||||
// Check if this is a prop we requested to spawn
|
||||
Vector3 identity = GetIdentityKeyFromPropData(newPropData);
|
||||
if (!_keyToPropData.Remove(identity, out CVRSyncHelper.PropData originalPropData)) return true;
|
||||
|
||||
// Remove original prop data from held
|
||||
if (_heldPropData.Contains(originalPropData)) _heldPropData.Remove(originalPropData);
|
||||
|
||||
// If original prop data is null spawn a new prop i guess :(
|
||||
if (originalPropData.Spawnable == null) return true;
|
||||
|
||||
// Add the new prop data to our held props in place of the old one
|
||||
if (!_heldPropData.Contains(newPropData)) _heldPropData.Add(newPropData);
|
||||
|
||||
// Apply new prop data to the spawnable
|
||||
newPropData.Spawnable = originalPropData.Spawnable;
|
||||
newPropData.Wrapper = originalPropData.Wrapper;
|
||||
newPropData.Wrapper.name = $"p+{newPropData.ObjectId}~{newPropData.InstanceId}";
|
||||
|
||||
// Copy sync values
|
||||
Array.Copy(newPropData.CustomFloats, originalPropData.CustomFloats, newPropData.CustomFloatsAmount);
|
||||
|
||||
CVRSyncHelper.ApplyPropValuesSpawn(newPropData);
|
||||
|
||||
// Place the prop in the additive content scene
|
||||
PlacePropInAdditiveContentScene(newPropData.Spawnable);
|
||||
|
||||
// Clear old data so Recycle() doesn't delete our prop
|
||||
originalPropData.Spawnable = null;
|
||||
originalPropData.Wrapper = null;
|
||||
originalPropData.Recycle();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool OnCVRSyncHelperClearProps() // Prevent deleting of our held props on scene load
|
||||
{
|
||||
for (var index = CVRSyncHelper.Props.Count - 1; index >= 0; index--)
|
||||
{
|
||||
CVRSyncHelper.PropData prop = CVRSyncHelper.Props[index];
|
||||
if (prop.Spawnable != null && _heldPropData.Contains(prop))
|
||||
continue; // Do not recycle props that are valid & held
|
||||
|
||||
DeleteOrRecycleProp(prop);
|
||||
}
|
||||
|
||||
CVRSyncHelper.MySpawnedPropInstanceIds.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool OnBetterBetterCharacterControllerSetSitting(bool isSitting, CVRSeat cvrSeat = null, bool callExitSeat = true)
|
||||
{
|
||||
if (!_ignoreNextSeatExit) return true;
|
||||
_ignoreNextSeatExit = false;
|
||||
if (BetterBetterCharacterController.Instance._lastCvrSeat == null) return true; // run original
|
||||
return false; // dont run if there is a chair & we skipped it
|
||||
}
|
||||
|
||||
#endregion Harmony Patches
|
||||
|
||||
#region Game Events
|
||||
|
||||
private object _worldLoadTimer;
|
||||
|
||||
private void OnWorldLoad(string _)
|
||||
{
|
||||
CVRWorld worldInstance = CVRWorld.Instance;
|
||||
if (worldInstance != null && !worldInstance.allowSpawnables)
|
||||
{
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropData) DeleteOrRecycleProp(prop); // Delete all props we kept
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = _heldPropData.Count - 1; index >= 0; index--)
|
||||
{
|
||||
CVRSyncHelper.PropData prop = _heldPropData[index];
|
||||
if (prop.Spawnable == null)
|
||||
{
|
||||
DeleteOrRecycleProp(prop);
|
||||
return;
|
||||
}
|
||||
|
||||
// apply positions
|
||||
int stackCount = _spawnablePositionStack.Count;
|
||||
for (int i = stackCount - 1; i >= 0; i--) _spawnablePositionStack.Pop().ReapplyOffsets();
|
||||
}
|
||||
|
||||
// Start a timer, and anything that did not load within 3 seconds will be destroyed
|
||||
if (_worldLoadTimer != null)
|
||||
{
|
||||
MelonCoroutines.Stop(_worldLoadTimer);
|
||||
_worldLoadTimer = null;
|
||||
}
|
||||
_worldLoadTimer = MelonCoroutines.Start(DestroyPersistantPropContainerInFive());
|
||||
_ignoreNextSeatExit = true; // just in case we are in a car / vehicle
|
||||
}
|
||||
|
||||
private IEnumerator DestroyPersistantPropContainerInFive()
|
||||
{
|
||||
yield return new WaitForSeconds(3f);
|
||||
_worldLoadTimer = null;
|
||||
Object.Destroy(_persistantPropsContainer);
|
||||
_persistantPropsContainer = null;
|
||||
_keyToPropData.Clear(); // no more chances
|
||||
}
|
||||
|
||||
private static void OnWorldUnload(string _)
|
||||
{
|
||||
// Prevent deleting of our held props on scene destruction
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropData)
|
||||
{
|
||||
if (prop.Spawnable == null) continue;
|
||||
PlacePropInPersistantPropsContainer(prop.Spawnable);
|
||||
_spawnablePositionStack.Push(new SpawnablePositionContainer(prop.Spawnable));
|
||||
}
|
||||
|
||||
// Likely in a vehicle
|
||||
_heightOffset = BetterBetterCharacterController.Instance._lastCvrSeat != null
|
||||
? GetHeightOffsetFromPlayer()
|
||||
: 0f;
|
||||
}
|
||||
|
||||
private static void OnInstanceConnected(string _)
|
||||
{
|
||||
// Request the server to respawn our props by GUID, and add a secret key to the propData to identify it
|
||||
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropData)
|
||||
{
|
||||
if (prop.Spawnable == null) continue;
|
||||
|
||||
// Generate a new identity key for the prop (this is used to identify the prop when we respawn it)
|
||||
Vector3 identityKey = new(Random.Range(0, 1000), Random.Range(0, 1000), Random.Range(0, 1000));
|
||||
_keyToPropData.Add(identityKey, prop);
|
||||
|
||||
SpawnPropFromGuid(prop.ObjectId,
|
||||
new Vector3(prop.PositionX, prop.PositionY, prop.PositionZ),
|
||||
new Vector3(prop.RotationX, prop.RotationY, prop.RotationZ),
|
||||
identityKey);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Game Events
|
||||
|
||||
#region Util
|
||||
|
||||
private static bool GetPropData(CVRSpawnable spawnable, out CVRSyncHelper.PropData propData)
|
||||
{
|
||||
if (spawnable == null)
|
||||
{
|
||||
propData = null;
|
||||
return false;
|
||||
}
|
||||
foreach (CVRSyncHelper.PropData data in CVRSyncHelper.Props)
|
||||
{
|
||||
if (data.InstanceId != spawnable.instanceId) continue;
|
||||
propData = data;
|
||||
return true;
|
||||
}
|
||||
propData = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool GetPropDataById(string instanceId, out CVRSyncHelper.PropData propData)
|
||||
{
|
||||
foreach (CVRSyncHelper.PropData data in CVRSyncHelper.Props)
|
||||
{
|
||||
if (data.InstanceId != instanceId) continue;
|
||||
propData = data;
|
||||
return true;
|
||||
}
|
||||
propData = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void PlacePropInAdditiveContentScene(CVRSpawnable spawnable)
|
||||
{
|
||||
spawnable.transform.parent.SetParent(null); // Unparent from the prop container
|
||||
SceneManager.MoveGameObjectToScene(spawnable.transform.parent.gameObject,
|
||||
SceneManager.GetSceneByName(CVRObjectLoader.AdditiveContentSceneName));
|
||||
}
|
||||
|
||||
private static void PlacePropInPersistantPropsContainer(CVRSpawnable spawnable)
|
||||
{
|
||||
spawnable.transform.parent.SetParent(GetOrCreatePropsContainer().transform);
|
||||
}
|
||||
|
||||
private static void DeleteOrRecycleProp(CVRSyncHelper.PropData prop)
|
||||
{
|
||||
if (prop.Spawnable == null) prop.Recycle();
|
||||
else prop.Spawnable.Delete();
|
||||
if (_heldPropData.Contains(prop)) _heldPropData.Remove(prop);
|
||||
}
|
||||
|
||||
private static void SpawnPropFromGuid(string propGuid, Vector3 position, Vector3 rotation, Vector3 identityKey)
|
||||
{
|
||||
using DarkRiftWriter darkRiftWriter = DarkRiftWriter.Create();
|
||||
darkRiftWriter.Write(propGuid);
|
||||
darkRiftWriter.Write(position.x);
|
||||
darkRiftWriter.Write(position.y);
|
||||
darkRiftWriter.Write(position.z);
|
||||
darkRiftWriter.Write(rotation.x);
|
||||
darkRiftWriter.Write(rotation.y);
|
||||
darkRiftWriter.Write(rotation.z);
|
||||
darkRiftWriter.Write(identityKey.x); // for scale, but unused by CVR
|
||||
darkRiftWriter.Write(identityKey.y); // we will use this to identify our prop
|
||||
darkRiftWriter.Write(identityKey.z); // and recycle existing instance if it exists
|
||||
darkRiftWriter.Write(0f); // if not zero, prop spawn will be rejected by gs
|
||||
using Message message = Message.Create(10050, darkRiftWriter);
|
||||
NetworkManager.Instance.GameNetwork.SendMessage(message, SendMode.Reliable);
|
||||
}
|
||||
|
||||
private static Vector3 GetIdentityKeyFromPropData(CVRSyncHelper.PropData propData)
|
||||
=> new(propData.ScaleX, propData.ScaleY, propData.ScaleZ);
|
||||
|
||||
private const int WORLD_RAYCAST_LAYER_MASK =
|
||||
(1 << 0) | // Default
|
||||
(1 << 16) | (1 << 17) | (1 << 18) | (1 << 19) |
|
||||
(1 << 20) | (1 << 21) | (1 << 22) | (1 << 23) |
|
||||
(1 << 24) | (1 << 25) | (1 << 26) | (1 << 27) |
|
||||
(1 << 28) | (1 << 29) | (1 << 30) | (1 << 31);
|
||||
|
||||
private static float GetHeightOffsetFromPlayer()
|
||||
{
|
||||
Vector3 playerPos = PlayerSetup.Instance.GetPlayerPosition();
|
||||
Ray ray = new(playerPos, Vector3.down);
|
||||
|
||||
// ReSharper disable once Unity.PreferNonAllocApi
|
||||
RaycastHit[] hits = Physics.RaycastAll(ray, 1000f, WORLD_RAYCAST_LAYER_MASK, QueryTriggerInteraction.Ignore);
|
||||
Scene baseScene = SceneManager.GetActiveScene();
|
||||
|
||||
float closestDist = float.MaxValue;
|
||||
Vector3 closestPoint = Vector3.zero;
|
||||
bool foundValidHit = false;
|
||||
|
||||
foreach (RaycastHit hit in hits)
|
||||
{
|
||||
if (hit.collider.gameObject.scene != baseScene) continue; // Ignore objects not in the world scene
|
||||
if (!(hit.distance < closestDist)) continue;
|
||||
closestDist = hit.distance;
|
||||
closestPoint = hit.point;
|
||||
foundValidHit = true;
|
||||
}
|
||||
|
||||
if (!foundValidHit) return 0f; // TODO: idk if i should do this
|
||||
float offset = playerPos.y - closestPoint.y;
|
||||
return Mathf.Clamp(offset, 0f, 20f);
|
||||
}
|
||||
|
||||
#endregion Util
|
||||
|
||||
#region Helper Classes
|
||||
|
||||
private readonly struct SpawnablePositionContainer
|
||||
{
|
||||
private readonly CVRSpawnable _spawnable;
|
||||
private readonly Vector3[] _posOffsets;
|
||||
private readonly Quaternion[] _rotOffsets;
|
||||
|
||||
public SpawnablePositionContainer(CVRSpawnable spawnable)
|
||||
{
|
||||
_spawnable = spawnable;
|
||||
|
||||
int syncedTransforms = 1 + _spawnable.subSyncs.Count; // root + subSyncs
|
||||
|
||||
_posOffsets = new Vector3[syncedTransforms];
|
||||
_rotOffsets = new Quaternion[syncedTransforms];
|
||||
|
||||
Transform playerTransform = PlayerSetup.Instance.transform;
|
||||
|
||||
// Save root offset relative to player
|
||||
Transform _spawnableTransform = _spawnable.transform;
|
||||
_posOffsets[0] = playerTransform.InverseTransformPoint(_spawnableTransform.position);
|
||||
_rotOffsets[0] = Quaternion.Inverse(playerTransform.rotation) * _spawnableTransform.rotation;
|
||||
|
||||
// Save subSync offsets relative to player
|
||||
for (int i = 0; i < _spawnable.subSyncs.Count; i++)
|
||||
{
|
||||
Transform subSyncTransform = _spawnable.subSyncs[i].transform;
|
||||
if (subSyncTransform == null) continue;
|
||||
_posOffsets[i + 1] = playerTransform.InverseTransformPoint(subSyncTransform.position);
|
||||
_rotOffsets[i + 1] = Quaternion.Inverse(playerTransform.rotation) * subSyncTransform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReapplyOffsets()
|
||||
{
|
||||
Transform playerTransform = PlayerSetup.Instance.transform;
|
||||
|
||||
// Reapply to root
|
||||
Vector3 rootWorldPos = playerTransform.TransformPoint(_posOffsets[0]);
|
||||
rootWorldPos.y += _heightOffset;
|
||||
_spawnable.transform.position = rootWorldPos;
|
||||
_spawnable.transform.rotation = playerTransform.rotation * _rotOffsets[0];
|
||||
|
||||
// Reapply to subSyncs
|
||||
for (int i = 0; i < _spawnable.subSyncs.Count; i++)
|
||||
{
|
||||
Transform subSyncTransform = _spawnable.subSyncs[i].transform;
|
||||
if (subSyncTransform == null) continue;
|
||||
|
||||
Vector3 subWorldPos = playerTransform.TransformPoint(_posOffsets[i + 1]);
|
||||
subWorldPos.y += _heightOffset;
|
||||
subSyncTransform.position = subWorldPos;
|
||||
subSyncTransform.rotation = playerTransform.rotation * _rotOffsets[i + 1];
|
||||
}
|
||||
|
||||
// hack
|
||||
_spawnable.needsUpdate = true;
|
||||
_spawnable.UpdateSubSyncValues();
|
||||
_spawnable.sendUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Helper Classes
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.YouAreMyPropNowWeAreHavingSoftTacosLaterMod),
|
||||
nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
19
YouAreMyPropNowWeAreHavingSoftTacosLater/README.md
Normal file
19
YouAreMyPropNowWeAreHavingSoftTacosLater/README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# YouAreMyPropNowWeAreHavingSoftTacosLater
|
||||
|
||||
Lets you bring held & attached props through world loads.
|
||||
|
||||
https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO
|
||||
|
||||
## Examples
|
||||
https://fixupx.com/NotAKidoS/status/1910545346922422675
|
||||
|
||||
---
|
||||
|
||||
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.
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>YouAreMineNow</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
23
YouAreMyPropNowWeAreHavingSoftTacosLater/format.json
Normal file
23
YouAreMyPropNowWeAreHavingSoftTacosLater/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO",
|
||||
"searchtags": [
|
||||
"prop",
|
||||
"spawn",
|
||||
"friend",
|
||||
"load"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
|
||||
"changelog": "- Initial Release",
|
||||
"embedcolor": "#00FFFF"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue