mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
Make work on Stable, Add response on claim fail
This commit is contained in:
parent
e7b2ad4c31
commit
ef2be62605
15 changed files with 268 additions and 108 deletions
|
@ -19,11 +19,11 @@ public enum PedestalType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PedestalInfoBatchProcessor
|
public static class PedestalInfoBatchProcessor
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<PedestalType, Dictionary<string, TaskCompletionSource<PedestalInfoResponseButWithPublicationState>>> _pendingRequests
|
private static readonly Dictionary<PedestalType, Dictionary<string, TaskCompletionSource<PedestalInfoResponse_ButCorrect>>> _pendingRequests
|
||||||
= new()
|
= new()
|
||||||
{
|
{
|
||||||
{ PedestalType.Avatar, new Dictionary<string, TaskCompletionSource<PedestalInfoResponseButWithPublicationState>>() },
|
{ PedestalType.Avatar, new Dictionary<string, TaskCompletionSource<PedestalInfoResponse_ButCorrect>>() },
|
||||||
{ PedestalType.Prop, new Dictionary<string, TaskCompletionSource<PedestalInfoResponseButWithPublicationState>>() }
|
{ PedestalType.Prop, new Dictionary<string, TaskCompletionSource<PedestalInfoResponse_ButCorrect>>() }
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<PedestalType, bool> _isBatchProcessing
|
private static readonly Dictionary<PedestalType, bool> _isBatchProcessing
|
||||||
|
@ -36,9 +36,9 @@ public static class PedestalInfoBatchProcessor
|
||||||
private static readonly object _lock = new();
|
private static readonly object _lock = new();
|
||||||
private const float BATCH_DELAY = 2f;
|
private const float BATCH_DELAY = 2f;
|
||||||
|
|
||||||
public static Task<PedestalInfoResponseButWithPublicationState> QueuePedestalInfoRequest(PedestalType type, string contentId)
|
public static Task<PedestalInfoResponse_ButCorrect> QueuePedestalInfoRequest(PedestalType type, string contentId)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<PedestalInfoResponseButWithPublicationState>();
|
var tcs = new TaskCompletionSource<PedestalInfoResponse_ButCorrect>();
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
@ -62,12 +62,12 @@ public static class PedestalInfoBatchProcessor
|
||||||
await Task.Delay(TimeSpan.FromSeconds(BATCH_DELAY));
|
await Task.Delay(TimeSpan.FromSeconds(BATCH_DELAY));
|
||||||
|
|
||||||
List<string> contentIds;
|
List<string> contentIds;
|
||||||
Dictionary<string, TaskCompletionSource<PedestalInfoResponseButWithPublicationState>> requestBatch;
|
Dictionary<string, TaskCompletionSource<PedestalInfoResponse_ButCorrect>> requestBatch;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
contentIds = _pendingRequests[type].Keys.ToList();
|
contentIds = _pendingRequests[type].Keys.ToList();
|
||||||
requestBatch = new Dictionary<string, TaskCompletionSource<PedestalInfoResponseButWithPublicationState>>(_pendingRequests[type]);
|
requestBatch = new Dictionary<string, TaskCompletionSource<PedestalInfoResponse_ButCorrect>>(_pendingRequests[type]);
|
||||||
_pendingRequests[type].Clear();
|
_pendingRequests[type].Clear();
|
||||||
_isBatchProcessing[type] = false;
|
_isBatchProcessing[type] = false;
|
||||||
//ShareBubblesMod.Logger.Msg($"Processing {type} pedestal info batch with {contentIds.Count} items");
|
//ShareBubblesMod.Logger.Msg($"Processing {type} pedestal info batch with {contentIds.Count} items");
|
||||||
|
@ -82,7 +82,7 @@ public static class PedestalInfoBatchProcessor
|
||||||
_ => throw new ArgumentException($"Unsupported pedestal type: {type}")
|
_ => throw new ArgumentException($"Unsupported pedestal type: {type}")
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await ApiConnection.MakeRequest<IEnumerable<PedestalInfoResponseButWithPublicationState>>(operation, contentIds);
|
var response = await ApiConnection.MakeRequest<IEnumerable<PedestalInfoResponse_ButCorrect>>(operation, contentIds);
|
||||||
|
|
||||||
if (response?.Data != null)
|
if (response?.Data != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
using ABI_RC.Core.Networking.API.Responses;
|
|
||||||
|
|
||||||
namespace NAK.ShareBubbles.API.Responses;
|
|
||||||
|
|
||||||
/// Same as PedestalInfoResponse, but with an additional field for publication state, if you could not tell by the name.
|
|
||||||
/// TODO: actually waiting on luc to add Published to PedestalInfoResponse
|
|
||||||
[Serializable]
|
|
||||||
public class PedestalInfoResponseButWithPublicationState : UgcResponse
|
|
||||||
{
|
|
||||||
public UserDetails User { get; set; }
|
|
||||||
public bool Permitted { get; set; }
|
|
||||||
public bool Published { get; set; }
|
|
||||||
}
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
using ABI_RC.Core.Networking.API.Responses;
|
||||||
|
|
||||||
|
namespace NAK.ShareBubbles.API.Responses;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class PedestalInfoResponse_ButCorrect : UgcResponse
|
||||||
|
{
|
||||||
|
public UserDetails User { get; set; }
|
||||||
|
public bool Published { get; set; } // Client mislabelled this as Permitted, but it's actually Published
|
||||||
|
}
|
|
@ -2,7 +2,10 @@
|
||||||
using ABI_RC.Core.InteractionSystem;
|
using ABI_RC.Core.InteractionSystem;
|
||||||
using ABI_RC.Core.IO;
|
using ABI_RC.Core.IO;
|
||||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
using NAK.ShareBubbles.API;
|
using NAK.ShareBubbles.API;
|
||||||
|
using NAK.ShareBubbles.API.Exceptions;
|
||||||
|
using ShareBubbles.ShareBubbles.Implementation;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
@ -36,8 +39,10 @@ namespace NAK.ShareBubbles.Impl
|
||||||
Name = infoResponse.Name,
|
Name = infoResponse.Name,
|
||||||
ImageUrl = infoResponse.ImageUrl,
|
ImageUrl = infoResponse.ImageUrl,
|
||||||
AuthorId = infoResponse.User.Id,
|
AuthorId = infoResponse.User.Id,
|
||||||
IsPermitted = infoResponse.Permitted,
|
|
||||||
IsPublic = infoResponse.Published,
|
IsPublic = infoResponse.Published,
|
||||||
|
|
||||||
|
// Permit access if Public, Owned, or (CANNOT DO PRIVATE & SHARED CAUSE API DOESNT GIVE)
|
||||||
|
IsPermitted = infoResponse.Published || infoResponse.User.Id == MetaPort.Instance.ownerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl);
|
downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl);
|
||||||
|
@ -57,28 +62,40 @@ namespace NAK.ShareBubbles.Impl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleClaimAccept(string userId, Action<bool> onClaimActionCompleted)
|
public async Task<ShareClaimResult> HandleClaimAccept(string userId)
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
|
if (details == null)
|
||||||
|
return ShareClaimResult.Rejected();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await ShareApiHelper.ShareContentAsync<BaseResponse>(
|
await ShareApiHelper.ShareContentAsync<BaseResponse>(
|
||||||
ShareApiHelper.ShareContentType.Avatar, avatarId, userId);
|
ShareApiHelper.ShareContentType.Avatar,
|
||||||
|
avatarId,
|
||||||
|
userId);
|
||||||
|
|
||||||
// Store the temporary share to revoke when either party leaves the instance
|
// Add to temp shares if session access
|
||||||
if (bubble.Data.Access == ShareAccess.Session)
|
if (bubble.Data.Access == ShareAccess.Session)
|
||||||
|
{
|
||||||
TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Avatar,
|
TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Avatar,
|
||||||
avatarId, userId);
|
avatarId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
onClaimActionCompleted(response.IsSuccessStatusCode);
|
return ShareClaimResult.Success(bubble.Data.Access == ShareAccess.Session);
|
||||||
|
}
|
||||||
|
catch (ContentAlreadySharedException)
|
||||||
|
{
|
||||||
|
return ShareClaimResult.AlreadyShared();
|
||||||
|
}
|
||||||
|
catch (UserOnlyAllowsSharesFromFriendsException)
|
||||||
|
{
|
||||||
|
return ShareClaimResult.FriendsOnly();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ShareBubblesMod.Logger.Error($"Error sharing avatar: {ex.Message}");
|
ShareBubblesMod.Logger.Error($"Error sharing avatar: {ex.Message}");
|
||||||
onClaimActionCompleted(false);
|
return ShareClaimResult.Rejected();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ViewDetailsPage()
|
public void ViewDetailsPage()
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace NAK.ShareBubbles.Impl;
|
using ShareBubbles.ShareBubbles.Implementation;
|
||||||
|
|
||||||
|
namespace NAK.ShareBubbles.Impl;
|
||||||
|
|
||||||
public interface IShareBubbleImpl
|
public interface IShareBubbleImpl
|
||||||
{
|
{
|
||||||
|
@ -8,6 +10,6 @@ public interface IShareBubbleImpl
|
||||||
Task FetchContentInfo(); // Load the content info from the API
|
Task FetchContentInfo(); // Load the content info from the API
|
||||||
void ViewDetailsPage(); // Open the details page for the content
|
void ViewDetailsPage(); // Open the details page for the content
|
||||||
void EquipContent(); // Equip the content (Switch/Select)
|
void EquipContent(); // Equip the content (Switch/Select)
|
||||||
void HandleClaimAccept(string userId, Action<bool> onClaimActionCompleted); // Handle the claim action (Share via API)
|
Task<ShareClaimResult> HandleClaimAccept(string userId);
|
||||||
void Cleanup(); // Cleanup any resources
|
void Cleanup(); // Cleanup any resources
|
||||||
}
|
}
|
27
ShareBubbles/ShareBubbles/Implementation/ShareClaimResult.cs
Normal file
27
ShareBubbles/ShareBubbles/Implementation/ShareClaimResult.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using NAK.ShareBubbles.Networking;
|
||||||
|
|
||||||
|
namespace ShareBubbles.ShareBubbles.Implementation;
|
||||||
|
|
||||||
|
public class ShareClaimResult
|
||||||
|
{
|
||||||
|
public ModNetwork.ClaimResponseType ResponseType { get; }
|
||||||
|
public bool RequiresSessionTracking { get; }
|
||||||
|
|
||||||
|
private ShareClaimResult(ModNetwork.ClaimResponseType responseType, bool requiresSessionTracking = false)
|
||||||
|
{
|
||||||
|
ResponseType = responseType;
|
||||||
|
RequiresSessionTracking = requiresSessionTracking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShareClaimResult Success(bool isSessionShare = false)
|
||||||
|
=> new(ModNetwork.ClaimResponseType.Accepted, isSessionShare);
|
||||||
|
|
||||||
|
public static ShareClaimResult AlreadyShared()
|
||||||
|
=> new(ModNetwork.ClaimResponseType.AlreadyShared);
|
||||||
|
|
||||||
|
public static ShareClaimResult FriendsOnly()
|
||||||
|
=> new(ModNetwork.ClaimResponseType.NotAcceptingSharesFromNonFriends);
|
||||||
|
|
||||||
|
public static ShareClaimResult Rejected()
|
||||||
|
=> new(ModNetwork.ClaimResponseType.Rejected);
|
||||||
|
}
|
|
@ -2,7 +2,10 @@
|
||||||
using ABI_RC.Core.IO;
|
using ABI_RC.Core.IO;
|
||||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||||
using ABI_RC.Core.Player;
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
using NAK.ShareBubbles.API;
|
using NAK.ShareBubbles.API;
|
||||||
|
using NAK.ShareBubbles.API.Exceptions;
|
||||||
|
using ShareBubbles.ShareBubbles.Implementation;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
@ -36,8 +39,10 @@ namespace NAK.ShareBubbles.Impl
|
||||||
Name = infoResponse.Name,
|
Name = infoResponse.Name,
|
||||||
ImageUrl = infoResponse.ImageUrl,
|
ImageUrl = infoResponse.ImageUrl,
|
||||||
AuthorId = infoResponse.User.Id,
|
AuthorId = infoResponse.User.Id,
|
||||||
IsPermitted = infoResponse.Permitted,
|
|
||||||
IsPublic = infoResponse.Published,
|
IsPublic = infoResponse.Published,
|
||||||
|
|
||||||
|
// Permit access if Public, Owned, or (CANNOT DO PRIVATE & SHARED CAUSE API DOESNT GIVE)
|
||||||
|
IsPermitted = infoResponse.Published || infoResponse.User.Id == MetaPort.Instance.ownerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl);
|
downloadedTexture = await ImageCache.GetImageAsync(details.ImageUrl);
|
||||||
|
@ -57,36 +62,40 @@ namespace NAK.ShareBubbles.Impl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleClaimAccept(string userId, Action<bool> onClaimActionCompleted)
|
public async Task<ShareClaimResult> HandleClaimAccept(string userId)
|
||||||
{
|
{
|
||||||
if (details == null)
|
if (details == null)
|
||||||
{
|
return ShareClaimResult.Rejected();
|
||||||
onClaimActionCompleted(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await ShareApiHelper.ShareContentAsync<BaseResponse>(
|
await ShareApiHelper.ShareContentAsync<BaseResponse>(
|
||||||
ShareApiHelper.ShareContentType.Spawnable,
|
ShareApiHelper.ShareContentType.Spawnable,
|
||||||
spawnableId,
|
spawnableId,
|
||||||
userId);
|
userId);
|
||||||
|
|
||||||
// Store the temporary share to revoke when either party leaves the instance
|
// Add to temp shares if session access
|
||||||
if (bubble.Data.Access == ShareAccess.Session)
|
if (bubble.Data.Access == ShareAccess.Session)
|
||||||
|
{
|
||||||
TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Spawnable,
|
TempShareManager.Instance.AddTempShare(ShareApiHelper.ShareContentType.Spawnable,
|
||||||
spawnableId, userId);
|
spawnableId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
onClaimActionCompleted(response.IsSuccessStatusCode);
|
return ShareClaimResult.Success(bubble.Data.Access == ShareAccess.Session);
|
||||||
|
}
|
||||||
|
catch (ContentAlreadySharedException)
|
||||||
|
{
|
||||||
|
return ShareClaimResult.AlreadyShared();
|
||||||
|
}
|
||||||
|
catch (UserOnlyAllowsSharesFromFriendsException)
|
||||||
|
{
|
||||||
|
return ShareClaimResult.FriendsOnly();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ShareBubblesMod.Logger.Error($"Error sharing spawnable: {ex.Message}");
|
ShareBubblesMod.Logger.Error($"Error sharing spawnable: {ex.Message}");
|
||||||
onClaimActionCompleted(false);
|
return ShareClaimResult.Rejected();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ViewDetailsPage()
|
public void ViewDetailsPage()
|
||||||
|
|
|
@ -7,5 +7,7 @@ public static partial class ModNetwork
|
||||||
private const string NetworkVersion = "1.0.1"; // change each time network protocol changes
|
private const string NetworkVersion = "1.0.1"; // change each time network protocol changes
|
||||||
private const string ModId = $"NAK.SB:{NetworkVersion}"; // Cannot exceed 32 characters
|
private const string ModId = $"NAK.SB:{NetworkVersion}"; // Cannot exceed 32 characters
|
||||||
|
|
||||||
|
private const float ClaimRequestTimeout = 30f; // 30 second timeout
|
||||||
|
|
||||||
#endregion Constants
|
#endregion Constants
|
||||||
}
|
}
|
|
@ -29,9 +29,18 @@ public static partial class ModNetwork
|
||||||
|
|
||||||
private enum MNLogLevel : byte
|
private enum MNLogLevel : byte
|
||||||
{
|
{
|
||||||
Info = 0,
|
Info,
|
||||||
Warning = 1,
|
Warning,
|
||||||
Error = 2
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ClaimResponseType : byte
|
||||||
|
{
|
||||||
|
Accepted,
|
||||||
|
Rejected,
|
||||||
|
NotAcceptingSharesFromNonFriends,
|
||||||
|
AlreadyShared,
|
||||||
|
Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Enums
|
#endregion Enums
|
||||||
|
|
|
@ -150,11 +150,20 @@ public static partial class ModNetwork
|
||||||
private static void HandleBubbleClaimResponse(ModNetworkMessage msg)
|
private static void HandleBubbleClaimResponse(ModNetworkMessage msg)
|
||||||
{
|
{
|
||||||
msg.Read(out uint bubbleNetworkId);
|
msg.Read(out uint bubbleNetworkId);
|
||||||
msg.Read(out bool claimAccepted);
|
msg.Read(out byte responseTypeRaw);
|
||||||
|
|
||||||
ShareBubbleManager.Instance.OnRemoteBubbleClaimResponse(msg.Sender, bubbleNetworkId, claimAccepted);
|
if (!Enum.IsDefined(typeof(ClaimResponseType), responseTypeRaw))
|
||||||
|
{
|
||||||
|
LoggerInbound($"Invalid claim response type received: {responseTypeRaw}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LoggerInbound($"Bubble with ID {bubbleNetworkId} claim response: {claimAccepted}");
|
ClaimResponseType responseType = (ClaimResponseType)responseTypeRaw;
|
||||||
|
|
||||||
|
if (_pendingClaimRequests.TryGetValue(bubbleNetworkId, out PendingClaimRequest request))
|
||||||
|
request.CompletionSource.TrySetResult(responseType);
|
||||||
|
|
||||||
|
LoggerInbound($"Bubble with ID {bubbleNetworkId} claim response: {responseType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleActiveBubblesRequest(ModNetworkMessage msg)
|
private static void HandleActiveBubblesRequest(ModNetworkMessage msg)
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace NAK.ShareBubbles.Networking;
|
||||||
|
|
||||||
public static partial class ModNetwork
|
public static partial class ModNetwork
|
||||||
{
|
{
|
||||||
|
|
||||||
#region Mod Network Internals
|
#region Mod Network Internals
|
||||||
|
|
||||||
private static bool _isSubscribedToModNetwork;
|
private static bool _isSubscribedToModNetwork;
|
||||||
|
@ -33,4 +34,24 @@ public static partial class ModNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Mod Network Internals
|
#endregion Mod Network Internals
|
||||||
|
|
||||||
|
#region Pending Claim Requests
|
||||||
|
|
||||||
|
private static readonly Dictionary<uint, PendingClaimRequest> _pendingClaimRequests = new();
|
||||||
|
|
||||||
|
public class PendingClaimRequest
|
||||||
|
{
|
||||||
|
public TaskCompletionSource<ClaimResponseType> CompletionSource { get; }
|
||||||
|
public DateTime RequestTime { get; }
|
||||||
|
public uint BubbleId { get; }
|
||||||
|
|
||||||
|
public PendingClaimRequest(uint bubbleId)
|
||||||
|
{
|
||||||
|
CompletionSource = new TaskCompletionSource<ClaimResponseType>();
|
||||||
|
RequestTime = DateTime.UtcNow;
|
||||||
|
BubbleId = bubbleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
|
@ -51,20 +51,44 @@ public static partial class ModNetwork
|
||||||
LoggerOutbound($"Sending BubbleMove message for bubble {bubbleId}");
|
LoggerOutbound($"Sending BubbleMove message for bubble {bubbleId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendBubbleClaimRequest(string bubbleOwnerId, uint bubbleNetworkId)
|
public static async Task<ClaimResponseType> SendBubbleClaimRequestAsync(string bubbleOwnerId, uint bubbleNetworkId)
|
||||||
{
|
{
|
||||||
if (!CanSendModNetworkMessage())
|
if (!CanSendModNetworkMessage())
|
||||||
return;
|
return ClaimResponseType.Rejected;
|
||||||
|
|
||||||
using ModNetworkMessage modMsg = new(ModId, bubbleOwnerId);
|
// Create pending request
|
||||||
|
PendingClaimRequest request = new(bubbleNetworkId);
|
||||||
|
_pendingClaimRequests[bubbleNetworkId] = request;
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
using (ModNetworkMessage modMsg = new(ModId, bubbleOwnerId))
|
||||||
|
{
|
||||||
modMsg.Write((byte)MessageType.BubbleClaimRequest);
|
modMsg.Write((byte)MessageType.BubbleClaimRequest);
|
||||||
modMsg.Write(bubbleNetworkId);
|
modMsg.Write(bubbleNetworkId);
|
||||||
modMsg.Send();
|
modMsg.Send();
|
||||||
|
|
||||||
LoggerOutbound($"Sending BubbleClaimRequest message for bubble {bubbleNetworkId}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendBubbleClaimResponse(string requesterUserId, uint bubbleNetworkId, bool success)
|
LoggerOutbound($"Sending BubbleClaimRequest message for bubble {bubbleNetworkId}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Wait for response with timeout
|
||||||
|
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(ClaimRequestTimeout));
|
||||||
|
Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(ClaimRequestTimeout), cts.Token);
|
||||||
|
var responseTask = request.CompletionSource.Task;
|
||||||
|
|
||||||
|
Task completedTask = await Task.WhenAny(responseTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask) return ClaimResponseType.Timeout;
|
||||||
|
|
||||||
|
return await responseTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_pendingClaimRequests.Remove(bubbleNetworkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendBubbleClaimResponse(string requesterUserId, uint bubbleNetworkId, ClaimResponseType responseType)
|
||||||
{
|
{
|
||||||
if (!CanSendModNetworkMessage())
|
if (!CanSendModNetworkMessage())
|
||||||
return;
|
return;
|
||||||
|
@ -72,10 +96,10 @@ public static partial class ModNetwork
|
||||||
using ModNetworkMessage modMsg = new(ModId, requesterUserId);
|
using ModNetworkMessage modMsg = new(ModId, requesterUserId);
|
||||||
modMsg.Write((byte)MessageType.BubbleClaimResponse);
|
modMsg.Write((byte)MessageType.BubbleClaimResponse);
|
||||||
modMsg.Write(bubbleNetworkId);
|
modMsg.Write(bubbleNetworkId);
|
||||||
modMsg.Write(success);
|
modMsg.Write((byte)responseType);
|
||||||
modMsg.Send();
|
modMsg.Send();
|
||||||
|
|
||||||
LoggerOutbound($"Sending BubbleClaimResponse message for bubble {bubbleNetworkId}");
|
LoggerOutbound($"Sending BubbleClaimResponse message for bubble {bubbleNetworkId}: {responseType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendActiveBubblesRequest()
|
public static void SendActiveBubblesRequest()
|
||||||
|
|
|
@ -8,6 +8,8 @@ using NAK.ShareBubbles.Networking;
|
||||||
using NAK.ShareBubbles.UI;
|
using NAK.ShareBubbles.UI;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using ABI_RC.Core.InteractionSystem;
|
||||||
|
using ShareBubbles.ShareBubbles.Implementation;
|
||||||
|
|
||||||
namespace NAK.ShareBubbles;
|
namespace NAK.ShareBubbles;
|
||||||
|
|
||||||
|
@ -308,18 +310,62 @@ public class ShareBubble : MonoBehaviour
|
||||||
public void RequestContentClaim()
|
public void RequestContentClaim()
|
||||||
{
|
{
|
||||||
if (!CanRequestClaim()) return;
|
if (!CanRequestClaim()) return;
|
||||||
if (!RequestClaimTimeoutInactive()) return;
|
|
||||||
|
|
||||||
lastClaimRequest = DateTime.Now;
|
// Fire and forget but with error handling~
|
||||||
ModNetwork.SendBubbleClaimRequest(OwnerId, Data.BubbleId);
|
_ = RequestContentClaimAsync().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (!task.IsFaulted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ShareBubblesMod.Logger.Error($"Error requesting content claim: {task.Exception}");
|
||||||
|
CurrentClaimState = ClaimState.Rejected;
|
||||||
|
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ModNetwork.ClaimResponseType> RequestContentClaimAsync()
|
||||||
|
{
|
||||||
|
if (!CanRequestClaim())
|
||||||
|
return ModNetwork.ClaimResponseType.Rejected;
|
||||||
|
|
||||||
CurrentClaimState = ClaimState.Requested;
|
CurrentClaimState = ClaimState.Requested;
|
||||||
|
|
||||||
return;
|
try
|
||||||
bool RequestClaimTimeoutInactive()
|
|
||||||
{
|
{
|
||||||
if (!lastClaimRequest.HasValue) return true;
|
ModNetwork.ClaimResponseType response = await ModNetwork.SendBubbleClaimRequestAsync(OwnerId, Data.BubbleId);
|
||||||
TimeSpan timeSinceLastRequest = DateTime.Now - lastClaimRequest.Value;
|
|
||||||
return timeSinceLastRequest.TotalSeconds >= ClaimTimeout;
|
switch (response)
|
||||||
|
{
|
||||||
|
case ModNetwork.ClaimResponseType.Accepted:
|
||||||
|
case ModNetwork.ClaimResponseType.AlreadyShared:
|
||||||
|
CurrentClaimState = ClaimState.Permitted;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case ModNetwork.ClaimResponseType.Rejected:
|
||||||
|
case ModNetwork.ClaimResponseType.Timeout:
|
||||||
|
CurrentClaimState = ClaimState.Rejected;
|
||||||
|
break;
|
||||||
|
case ModNetwork.ClaimResponseType.NotAcceptingSharesFromNonFriends:
|
||||||
|
CurrentClaimState = ClaimState.Rejected;
|
||||||
|
|
||||||
|
string ownerName = CVRPlayerManager.Instance.TryGetPlayerName(OwnerId);
|
||||||
|
|
||||||
|
ShareBubblesMod.Logger.Msg($"Claim request for {Data.BubbleId} rejected: " +
|
||||||
|
$"You are not friends with the owner ({ownerName}) and do not have the permission " +
|
||||||
|
$"enabled on the Community Hub to accept shares from non-friends.");
|
||||||
|
// Display in UI
|
||||||
|
ViewManager.Instance.TriggerAlert("Claim Request Rejected",
|
||||||
|
$"You are not friends with the owner ({ownerName}) and do not have the permission " +
|
||||||
|
"enabled on the Community Hub to accept shares from non-friends.", -1, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
CurrentClaimState = ClaimState.Rejected;
|
||||||
|
return ModNetwork.ClaimResponseType.Rejected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,22 +387,19 @@ public class ShareBubble : MonoBehaviour
|
||||||
UpdateButtonStates();
|
UpdateButtonStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRemoteWantsClaim(string requesterId)
|
public async void OnRemoteWantsClaim(string requesterId)
|
||||||
{
|
{
|
||||||
if (!IsOwnBubble) return;
|
if (!IsOwnBubble) return;
|
||||||
|
|
||||||
// Check if requester is allowed to claim
|
// Rule check bypass attempt - reject immediately
|
||||||
bool isAllowed = Data.Rule == ShareRule.Everyone ||
|
if (Data.Rule == ShareRule.FriendsOnly && !Friends.FriendsWith(requesterId))
|
||||||
(Data.Rule == ShareRule.FriendsOnly && Friends.FriendsWith(requesterId));
|
|
||||||
|
|
||||||
if (!isAllowed)
|
|
||||||
{
|
{
|
||||||
ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, false);
|
ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, ModNetwork.ClaimResponseType.Rejected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation.HandleClaimAccept(requesterId,
|
ShareClaimResult result = await implementation.HandleClaimAccept(requesterId);
|
||||||
wasAccepted => ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, wasAccepted));
|
ModNetwork.SendBubbleClaimResponse(requesterId, Data.BubbleId, result.ResponseType);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Mod Network Callbacks
|
#endregion Mod Network Callbacks
|
||||||
|
|
|
@ -14,12 +14,12 @@ public class BubbleInteract : Interactable
|
||||||
return Vector3.Distance(transform.position, sourcePos) < 1.5f;
|
return Vector3.Distance(transform.position, sourcePos) < 1.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay)
|
public override void OnInteractDown(ControllerRay controllerRay)
|
||||||
{
|
{
|
||||||
// Not used
|
// Not used
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInteractUp(InteractionContext context, ControllerRay controllerRay)
|
public override void OnInteractUp(ControllerRay controllerRay)
|
||||||
{
|
{
|
||||||
if (PlayerSetup.Instance.GetCurrentPropSelectionMode()
|
if (PlayerSetup.Instance.GetCurrentPropSelectionMode()
|
||||||
!= PlayerSetup.PropSelectionMode.None)
|
!= PlayerSetup.PropSelectionMode.None)
|
||||||
|
@ -39,12 +39,12 @@ public class BubbleInteract : Interactable
|
||||||
GetComponentInParent<ShareBubble>().ViewDetailsPage();
|
GetComponentInParent<ShareBubble>().ViewDetailsPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnHoverEnter(InteractionContext context, ControllerRay controllerRay)
|
public override void OnHoverEnter()
|
||||||
{
|
{
|
||||||
// Not used
|
// Not used
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnHoverExit(InteractionContext context, ControllerRay controllerRay)
|
public override void OnHoverExit()
|
||||||
{
|
{
|
||||||
// Not used
|
// Not used
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,12 @@ public class ReturnOnRelease : MonoBehaviour
|
||||||
pickupable.onDrop.AddListener(OnPickupRelease);
|
pickupable.onDrop.AddListener(OnPickupRelease);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnPickupGrabbed(InteractionContext _)
|
public void OnPickupGrabbed()
|
||||||
{
|
{
|
||||||
isReturning = false;
|
isReturning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnPickupRelease(InteractionContext _)
|
public void OnPickupRelease()
|
||||||
{
|
{
|
||||||
isReturning = true;
|
isReturning = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue