From 392390cde7a097f851f94f984bf0a130b7c8d342 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com> Date: Fri, 11 Apr 2025 07:39:32 -0500 Subject: [PATCH] [YouAreMyPropNowWeAreHavingSoftTacosLater] Initial push --- NAK_CVR_Mods.sln | 6 + .../Main.cs | 479 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 32 ++ .../README.md | 19 + ...eMyPropNowWeAreHavingSoftTacosLater.csproj | 6 + .../format.json | 23 + 6 files changed, 565 insertions(+) create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/README.md create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj create mode 100644 YouAreMyPropNowWeAreHavingSoftTacosLater/format.json diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 976736f..26deacd 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -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 diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs new file mode 100644 index 0000000..645f7c0 --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Main.cs @@ -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 _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 _keyToPropData = new(); + private static readonly Stack _spawnablePositionStack = new(); + private static bool _ignoreNextSeatExit; + private static float _heightOffset; + + private static void OnCVRPickupObjectOnGrab(CVRPickupObject __instance) + { + if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData); + } + + private static void OnCVRPickupObjectOnDrop(CVRPickupObject __instance) + { + if (!GetPropData(__instance.GetComponentInParent(true), out CVRSyncHelper.PropData propData)) return; + if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData); + } + + private static void OnCVRAttachmentAttachInternal(CVRAttachment __instance) + { + if (!GetPropData(__instance.GetComponentInParent(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(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 +} \ No newline at end of file diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs b/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bfc0a13 --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/Properties/AssemblyInfo.cs @@ -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"; +} \ No newline at end of file diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md b/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md new file mode 100644 index 0000000..9ddff99 --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/README.md @@ -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. diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj b/YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj new file mode 100644 index 0000000..5a8badc --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/YouAreMyPropNowWeAreHavingSoftTacosLater.csproj @@ -0,0 +1,6 @@ + + + + YouAreMineNow + + diff --git a/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json new file mode 100644 index 0000000..78c5bbc --- /dev/null +++ b/YouAreMyPropNowWeAreHavingSoftTacosLater/format.json @@ -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" +} \ No newline at end of file