mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-03 06:49:22 +00:00
Compare commits
13 commits
c455d20f9c
...
6d28f734da
Author | SHA1 | Date | |
---|---|---|---|
|
6d28f734da | ||
|
969bd00df3 | ||
|
7deddd88ea | ||
|
1a591749c6 | ||
|
218344a121 | ||
|
07daceea44 | ||
|
aaeb187b9e | ||
|
e378a717d3 | ||
|
a0a859aa86 | ||
|
13e206cd58 | ||
|
548fcf72bc | ||
|
ee4df06d2e | ||
|
faf9d48fb6 |
33 changed files with 193 additions and 2053 deletions
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.CustomSpawnPoint.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.2";
|
||||
public const string Version = "1.0.3";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -41,20 +41,20 @@ internal static class SpawnPointManager
|
|||
{
|
||||
while (ViewManager.Instance == null)
|
||||
yield return null;
|
||||
while (ViewManager.Instance.gameMenuView == null)
|
||||
while (ViewManager.Instance.cohtmlView == null)
|
||||
yield return null;
|
||||
while (ViewManager.Instance.gameMenuView.Listener == null)
|
||||
while (ViewManager.Instance.cohtmlView.Listener == null)
|
||||
yield return null;
|
||||
|
||||
ViewManager.Instance.OnUiConfirm.AddListener(OnClearSpawnpointConfirm);
|
||||
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
|
||||
ViewManager.Instance.cohtmlView.Listener.FinishLoad += (_) =>
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View._view.ExecuteScript(spawnpointJs);
|
||||
ViewManager.Instance.cohtmlView.View._view.ExecuteScript(spawnpointJs);
|
||||
};
|
||||
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
|
||||
ViewManager.Instance.cohtmlView.Listener.ReadyForBindings += () =>
|
||||
{
|
||||
// listen for setting the spawn point on our custom button
|
||||
ViewManager.Instance.gameMenuView.View.BindCall("NAKCallSetSpawnpoint", SetSpawnPoint);
|
||||
ViewManager.Instance.cohtmlView.View.BindCall("NAKCallSetSpawnpoint", SetSpawnPoint);
|
||||
};
|
||||
|
||||
// create our custom spawn point object
|
||||
|
@ -179,7 +179,7 @@ internal static class SpawnPointManager
|
|||
|
||||
private static void UpdateMenuButtonState(bool hasSpawnpoint, bool isInWorld)
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString());
|
||||
ViewManager.Instance.cohtmlView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString());
|
||||
}
|
||||
|
||||
private static void ClearCurrentWorldState()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 228,
|
||||
"name": "CustomSpawnPoint",
|
||||
"modversion": "1.0.2",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Replaces the unused Images button in the World Details page with a button to set a custom spawn point.",
|
||||
|
@ -17,8 +17,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/CustomSpawnPoint.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -16,17 +16,17 @@ namespace NAK.PropUndoButton;
|
|||
|
||||
public class PropUndoButton : MelonMod
|
||||
{
|
||||
public static readonly MelonPreferences_Category Category =
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(PropUndoButton));
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
||||
private static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
||||
Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
||||
private static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
||||
Category.CreateEntry("Use SFX", true,
|
||||
description: "Toggle audio queues for prop spawn, undo, redo, and warning.");
|
||||
|
||||
internal static List<DeletedProp> deletedProps = new();
|
||||
private static readonly List<DeletedProp> deletedProps = [];
|
||||
|
||||
// audio clip names, InterfaceAudio adds "PropUndo_" prefix
|
||||
private const string sfx_spawn = "PropUndo_sfx_spawn";
|
||||
|
@ -92,11 +92,11 @@ public class PropUndoButton : MelonMod
|
|||
if (!File.Exists(clipPath))
|
||||
{
|
||||
// read the clip data from embedded resources
|
||||
byte[] clipData = null;
|
||||
byte[] clipData;
|
||||
var resourceName = "PropUndoButton.SFX." + clipName;
|
||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
|
||||
{
|
||||
clipData = new byte[stream.Length];
|
||||
clipData = new byte[stream!.Length];
|
||||
stream.Read(clipData, 0, clipData.Length);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/UndoPropButton"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.PropUndoButton.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 147,
|
||||
"name": "PropUndoButton",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "**CTRL+Z** to undo latest spawned prop. **CTRL+SHIFT+Z** to redo deleted prop.\nIncludes optional SFX for prop spawn, undo, redo, warn, and deny, which can be disabled in settings.\n\nYou can replace the sfx in 'ChilloutVR\\ChilloutVR_Data\\StreamingAssets\\Cohtml\\UIResources\\GameUI\\mods\\PropUndo\\audio'.",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropUndoButton.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/PropUndoButton.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PropUndoButton/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#00FFFF"
|
||||
}
|
|
@ -18,7 +18,7 @@ using NAK.RCCVirtualSteeringWheel.Properties;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
|
@ -28,6 +28,6 @@ using NAK.RCCVirtualSteeringWheel.Properties;
|
|||
namespace NAK.RCCVirtualSteeringWheel.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.4";
|
||||
public const string Version = "1.0.6";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -26,8 +26,8 @@ public class SteeringWheelPickup : Pickupable
|
|||
|
||||
public override void OnUseDown(InteractionContext context) { }
|
||||
public override void OnUseUp(InteractionContext context) { }
|
||||
public override void OnFlingTowardsTarget(Vector3 target) { }
|
||||
|
||||
public override void FlingTowardsTarget(ControllerRay controllerRay) { }
|
||||
|
||||
public override void OnGrab(InteractionContext context, Vector3 grabPoint) {
|
||||
if (ControllerRay?.pivotPoint != null)
|
||||
root.StartTrackingTransform(ControllerRay.transform);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 248,
|
||||
"name": "RCCVirtualSteeringWheel",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.6",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RCCVirtualSteeringWheel.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/",
|
||||
"changelog": "- Recompiled for 2025r179\n- Fixed generated steering wheel pickup collider not being marked isTrigger\n- Fixed error spam when sitting in a CVRSeat with lockControls, but no RCC component in parent",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI_RC.Systems.IK.VRIKHandlers;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI.CCK.Components;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.ScrollFlight.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 219,
|
||||
"name": "ScrollFlight",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Scroll-wheel to adjust flight speed in Desktop. Stole idea from Luc.",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ScrollFlight.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ScrollFlight.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight/",
|
||||
"changelog": "- Recompiled for 2025r179\n- Added an option to reset flight speed to default on exit flight",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -28,7 +28,7 @@ public class ShareBubblesMod : MelonMod
|
|||
|
||||
LoadAssetBundle();
|
||||
}
|
||||
|
||||
|
||||
public override void OnApplicationQuit()
|
||||
{
|
||||
ModNetwork.Unsubscribe();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@ using NAK.ShareBubbles.Properties;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
|
@ -27,6 +27,6 @@ using NAK.ShareBubbles.Properties;
|
|||
namespace NAK.ShareBubbles.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.5";
|
||||
public const string Version = "1.1.6";
|
||||
public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler";
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using System.Net;
|
||||
|
||||
namespace NAK.ShareBubbles.API.Exceptions;
|
||||
|
||||
public class ShareApiException : Exception
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; } // TODO: network back status code to claiming client, to show why the request failed
|
||||
public string UserFriendlyMessage { get; }
|
||||
|
||||
public ShareApiException(HttpStatusCode statusCode, string message, string userFriendlyMessage)
|
||||
: base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
UserFriendlyMessage = userFriendlyMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentNotSharedException : ShareApiException
|
||||
{
|
||||
public ContentNotSharedException(string contentId)
|
||||
: base(HttpStatusCode.BadRequest,
|
||||
$"Content {contentId} is not currently shared",
|
||||
"This content is not currently shared with anyone")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentNotFoundException : ShareApiException
|
||||
{
|
||||
public ContentNotFoundException(string contentId)
|
||||
: base(HttpStatusCode.NotFound,
|
||||
$"Content {contentId} not found",
|
||||
"The specified content could not be found")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UserOnlyAllowsSharesFromFriendsException : ShareApiException
|
||||
{
|
||||
public UserOnlyAllowsSharesFromFriendsException(string userId)
|
||||
: base(HttpStatusCode.Forbidden,
|
||||
$"User {userId} only accepts shares from friends",
|
||||
"This user only accepts shares from friends")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UserNotFoundException : ShareApiException
|
||||
{
|
||||
public UserNotFoundException(string userId)
|
||||
: base(HttpStatusCode.NotFound,
|
||||
$"User {userId} not found",
|
||||
"The specified user could not be found")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentAlreadySharedException : ShareApiException
|
||||
{
|
||||
public ContentAlreadySharedException(string contentId, string userId)
|
||||
: base(HttpStatusCode.Conflict,
|
||||
$"Content {contentId} is already shared with user {userId}",
|
||||
"This content is already shared with this user")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Responses;
|
||||
using NAK.ShareBubbles.API.Responses;
|
||||
|
||||
namespace NAK.ShareBubbles.API;
|
||||
|
||||
|
@ -34,6 +33,8 @@ public static class PedestalInfoBatchProcessor
|
|||
{ PedestalType.Prop, false }
|
||||
};
|
||||
|
||||
// This breaks compile accepting this change.
|
||||
// ReSharper disable once ChangeFieldTypeToSystemThreadingLock
|
||||
private static readonly object _lock = new();
|
||||
private const float BATCH_DELAY = 2f;
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace NAK.ShareBubbles.API.Responses;
|
||||
|
||||
public class ActiveSharesResponse
|
||||
{
|
||||
[JsonProperty("value")]
|
||||
public List<ShareUser> Value { get; set; }
|
||||
}
|
||||
|
||||
// Idk why not just reuse UserDetails
|
||||
public class ShareUser
|
||||
{
|
||||
[JsonProperty("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Responses;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.ShareBubbles.API.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NAK.ShareBubbles.API;
|
||||
|
||||
/// <summary>
|
||||
/// API for content sharing management.
|
||||
/// </summary>
|
||||
public static class ShareApiHelper
|
||||
{
|
||||
#region Enums
|
||||
|
||||
public enum ShareContentType
|
||||
{
|
||||
Avatar,
|
||||
Spawnable
|
||||
}
|
||||
|
||||
private enum ShareApiOperation
|
||||
{
|
||||
ShareAvatar,
|
||||
ReleaseAvatar,
|
||||
|
||||
ShareSpawnable,
|
||||
ReleaseSpawnable,
|
||||
|
||||
GetAvatarShares,
|
||||
GetSpawnableShares
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Shares content with a specified user.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content to share</param>
|
||||
/// <param name="contentId">ID of the content</param>
|
||||
/// <param name="userId">Target user ID</param>
|
||||
/// <returns>Response containing share information</returns>
|
||||
/// <exception cref="ShareApiException">Thrown when API request fails</exception>
|
||||
/// <exception cref="UserNotFoundException">Thrown when target user is not found</exception>
|
||||
/// <exception cref="ContentNotFoundException">Thrown when content is not found</exception>
|
||||
/// <exception cref="UserOnlyAllowsSharesFromFriendsException">Thrown when user only accepts shares from friends</exception>
|
||||
/// <exception cref="ContentAlreadySharedException">Thrown when content is already shared with user</exception>
|
||||
public static Task<BaseResponse<T>> ShareContentAsync<T>(ShareContentType type, string contentId, string userId)
|
||||
{
|
||||
ShareApiOperation operation = type == ShareContentType.Avatar
|
||||
? ShareApiOperation.ShareAvatar
|
||||
: ShareApiOperation.ShareSpawnable;
|
||||
|
||||
ShareRequest data = new()
|
||||
{
|
||||
ContentId = contentId,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
return MakeApiRequestAsync<T>(operation, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases shared content from a specified user.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content to release</param>
|
||||
/// <param name="contentId">ID of the content</param>
|
||||
/// <param name="userId">Optional user ID. If null, releases share from self</param>
|
||||
/// <returns>Response indicating success</returns>
|
||||
/// <exception cref="ShareApiException">Thrown when API request fails</exception>
|
||||
/// <exception cref="ContentNotSharedException">Thrown when content is not shared</exception>
|
||||
/// <exception cref="ContentNotFoundException">Thrown when content is not found</exception>
|
||||
/// <exception cref="UserNotFoundException">Thrown when specified user is not found</exception>
|
||||
public static Task<BaseResponse<T>> ReleaseShareAsync<T>(ShareContentType type, string contentId, string userId = null)
|
||||
{
|
||||
ShareApiOperation operation = type == ShareContentType.Avatar
|
||||
? ShareApiOperation.ReleaseAvatar
|
||||
: ShareApiOperation.ReleaseSpawnable;
|
||||
|
||||
// If no user ID is provided, release share from self
|
||||
userId ??= MetaPort.Instance.ownerId;
|
||||
|
||||
ShareRequest data = new()
|
||||
{
|
||||
ContentId = contentId,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
return MakeApiRequestAsync<T>(operation, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all shares for specified content.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content</param>
|
||||
/// <param name="contentId">ID of the content</param>
|
||||
/// <returns>Response containing share information</returns>
|
||||
/// <exception cref="ShareApiException">Thrown when API request fails</exception>
|
||||
/// <exception cref="ContentNotFoundException">Thrown when content is not found</exception>
|
||||
public static Task<BaseResponse<T>> GetSharesAsync<T>(ShareContentType type, string contentId)
|
||||
{
|
||||
ShareApiOperation operation = type == ShareContentType.Avatar
|
||||
? ShareApiOperation.GetAvatarShares
|
||||
: ShareApiOperation.GetSpawnableShares;
|
||||
|
||||
ShareRequest data = new() { ContentId = contentId };
|
||||
return MakeApiRequestAsync<T>(operation, data);
|
||||
}
|
||||
|
||||
#endregion Public API
|
||||
|
||||
#region Private Implementation
|
||||
|
||||
[Serializable]
|
||||
private record ShareRequest
|
||||
{
|
||||
public string ContentId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
private static async Task<BaseResponse<T>> MakeApiRequestAsync<T>(ShareApiOperation operation, ShareRequest data)
|
||||
{
|
||||
ValidateAuthenticationState();
|
||||
|
||||
(string endpoint, HttpMethod method) = GetApiEndpointAndMethod(operation, data);
|
||||
using HttpRequestMessage request = CreateHttpRequest(endpoint, method, data);
|
||||
|
||||
try
|
||||
{
|
||||
using HttpResponseMessage response = await ApiConnection._client.SendAsync(request);
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return HandleApiResponse<T>(response, content, data.ContentId, data.UserId);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
throw new ShareApiException(
|
||||
HttpStatusCode.ServiceUnavailable,
|
||||
$"Failed to communicate with the server: {ex.Message}",
|
||||
"Unable to connect to the server. Please check your internet connection.");
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new ShareApiException(
|
||||
HttpStatusCode.UnprocessableEntity,
|
||||
$"Failed to process response data: {ex.Message}",
|
||||
"Server returned invalid data. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateAuthenticationState()
|
||||
{
|
||||
if (!AuthManager.IsAuthenticated)
|
||||
{
|
||||
throw new ShareApiException(
|
||||
HttpStatusCode.Unauthorized,
|
||||
"User is not authenticated",
|
||||
"Please log in to perform this action");
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpRequestMessage CreateHttpRequest(string endpoint, HttpMethod method, ShareRequest data)
|
||||
{
|
||||
HttpRequestMessage request = new(method, endpoint);
|
||||
|
||||
if (method == HttpMethod.Post)
|
||||
{
|
||||
JObject json = JObject.FromObject(data);
|
||||
request.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static BaseResponse<T> HandleApiResponse<T>(HttpResponseMessage response, string content, string contentId, string userId)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
return CreateSuccessResponse<T>(content);
|
||||
|
||||
// Let specific exceptions propagate up to the caller
|
||||
throw response.StatusCode switch
|
||||
{
|
||||
HttpStatusCode.BadRequest => new ContentNotSharedException(contentId),
|
||||
HttpStatusCode.NotFound when userId != null => new UserNotFoundException(userId),
|
||||
HttpStatusCode.NotFound => new ContentNotFoundException(contentId),
|
||||
HttpStatusCode.Forbidden => new UserOnlyAllowsSharesFromFriendsException(userId),
|
||||
HttpStatusCode.Conflict => new ContentAlreadySharedException(contentId, userId),
|
||||
_ => new ShareApiException(
|
||||
response.StatusCode,
|
||||
$"API request failed with status {response.StatusCode}: {content}",
|
||||
"An unexpected error occurred. Please try again later.")
|
||||
};
|
||||
}
|
||||
|
||||
private static BaseResponse<T> CreateSuccessResponse<T>(string content)
|
||||
{
|
||||
var response = new BaseResponse<T>("")
|
||||
{
|
||||
IsSuccessStatusCode = true,
|
||||
HttpStatusCode = HttpStatusCode.OK
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
response.Data = JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static (string endpoint, HttpMethod method) GetApiEndpointAndMethod(ShareApiOperation operation, ShareRequest data)
|
||||
{
|
||||
string baseUrl = $"{ApiConnection.APIAddress}/{ApiConnection.APIVersion}";
|
||||
string encodedContentId = HttpUtility.UrlEncode(data.ContentId);
|
||||
|
||||
return operation switch
|
||||
{
|
||||
ShareApiOperation.GetAvatarShares => ($"{baseUrl}/avatars/{encodedContentId}/shares", HttpMethod.Get),
|
||||
ShareApiOperation.ShareAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post),
|
||||
ShareApiOperation.ReleaseAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete),
|
||||
ShareApiOperation.GetSpawnableShares => ($"{baseUrl}/spawnables/{encodedContentId}/shares", HttpMethod.Get),
|
||||
ShareApiOperation.ShareSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post),
|
||||
ShareApiOperation.ReleaseSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete),
|
||||
_ => throw new ArgumentException($"Unknown operation: {operation}")
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Private Implementation
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
using ABI_RC.Core.EventSystem;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Exceptions;
|
||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.ShareBubbles.API;
|
||||
using NAK.ShareBubbles.API.Exceptions;
|
||||
using ShareBubbles.ShareBubbles.Implementation;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Exceptions;
|
||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.ShareBubbles.API;
|
||||
using NAK.ShareBubbles.API.Exceptions;
|
||||
using ShareBubbles.ShareBubbles.Implementation;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Networking.IO.Instancing;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using NAK.ShareBubbles.API;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using ABI_RC.Core.Networking.IO.Social;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.IO.Social;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using NAK.ShareBubbles.API;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ShareBubbles.Networking;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using ABI_RC.Systems.ModNetwork;
|
||||
using NAK.ShareBubbles.API;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ShareBubbles.Networking;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ShareBubbles.UI;
|
||||
|
@ -9,11 +11,23 @@ namespace NAK.ShareBubbles.UI;
|
|||
// Must be added manually by ShareBubble creation...
|
||||
public class BubbleInteract : Interactable
|
||||
{
|
||||
public override bool IsInteractableWithinRange(Vector3 sourcePos)
|
||||
public override bool IsInteractable
|
||||
{
|
||||
return Vector3.Distance(transform.position, sourcePos) < 1.5f;
|
||||
get
|
||||
{
|
||||
if (ViewManager.Instance.IsAnyMenuOpen)
|
||||
return true;
|
||||
|
||||
if (!MetaPort.Instance.isUsingVr
|
||||
&& CVRInputManager.Instance.unlockMouse)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsInteractableWithinRange(Vector3 sourcePos) => true;
|
||||
|
||||
public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay)
|
||||
{
|
||||
// Not used
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 244,
|
||||
"name": "ShareBubbles",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.1.6",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc",
|
||||
"description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n- Adds basic in-game Share Management:\n - Directly share content to users within the current instance.\n - Revoke granted or received content shares.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ABI Account settings!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).",
|
||||
|
@ -17,8 +17,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ShareBubbles.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/",
|
||||
"changelog": "- Fixes for 2025r179\n- Fixed Public/Private text on bubble not being correct\n- Fixed bubble displaying as locked or not claimed despite being shared the content if it was private and not owned by you",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.Util.Object_Behaviour;
|
||||
using System.Collections;
|
||||
using ABI_RC.Core;
|
||||
using ABI.CCK.Components;
|
||||
using UnityEngine;
|
||||
|
@ -42,10 +41,8 @@ internal static class CameraLogic
|
|||
}
|
||||
}
|
||||
|
||||
internal static IEnumerator SetupCamera()
|
||||
internal static void SetupCamera()
|
||||
{
|
||||
yield return new WaitUntil(() => PlayerSetup.Instance);
|
||||
|
||||
_thirdPersonCam = new GameObject("ThirdPersonCameraObj", typeof(Camera)).GetComponent<Camera>();
|
||||
|
||||
_cameraFovClone = _thirdPersonCam.gameObject.AddComponent<CameraFovClone>();
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 97)] // do not change color, originally chosen by Davi
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.ThirdPerson.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.1.2";
|
||||
public const string Version = "1.1.3";
|
||||
public const string Author = "Davi & NotAKidoS";
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using MelonLoader;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using static NAK.ThirdPerson.CameraLogic;
|
||||
|
||||
|
@ -13,7 +14,7 @@ public class ThirdPerson : MelonMod
|
|||
Logger = LoggerInstance;
|
||||
|
||||
Patches.Apply(HarmonyInstance);
|
||||
MelonCoroutines.Start(SetupCamera());
|
||||
CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(SetupCamera);
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
{
|
||||
"_id": 16,
|
||||
"name": "ThirdPerson",
|
||||
"modversion": "1.1.2",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.1.3",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "Davi & NotAKidoS",
|
||||
"description": "Allows you to go into third person view by pressing Ctrl + T to toggle and Ctrl + Y to cycle modes.",
|
||||
|
@ -14,9 +14,9 @@
|
|||
"third person"
|
||||
],
|
||||
"requirements": [],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ThirdPerson.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson",
|
||||
"changelog": "- Adjusted the local player scaled event patch to work both on Stable and Nightly (r179 & r180)",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#F61961"
|
||||
}
|
||||
]
|
|
@ -1,11 +1,13 @@
|
|||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using ABI_RC.Core.EventSystem;
|
||||
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.Core.Util.Encryption;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI.CCK.Components;
|
||||
|
@ -64,7 +66,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
#region CVRAttachment Patches
|
||||
|
||||
HarmonyInstance.Patch( // Cannot compile when using nameof
|
||||
typeof(CVRAttachment).GetMethod("\u003CAttachInternal\u003Eg__DoAttachmentSetup\u007C43_0",
|
||||
typeof(CVRAttachment).GetMethod(nameof(CVRAttachment.DoAttachmentSetup),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnCVRAttachmentAttachInternal),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
|
@ -96,6 +98,17 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
|
||||
#endregion CVRSeat Patches
|
||||
|
||||
#region CVRSpawnable Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRSpawnable).GetMethod(nameof(CVRSpawnable.OnDestroy),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(YouAreMyPropNowWeAreHavingSoftTacosLaterMod).GetMethod(nameof(OnSpawnableOnDestroy),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion CVRSpawnable Patches
|
||||
|
||||
#region CVRSyncHelper Patches
|
||||
|
||||
HarmonyInstance.Patch( // Replaces method, original needlessly ToArray???
|
||||
|
@ -147,12 +160,28 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
|
||||
#region Harmony Patches
|
||||
|
||||
private static readonly List<CVRSyncHelper.PropData> _heldPropData = new();
|
||||
[Flags] private enum HeldPropState { None = 0, Pickup = 1, Attachment = 2, Seat = 3 }
|
||||
|
||||
private static readonly Dictionary<CVRSyncHelper.PropData, HeldPropState> _heldPropStates = new();
|
||||
|
||||
private static void AddHeldPropState(CVRSyncHelper.PropData propData, HeldPropState state)
|
||||
{
|
||||
if (!_heldPropStates.TryAdd(propData, state)) _heldPropStates[propData] |= state;
|
||||
}
|
||||
|
||||
private static void RemoveHeldPropState(CVRSyncHelper.PropData propData, HeldPropState state)
|
||||
{
|
||||
if (!_heldPropStates.TryGetValue(propData, out HeldPropState currentState)) return;
|
||||
currentState &= ~state;
|
||||
if (currentState == HeldPropState.None) _heldPropStates.Remove(propData);
|
||||
else _heldPropStates[propData] = currentState;
|
||||
}
|
||||
|
||||
private static GameObject _persistantPropsContainer;
|
||||
private static GameObject GetOrCreatePropsContainer()
|
||||
{
|
||||
if (_persistantPropsContainer != null) return _persistantPropsContainer;
|
||||
_persistantPropsContainer = new("[NAK] PersistantProps");
|
||||
if (_persistantPropsContainer) return _persistantPropsContainer;
|
||||
_persistantPropsContainer = new("YouAreMyPropNowWeAreHavingSoftTacosLater");
|
||||
_persistantPropsContainer.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
_persistantPropsContainer.transform.localScale = Vector3.one;
|
||||
Object.DontDestroyOnLoad(_persistantPropsContainer);
|
||||
|
@ -168,47 +197,50 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
{
|
||||
if (!EntryTrackPickups.Value) return;
|
||||
if (!TryGetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData);
|
||||
AddHeldPropState(propData, HeldPropState.Pickup);
|
||||
}
|
||||
|
||||
private static void OnCVRPickupObjectOnDrop(CVRPickupObject __instance)
|
||||
{
|
||||
if (!TryGetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData);
|
||||
RemoveHeldPropState(propData, HeldPropState.Pickup);
|
||||
}
|
||||
|
||||
private static void OnCVRAttachmentAttachInternal(CVRAttachment __instance)
|
||||
{
|
||||
if (!EntryTrackAttachments.Value) return;
|
||||
if (!TryGetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData);
|
||||
AddHeldPropState(propData, HeldPropState.Attachment);
|
||||
}
|
||||
|
||||
private static void OnCVRAttachmentDeAttach(CVRAttachment __instance)
|
||||
{
|
||||
if (!__instance._isAttached) return; // Can invoke DeAttach without being attached
|
||||
if (!TryGetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData);
|
||||
RemoveHeldPropState(propData, HeldPropState.Attachment);
|
||||
}
|
||||
|
||||
private static void OnCVRSeatSitDown(CVRSeat __instance)
|
||||
{
|
||||
if (!EntryTrackSeats.Value) return;
|
||||
if (!TryGetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (!_heldPropData.Contains(propData)) _heldPropData.Add(propData);
|
||||
AddHeldPropState(propData, HeldPropState.Seat);
|
||||
}
|
||||
|
||||
private static void OnCVRSeatExitSeat(CVRSeat __instance)
|
||||
{
|
||||
if (!TryGetPropData(__instance.GetComponentInParent<CVRSpawnable>(true), out CVRSyncHelper.PropData propData)) return;
|
||||
if (_heldPropData.Contains(propData)) _heldPropData.Remove(propData);
|
||||
RemoveHeldPropState(propData, HeldPropState.Seat);
|
||||
}
|
||||
|
||||
private static void OnSpawnableOnDestroy(CVRSpawnable __instance)
|
||||
{
|
||||
if (!TryGetPropData(__instance, out CVRSyncHelper.PropData propData)) return;
|
||||
_heldPropStates.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)
|
||||
private static bool OnCVRDownloadManagerQueueTask(AssetManagement.UgcMetadata metadata, DownloadTask.ObjectType type, string assetUrl, string fileId, string toAttach, CVRLoadingAvatarController loadingAvatarController = null, bool joinOnComplete = false, bool isHomeRequested = false, CompatibilityVersions compatibilityVersion = CompatibilityVersions.NotSpi, CVREncryptionRouter.EncryptionAlgorithm encryptionAlgorithm = CVREncryptionRouter.EncryptionAlgorithm.Gen1, string spawnerId = null)
|
||||
{
|
||||
if (type != DownloadTask.ObjectType.Prop) return true; // Only care about props
|
||||
|
||||
|
@ -219,14 +251,20 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
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);
|
||||
// Remove original prop data from held, cache states
|
||||
HeldPropState heldState = HeldPropState.None;
|
||||
if (_heldPropStates.ContainsKey(originalPropData))
|
||||
{
|
||||
heldState = _heldPropStates[originalPropData];
|
||||
_heldPropStates.Remove(originalPropData);
|
||||
}
|
||||
|
||||
// If original prop data is null spawn a new prop i guess :(
|
||||
if (originalPropData.Spawnable == null) return true;
|
||||
if (!originalPropData.Spawnable) return true;
|
||||
|
||||
// Add the new prop data to our held props in place of the old one
|
||||
if (!_heldPropData.Contains(newPropData)) _heldPropData.Add(newPropData);
|
||||
// if (!_heldPropData.Contains(newPropData)) _heldPropData.Add(newPropData);
|
||||
_heldPropStates.TryAdd(newPropData, heldState);
|
||||
|
||||
// Apply new prop data to the spawnable
|
||||
newPropData.Spawnable = originalPropData.Spawnable;
|
||||
|
@ -255,7 +293,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
for (var index = CVRSyncHelper.Props.Count - 1; index >= 0; index--)
|
||||
{
|
||||
CVRSyncHelper.PropData prop = CVRSyncHelper.Props[index];
|
||||
if (prop.Spawnable != null && _heldPropData.Contains(prop))
|
||||
if (prop.Spawnable && _heldPropStates.ContainsKey(prop))
|
||||
continue; // Do not recycle props that are valid & held
|
||||
|
||||
DeleteOrRecycleProp(prop);
|
||||
|
@ -269,7 +307,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
{
|
||||
if (!_ignoreNextSeatExit) return true;
|
||||
_ignoreNextSeatExit = false;
|
||||
if (BetterBetterCharacterController.Instance._lastCvrSeat == null) return true; // run original
|
||||
if (!BetterBetterCharacterController.Instance._lastCvrSeat) return true; // run original
|
||||
return false; // dont run if there is a chair & we skipped it
|
||||
}
|
||||
|
||||
|
@ -282,16 +320,16 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
private void OnWorldLoad(string _)
|
||||
{
|
||||
CVRWorld worldInstance = CVRWorld.Instance;
|
||||
if (worldInstance != null && !worldInstance.allowSpawnables)
|
||||
if (worldInstance && !worldInstance.allowSpawnables)
|
||||
{
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropData) DeleteOrRecycleProp(prop); // Delete all props we kept
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropStates.Keys) DeleteOrRecycleProp(prop); // Delete all props we kept
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = _heldPropData.Count - 1; index >= 0; index--)
|
||||
for (var index = _heldPropStates.Count - 1; index >= 0; index--)
|
||||
{
|
||||
CVRSyncHelper.PropData prop = _heldPropData[index];
|
||||
if (prop.Spawnable == null)
|
||||
CVRSyncHelper.PropData prop = _heldPropStates.ElementAt(index).Key;
|
||||
if (!prop.Spawnable)
|
||||
{
|
||||
DeleteOrRecycleProp(prop);
|
||||
return;
|
||||
|
@ -324,9 +362,9 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
private static void OnWorldUnload(string _)
|
||||
{
|
||||
// Prevent deleting of our held props on scene destruction
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropData)
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropStates.Keys)
|
||||
{
|
||||
if (prop.Spawnable == null) continue;
|
||||
if (!prop.Spawnable) continue;
|
||||
PlacePropInPersistantPropsContainer(prop.Spawnable);
|
||||
_spawnablePositionStack.Push(new SpawnablePositionContainer(prop.Spawnable));
|
||||
}
|
||||
|
@ -341,9 +379,9 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
{
|
||||
// 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)
|
||||
foreach (CVRSyncHelper.PropData prop in _heldPropStates.Keys)
|
||||
{
|
||||
if (prop.Spawnable == null) continue;
|
||||
if (!prop.Spawnable) 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));
|
||||
|
@ -362,7 +400,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
|
||||
private static bool TryGetPropData(CVRSpawnable spawnable, out CVRSyncHelper.PropData propData)
|
||||
{
|
||||
if (spawnable == null)
|
||||
if (!spawnable)
|
||||
{
|
||||
propData = null;
|
||||
return false;
|
||||
|
@ -408,9 +446,9 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
|
||||
private static void DeleteOrRecycleProp(CVRSyncHelper.PropData prop)
|
||||
{
|
||||
if (prop.Spawnable == null) prop.Recycle();
|
||||
if (!prop.Spawnable) prop.Recycle();
|
||||
else prop.Spawnable.Delete();
|
||||
if (_heldPropData.Contains(prop)) _heldPropData.Remove(prop);
|
||||
_heldPropStates.Remove(prop);
|
||||
}
|
||||
|
||||
private static void SpawnPropFromGuid(string propGuid, Vector3 position, Vector3 rotation, Vector3 identityKey)
|
||||
|
@ -518,7 +556,7 @@ public class YouAreMyPropNowWeAreHavingSoftTacosLaterMod : MelonMod
|
|||
for (int i = 0; i < _spawnable.subSyncs.Count; i++)
|
||||
{
|
||||
Transform subSyncTransform = _spawnable.subSyncs[i].transform;
|
||||
if (subSyncTransform == null) continue;
|
||||
if (!subSyncTransform) continue;
|
||||
|
||||
Vector3 subWorldPos = playerTransform.TransformPoint(_posOffsets[i + 1]);
|
||||
subWorldPos.y += _heightOffset;
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"_id": 262,
|
||||
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO",
|
||||
"description": "Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO\n\nThere is special logic in place for bringing air vehicles through world loads. If above the ground you will be placed up to 20m above the spawnpoint of the next world.",
|
||||
"searchtags": [
|
||||
"prop",
|
||||
"spawn",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
|
||||
"changelog": "- Initial Release",
|
||||
"embedcolor": "#00FFFF"
|
||||
"changelog": "- Initial release",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue