mirror of
https://github.com/myawired/2025-RecRoom-Server-E12354.git
synced 2026-07-18 00:21:15 +10:00
Initial upload
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
using System.Xml.Linq;
|
||||
using LiteDB;
|
||||
using Newtonsoft.Json;
|
||||
using Rec_rewild_live_rewrite.api.server;
|
||||
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
|
||||
using static Rec_rewild_live_rewrite.api.server.Classes.db_class.ChatDBClasses;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class ChatDB
|
||||
{
|
||||
public static LiteDatabase ChatDBFile = new LiteDatabase(Environment.CurrentDirectory + "/Data/DBs/Chat.db");
|
||||
private static ILiteCollection<ChatThread> Threads => ChatDBFile.GetCollection<ChatThread>("threads");
|
||||
private static ILiteCollection<ChatMessage> Messages => ChatDBFile.GetCollection<ChatMessage>("messages");
|
||||
|
||||
static ChatDB()
|
||||
{
|
||||
Threads.EnsureIndex(x => x.ChatThreadId, unique: true);
|
||||
Threads.EnsureIndex(x => x.PlayerIds);
|
||||
Messages.EnsureIndex(x => x.ChatThreadId);
|
||||
Messages.EnsureIndex(x => x.ChatMessageId, unique: true);
|
||||
}
|
||||
|
||||
public static ChatThread CreateThread(List<ulong> memberIds, ulong creatorid, string? name = null)
|
||||
{
|
||||
long newThreadId = Threads.Count() + 1;
|
||||
|
||||
ChatThread thread = new ChatThread
|
||||
{
|
||||
ChatThreadId = newThreadId,
|
||||
PlayerIds = memberIds,
|
||||
ChatThreadName = name,
|
||||
IsFavorited = false,
|
||||
SnoozedUntil = null,
|
||||
LastReadMessageId = 1,
|
||||
Messages = new List<ChatMessage>()
|
||||
};
|
||||
|
||||
Threads.Insert(thread);
|
||||
|
||||
var messageJson = new MessageJson
|
||||
{
|
||||
Type = MessageContentType.Text,
|
||||
Version = 1,
|
||||
Data = $"Player <@U{creatorid}> started a chat"
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(messageJson);
|
||||
|
||||
AddMessage(
|
||||
threadId: newThreadId,
|
||||
senderId: -5,
|
||||
jsonContents: json
|
||||
);
|
||||
|
||||
// IMPORTANT FIX → Load thread again with messages included
|
||||
return GetThread(newThreadId);
|
||||
}
|
||||
|
||||
|
||||
public static ChatMessage AddMessage(long threadId, int senderId, string jsonContents)
|
||||
{
|
||||
// if senderId is -5 then it will do the chat welcome
|
||||
|
||||
var thread = GetThread(threadId);
|
||||
if (thread == null)
|
||||
return null;
|
||||
|
||||
long newMessageId = Messages.Count() + 1;
|
||||
|
||||
ChatMessage msg = new ChatMessage
|
||||
{
|
||||
ChatMessageId = newMessageId,
|
||||
ChatThreadId = threadId,
|
||||
SenderPlayerId = senderId,
|
||||
TimeSent = DateTime.UtcNow,
|
||||
Contents = jsonContents,
|
||||
ModerationState = 0
|
||||
};
|
||||
|
||||
Messages.Insert(msg);
|
||||
|
||||
thread.Messages.Add(msg);
|
||||
thread.LastReadMessageId = newMessageId;
|
||||
Threads.Update(thread);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ChatThread RenameThread(long threadId, ulong senderId, string NewThreadName)
|
||||
{
|
||||
var thread = GetThread(threadId);
|
||||
if (thread == null)
|
||||
return null;
|
||||
|
||||
thread.ChatThreadName = NewThreadName;
|
||||
|
||||
var messageJson = new MessageJson
|
||||
{
|
||||
Type = MessageContentType.Text,
|
||||
Version = 1,
|
||||
Data = $"Player <@U{senderId}> renamed the chat"
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(messageJson);
|
||||
|
||||
var newmsg = AddMessage(
|
||||
threadId: threadId,
|
||||
senderId: -5,
|
||||
jsonContents: json
|
||||
);
|
||||
|
||||
var payload = new
|
||||
{
|
||||
Id = "ChatMessageReceived",
|
||||
Msg = new
|
||||
{
|
||||
chatMessageId = newmsg.ChatMessageId,
|
||||
chatThreadId = newmsg.ChatThreadId,
|
||||
senderPlayerId = newmsg.SenderPlayerId,
|
||||
timeSent = newmsg.TimeSent,
|
||||
contents = newmsg.Contents,
|
||||
moderationState = newmsg.ModerationState
|
||||
}
|
||||
};
|
||||
|
||||
Notifications.SendToPlayers(thread.PlayerIds, JsonConvert.SerializeObject(payload));
|
||||
Threads.Update(thread);
|
||||
return GetThread(threadId);
|
||||
}
|
||||
|
||||
public static ChatThread LeaveThread(long threadId, ulong senderId)
|
||||
{
|
||||
var thread = GetThread(threadId);
|
||||
if (thread == null)
|
||||
return null;
|
||||
|
||||
thread.PlayerIds.Remove(senderId);
|
||||
var messageJson = new MessageJson
|
||||
{
|
||||
Type = MessageContentType.Text,
|
||||
Version = 1,
|
||||
Data = $"Player <@U{senderId}> left"
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(messageJson);
|
||||
|
||||
var newmsg = AddMessage(
|
||||
threadId: threadId,
|
||||
senderId: -5,
|
||||
jsonContents: json
|
||||
);
|
||||
|
||||
var payload = new
|
||||
{
|
||||
Id = "ChatMessageReceived",
|
||||
Msg = new
|
||||
{
|
||||
chatMessageId = newmsg.ChatMessageId,
|
||||
chatThreadId = newmsg.ChatThreadId,
|
||||
senderPlayerId = newmsg.SenderPlayerId,
|
||||
timeSent = newmsg.TimeSent,
|
||||
contents = newmsg.Contents,
|
||||
moderationState = newmsg.ModerationState
|
||||
}
|
||||
};
|
||||
|
||||
Notifications.SendToPlayers(thread.PlayerIds, JsonConvert.SerializeObject(payload));
|
||||
|
||||
Threads.Update(thread);
|
||||
return GetThread(threadId);
|
||||
}
|
||||
|
||||
public static ChatThread? GetThread(long threadId)
|
||||
{
|
||||
var thread = Threads.FindOne(x => x.ChatThreadId == threadId);
|
||||
if (thread == null) return null;
|
||||
|
||||
thread.Messages = Messages.Find(x => x.ChatThreadId == threadId).OrderBy(x => x.ChatMessageId).ToList();
|
||||
return thread;
|
||||
}
|
||||
|
||||
public static List<ChatThread> GetThreadsForPlayer(ulong playerId, int maxCount)
|
||||
{
|
||||
var list = Threads.Find(x => x.PlayerIds.Contains(playerId)).OrderByDescending(x => x.LastReadMessageId).Take(maxCount).ToList();
|
||||
|
||||
foreach (var t in list)
|
||||
{
|
||||
t.Messages = Messages.Find(x => x.ChatThreadId == t.ChatThreadId).OrderByDescending(x => x.ChatMessageId).Take(1).ToList(); // Load only the latest message
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class ClubDB
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Discord;
|
||||
using LiteDB;
|
||||
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
|
||||
using static Rec_rewild_live_rewrite.api.server.Classes.db_class.EventDBClasses;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class EventDB
|
||||
{
|
||||
public static LiteDatabase EventDBFile = new LiteDatabase(Environment.CurrentDirectory + "/Data/DBs/Events.db");
|
||||
private static ILiteCollection<FullPlayerEvent> Events => EventDBFile.GetCollection<FullPlayerEvent>("events");
|
||||
|
||||
private static ulong GetNextId()
|
||||
{
|
||||
var last = Events.Query()
|
||||
.OrderByDescending(x => x.PlayerEventId)
|
||||
.FirstOrDefault();
|
||||
|
||||
return last == null ? 1UL : last.PlayerEventId + 1;
|
||||
}
|
||||
|
||||
public static FullPlayerEvent? GetFullEventById(ulong eventId)
|
||||
{
|
||||
return Events.FindById(eventId);
|
||||
}
|
||||
|
||||
public static FullPlayerEvent? SetEventName(ulong eventId, string NewName)
|
||||
{
|
||||
var ev = Events.FindById(eventId);
|
||||
if (ev == null)
|
||||
return null;
|
||||
|
||||
ev.Name = NewName;
|
||||
Events.Update(ev);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
public static FullPlayerEvent? SetEventImageName(ulong eventId, string NewImageName)
|
||||
{
|
||||
var ev = Events.FindById(eventId);
|
||||
if (ev == null)
|
||||
return null;
|
||||
|
||||
ev.ImageName = NewImageName;
|
||||
Events.Update(ev);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
public static List<FullPlayerEvent> SearchEventByQuery(string? query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Events.Query()
|
||||
.OrderBy(e => e.StartTime)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
query = query.Trim();
|
||||
|
||||
bool isTagSearch =
|
||||
query.StartsWith("#") ||
|
||||
query.StartsWith("%23");
|
||||
|
||||
if (isTagSearch)
|
||||
{
|
||||
string tag = query
|
||||
.Replace("%23", "")
|
||||
.TrimStart('#')
|
||||
.ToLower();
|
||||
|
||||
return Events
|
||||
.Find(Query.EQ("Tags.Tag", tag))
|
||||
.OrderBy(e => e.StartTime)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
string nameQuery = query.ToLower();
|
||||
|
||||
return Events.Query()
|
||||
.Where(e =>
|
||||
e.Name != null &&
|
||||
e.Name.ToLower().Contains(nameQuery))
|
||||
.OrderBy(e => e.StartTime)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static FullPlayerEvent CreateEvent(ulong creatorPlayerId, EventDBClasses.CreateEventRequest request)
|
||||
{
|
||||
var ev = new FullPlayerEvent
|
||||
{
|
||||
PlayerEventId = GetNextId(),
|
||||
CreatorPlayerId = creatorPlayerId,
|
||||
RoomId = request.RoomId,
|
||||
SubRoomId = request.SubRoomId,
|
||||
ClubId = request.ClubId,
|
||||
Name = request.Name,
|
||||
Description = request.Description,
|
||||
ImageName = request.ImageName,
|
||||
Tags = request.Tags?.Select(t => new EventDBClasses.FullTag
|
||||
{
|
||||
Tag = t,
|
||||
Type = EventDBClasses.TagType.Unknown1
|
||||
}).ToList(),
|
||||
StartTime = request.StartTime,
|
||||
EndTime = request.EndTime,
|
||||
AttendeeCount = 0,
|
||||
State = 0,
|
||||
Accessibility = request.Accessibility,
|
||||
IsMultiInstance = request.IsMultiInstance,
|
||||
SupportMultiInstanceRoomChat = request.SupportMultiInstanceRoomChat,
|
||||
DefaultBroadcastPermissions = (EventDBClasses.BroadcastPermissionsEnum)request.DefaultBroadcastPermissions,
|
||||
CanRequestBroadcastPermissions = (EventDBClasses.BroadcastPermissionsEnum)request.CanRequestBroadcastPermissions,
|
||||
Responses = new List<EventDBClasses.FullEventResponses>()
|
||||
};
|
||||
|
||||
Events.Insert(ev);
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
using System.Net.WebSockets;
|
||||
using LiteDB;
|
||||
using Newtonsoft.Json;
|
||||
using Rec_rewild_live_rewrite.api.server;
|
||||
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
|
||||
using Rec_rewild_live_rewrite.Controllers;
|
||||
using static Rec_rewild_live_rewrite.api.WebsocketEvents;
|
||||
using static Rec_rewild_live_rewrite.Controllers.AccountsController;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class FriendsDB
|
||||
{
|
||||
public static LiteDatabase PlayerFriendDBFile = new LiteDatabase(Environment.CurrentDirectory + "/Data/DBs/PlayersFriends.db");
|
||||
|
||||
|
||||
// ==========================
|
||||
// GET ALL RELATIONSHIPS
|
||||
// ==========================
|
||||
public static List<RelationshipDetail> GetRelationships(ulong playerId)
|
||||
{
|
||||
var col = PlayerFriendDBFile.GetCollection<YourFriendData>("PlayersFriends");
|
||||
|
||||
List<RelationshipDetail> list = new();
|
||||
|
||||
foreach (var data in col.FindAll())
|
||||
{
|
||||
if (data.Relationship.OtherPlayerID != playerId)
|
||||
continue;
|
||||
|
||||
list.Add(new RelationshipDetail
|
||||
{
|
||||
Id = data.Relationship.Id,
|
||||
PlayerID = data.Relationship.PlayerID,
|
||||
OtherPlayerID = data.Relationship.OtherPlayerID,
|
||||
RelationshipType = data.Relationship.RelationshipType,
|
||||
Muted = data.Relationship.Muted,
|
||||
Ignored = data.Relationship.Ignored,
|
||||
Favorited = data.Relationship.Favorited,
|
||||
VoiceVolume = data.Relationship.VoiceVolume
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// SEND FRIEND REQUEST
|
||||
// ==========================
|
||||
public static RelationshipDetail SendFriendRequest(ulong fromPlayer, ulong toPlayer)
|
||||
{
|
||||
var col = PlayerFriendDBFile.GetCollection<YourFriendData>("PlayersFriends");
|
||||
|
||||
// ---- A -> B ----
|
||||
var relA = col.FindOne(x =>
|
||||
x.PlayerID == fromPlayer &&
|
||||
x.Relationship.OtherPlayerID == toPlayer
|
||||
);
|
||||
|
||||
if (relA == null)
|
||||
{
|
||||
relA = new YourFriendData
|
||||
{
|
||||
Id = ObjectId.NewObjectId(),
|
||||
PlayerID = fromPlayer,
|
||||
Relationship = new RelationshipDetail
|
||||
{
|
||||
Id = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
PlayerID = fromPlayer,
|
||||
OtherPlayerID = toPlayer,
|
||||
RelationshipType = RelationshipType.None,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
VoiceVolume = 100
|
||||
}
|
||||
};
|
||||
|
||||
col.Insert(relA);
|
||||
}
|
||||
|
||||
// ---- B -> A ----
|
||||
var relB = col.FindOne(x =>
|
||||
x.PlayerID == toPlayer &&
|
||||
x.Relationship.OtherPlayerID == fromPlayer
|
||||
);
|
||||
|
||||
if (relB == null)
|
||||
{
|
||||
relB = new YourFriendData
|
||||
{
|
||||
Id = ObjectId.NewObjectId(),
|
||||
PlayerID = toPlayer,
|
||||
Relationship = new RelationshipDetail
|
||||
{
|
||||
Id = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
PlayerID = toPlayer,
|
||||
OtherPlayerID = fromPlayer,
|
||||
RelationshipType = RelationshipType.None,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
VoiceVolume = 100
|
||||
}
|
||||
};
|
||||
|
||||
col.Insert(relB);
|
||||
}
|
||||
|
||||
|
||||
// Update types
|
||||
relA.Relationship.RelationshipType = RelationshipType.FriendRequestSent;
|
||||
relB.Relationship.RelationshipType = RelationshipType.FriendRequestReceived;
|
||||
|
||||
col.Update(relA);
|
||||
col.Update(relB);
|
||||
|
||||
//todo: send ws event to anounce the player that it got a new friend request
|
||||
|
||||
// Websocket pushes
|
||||
Notifications.SendToPlayer(
|
||||
fromPlayer,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendRequestSent(toPlayer))
|
||||
);
|
||||
|
||||
Notifications.SendToPlayer(
|
||||
toPlayer,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendRequestReceived(fromPlayer))
|
||||
);
|
||||
|
||||
Notifications.SendToPlayer(
|
||||
fromPlayer,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendRequestMsg(toPlayer))
|
||||
);
|
||||
return relA.Relationship;
|
||||
}
|
||||
|
||||
public static RelationshipDetail? AcceptFriendRequest(ulong fromPlayer, ulong toPlayer)
|
||||
{
|
||||
var col = PlayerFriendDBFile.GetCollection<YourFriendData>("PlayersFriends");
|
||||
|
||||
// Request MUST exist in both directions (pending)
|
||||
var relA = col.FindOne(x => x.PlayerID == fromPlayer && x.Relationship.OtherPlayerID == toPlayer);
|
||||
var relB = col.FindOne(x => x.PlayerID == toPlayer && x.Relationship.OtherPlayerID == fromPlayer);
|
||||
|
||||
// Request cannot be accepted if it doesn't exist
|
||||
if (relA == null || relA.Relationship == null ||
|
||||
relB == null || relB.Relationship == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set both sides to Friend
|
||||
relA.Relationship.RelationshipType = RelationshipType.Friend;
|
||||
relB.Relationship.RelationshipType = RelationshipType.Friend;
|
||||
|
||||
col.Update(relA);
|
||||
col.Update(relB);
|
||||
|
||||
// Send WebSocket events
|
||||
Notifications.SendToPlayer(fromPlayer,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendAccepted(toPlayer, fromPlayer)));
|
||||
|
||||
Notifications.SendToPlayer(toPlayer,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendAccepted(fromPlayer, toPlayer)));
|
||||
|
||||
Notifications.SendToPlayer(fromPlayer,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendAcceptedMsg(toPlayer)));
|
||||
|
||||
// return the accepted version
|
||||
return relA.Relationship;
|
||||
}
|
||||
|
||||
|
||||
private static WebsocketEvents.Reponse CreateResponseFriendAcceptedMsg(ulong fromPlayer) => new WebsocketEvents.Reponse
|
||||
{
|
||||
Id = "2",
|
||||
Msg = new
|
||||
{
|
||||
Id = (ulong)new Random().Next(),
|
||||
FromPlayerId = fromPlayer,
|
||||
SentTime = DateTime.UtcNow,
|
||||
Type = WebsocketEvents.MessageType.FriendRequestAccepted,
|
||||
Data = "",
|
||||
RoomId = (ulong?)null,
|
||||
PlayerEventId = (ulong?)null,
|
||||
}
|
||||
};
|
||||
|
||||
public static void DeleteFriends(ulong playerId)
|
||||
{
|
||||
if (playerId == 0) return;
|
||||
var col = PlayerFriendDBFile.GetCollection<YourFriendData>("PlayersFriends");
|
||||
col.DeleteMany(x => x.PlayerID == playerId);
|
||||
}
|
||||
|
||||
public static Reponse CreateResponseFriendAccepted(ulong playerid, ulong fromplayerid)
|
||||
{
|
||||
return new Reponse
|
||||
{
|
||||
Id = "1",
|
||||
Msg = new RelationshipDetail()
|
||||
{
|
||||
Id = (ulong)new Random().Next(),
|
||||
PlayerID = playerid,
|
||||
OtherPlayerID = fromplayerid,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
RelationshipType = RelationshipType.Friend,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Reponse CreateResponseFriendRemoved(ulong playerid, ulong fromplayerid)
|
||||
{
|
||||
return new Reponse
|
||||
{
|
||||
Id = "1",
|
||||
Msg = new RelationshipDetail()
|
||||
{
|
||||
Id = (ulong)new Random().Next(),
|
||||
PlayerID = playerid,
|
||||
OtherPlayerID = fromplayerid,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
RelationshipType = RelationshipType.None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static WebsocketEvents.Reponse CreateResponseFriendRequestReceived(ulong fromPlayer) => new WebsocketEvents.Reponse
|
||||
{
|
||||
Id = "1",
|
||||
Msg = new RelationshipDetail
|
||||
{
|
||||
Id = (ulong)new Random().Next(),
|
||||
PlayerID = fromPlayer,
|
||||
OtherPlayerID = fromPlayer,
|
||||
RelationshipType = RelationshipType.FriendRequestReceived,
|
||||
Favorited = 0,
|
||||
Muted = 0,
|
||||
Ignored = 0,
|
||||
VoiceVolume = 100
|
||||
}
|
||||
};
|
||||
|
||||
private static WebsocketEvents.Reponse CreateResponseFriendRequestMsg(ulong fromPlayer) => new WebsocketEvents.Reponse
|
||||
{
|
||||
Id = "2",
|
||||
Msg = new
|
||||
{
|
||||
Id = (ulong)new Random().Next(),
|
||||
FromPlayerId = fromPlayer,
|
||||
SentTime = DateTime.UtcNow,
|
||||
Type = WebsocketEvents.MessageType.FriendInvite,
|
||||
Data = "",
|
||||
RoomId = (ulong?)null,
|
||||
PlayerEventId = (ulong?)null,
|
||||
}
|
||||
};
|
||||
|
||||
public static RelationshipDetail RemoveFriend(ulong playerA, ulong playerB)
|
||||
{
|
||||
var col = PlayerFriendDBFile.GetCollection<YourFriendData>("PlayersFriends");
|
||||
|
||||
// A -> B
|
||||
var relA = col.FindOne(x => x.PlayerID == playerA &&
|
||||
x.Relationship.OtherPlayerID == playerB);
|
||||
|
||||
// B -> A
|
||||
var relB = col.FindOne(x => x.PlayerID == playerB &&
|
||||
x.Relationship.OtherPlayerID == playerA);
|
||||
|
||||
// If they were never connected, return empty relationship
|
||||
if (relA == null && relB == null)
|
||||
{
|
||||
return new RelationshipDetail
|
||||
{
|
||||
PlayerID = playerA,
|
||||
OtherPlayerID = playerB,
|
||||
RelationshipType = RelationshipType.None
|
||||
};
|
||||
}
|
||||
|
||||
// Delete A->B
|
||||
if (relA != null)
|
||||
col.Delete(relA.Id);
|
||||
|
||||
// Delete B->A
|
||||
if (relB != null)
|
||||
col.Delete(relB.Id);
|
||||
|
||||
// Send WS update to player A
|
||||
Notifications.SendToPlayer(
|
||||
playerA,
|
||||
JsonConvert.SerializeObject(
|
||||
CreateResponseFriendRemoved(playerA, playerB)
|
||||
)
|
||||
);
|
||||
|
||||
// Send WS update to player B
|
||||
Notifications.SendToPlayer(
|
||||
playerB,
|
||||
JsonConvert.SerializeObject(
|
||||
CreateResponseFriendRemoved(playerB, playerA)
|
||||
)
|
||||
);
|
||||
|
||||
// Return final relationship state
|
||||
return new RelationshipDetail
|
||||
{
|
||||
PlayerID = playerA,
|
||||
OtherPlayerID = playerB,
|
||||
RelationshipType = RelationshipType.None,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
VoiceVolume = 100
|
||||
};
|
||||
}
|
||||
|
||||
public static RelationshipDetail AddFriend(ulong playerA, ulong playerB)
|
||||
{
|
||||
var col = PlayerFriendDBFile.GetCollection<YourFriendData>("PlayersFriends");
|
||||
|
||||
// A -> B
|
||||
var relA = col.FindOne(x => x.PlayerID == playerA &&
|
||||
x.Relationship.OtherPlayerID == playerB);
|
||||
|
||||
if (relA == null)
|
||||
{
|
||||
relA = new YourFriendData
|
||||
{
|
||||
Id = ObjectId.NewObjectId(),
|
||||
PlayerID = playerA,
|
||||
Relationship = new RelationshipDetail
|
||||
{
|
||||
Id = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
PlayerID = playerA,
|
||||
OtherPlayerID = playerB,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
VoiceVolume = 100
|
||||
}
|
||||
};
|
||||
col.Insert(relA);
|
||||
}
|
||||
|
||||
// B -> A
|
||||
var relB = col.FindOne(x => x.PlayerID == playerB &&
|
||||
x.Relationship.OtherPlayerID == playerA);
|
||||
|
||||
if (relB == null)
|
||||
{
|
||||
relB = new YourFriendData
|
||||
{
|
||||
Id = ObjectId.NewObjectId(),
|
||||
PlayerID = playerB,
|
||||
Relationship = new RelationshipDetail
|
||||
{
|
||||
Id = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
PlayerID = playerB,
|
||||
OtherPlayerID = playerA,
|
||||
Favorited = ReciprocalStatus.None,
|
||||
Ignored = ReciprocalStatus.None,
|
||||
Muted = ReciprocalStatus.None,
|
||||
VoiceVolume = 100
|
||||
}
|
||||
};
|
||||
col.Insert(relB);
|
||||
}
|
||||
|
||||
// Set both sides to Friend
|
||||
relA.Relationship.RelationshipType = RelationshipType.Friend;
|
||||
relB.Relationship.RelationshipType = RelationshipType.Friend;
|
||||
|
||||
col.Update(relA);
|
||||
col.Update(relB);
|
||||
|
||||
// WebSocket updates
|
||||
Notifications.SendToPlayer(
|
||||
playerA,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendAccepted(playerA, playerB))
|
||||
);
|
||||
|
||||
Notifications.SendToPlayer(
|
||||
playerB,
|
||||
JsonConvert.SerializeObject(CreateResponseFriendAccepted(playerB, playerA))
|
||||
);
|
||||
|
||||
return relA.Relationship;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// {"type":1,"target":"Notification","arguments":["{\"id\":\"2\",\"msg\":{\"id\":16212007930,\"fromPlayerId\":1764227894,\"sentTime\":\"2025-06-23T17:20:53.72092Z\",\"type\":4,\"data\":\"\",\"roomId\":null,\"playerEventId\":null}}"]}
|
||||
|
||||
private static WebsocketEvents.Reponse CreateResponseFriendRequestSent(ulong toPlayer) => new WebsocketEvents.Reponse
|
||||
{
|
||||
Id = "50",
|
||||
Msg = ""
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using LiteDB;
|
||||
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class HeartbeatDB
|
||||
{
|
||||
private static readonly LiteDatabase _db = new($"{Environment.CurrentDirectory}/Data/DBs/Heartbeats.db");
|
||||
private static readonly ILiteCollection<PlayerHeartbeatData> _col =
|
||||
_db.GetCollection<PlayerHeartbeatData>("players_heartbeat");
|
||||
|
||||
public static void Setup()
|
||||
{
|
||||
_col.EnsureIndex(x => x.playerid, unique: true);
|
||||
}
|
||||
|
||||
public class PlayerHeartbeatData
|
||||
{
|
||||
[BsonId]
|
||||
public ulong iD { get; set; }
|
||||
public ulong playerid { get; set; }
|
||||
public Heartbeat Heartbeat { get; set; }
|
||||
}
|
||||
|
||||
private static Heartbeat CreateDefaultHeartbeat(ulong playerid, Platforms platform, DeviceClasses deviceClasses) =>
|
||||
new()
|
||||
{
|
||||
playerId = (long)playerid,
|
||||
appVersion = "20250731.01",
|
||||
deviceClass = -1,
|
||||
isOnline = false,
|
||||
platform = 0,
|
||||
roomInstance = null,
|
||||
statusVisibility = 4,
|
||||
vrMovementMode = 0,
|
||||
};
|
||||
|
||||
public static Heartbeat GetPlayerHeartbeat(ulong playerid)
|
||||
{
|
||||
var data = _col.FindOne(x => x.playerid == playerid);
|
||||
return data?.Heartbeat ?? CreateDefaultHeartbeat(playerid, Platforms.Steam, DeviceClasses.Screen);
|
||||
}
|
||||
|
||||
public static string GetPlayerHeartbeatRoomName(ulong playerid)
|
||||
{
|
||||
var data = _col.FindOne(x => x.playerid == playerid);
|
||||
return data?.Heartbeat?.roomInstance?.name ?? "nowhere!";
|
||||
}
|
||||
|
||||
public static bool CreatePlayerHeartbeat(ulong playerid, out Heartbeat heartbeat, Platforms platform, DeviceClasses deviceClasses)
|
||||
{
|
||||
if (_col.Exists(x => x.playerid == playerid))
|
||||
{
|
||||
heartbeat = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
heartbeat = CreateDefaultHeartbeat(playerid, platform, deviceClasses);
|
||||
_col.Insert(new PlayerHeartbeatData
|
||||
{
|
||||
iD = (ulong)_col.LongCount() + 1,
|
||||
playerid = playerid,
|
||||
Heartbeat = heartbeat
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void UpdateStatusVisibility(ulong playerid, int visibility)
|
||||
{
|
||||
var data = _col.FindOne(x => x.playerid == playerid);
|
||||
if (data != null)
|
||||
{
|
||||
data.Heartbeat.statusVisibility = visibility;
|
||||
_col.Update(data);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPlayerCurrentRoomName(ulong playerid)
|
||||
{
|
||||
var data = _col.FindOne(x => x.playerid == playerid);
|
||||
return data?.Heartbeat?.roomInstance?.name ?? "not in a room.";
|
||||
}
|
||||
|
||||
public static void ClearPlayersHeartbeats()
|
||||
{
|
||||
foreach (var data in _col.FindAll())
|
||||
{
|
||||
UpdatePlayerHeartbeat(data.playerid, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearPlayerHeartbeat(ulong playerId) =>
|
||||
UpdatePlayerHeartbeat(playerId, null, false);
|
||||
|
||||
public static void UpdatePlayerHeartbeat(
|
||||
ulong playerid,
|
||||
RoomInstance? roomInstance,
|
||||
bool online = true,
|
||||
Platforms platform = Platforms.All,
|
||||
DeviceClasses deviceClasses = DeviceClasses.Unknown)
|
||||
{
|
||||
var data = _col.FindOne(x => x.playerid == playerid);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var hb = data.Heartbeat;
|
||||
hb.roomInstance = online ? roomInstance : null;
|
||||
hb.isOnline = online;
|
||||
|
||||
_col.Update(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new heartbeat and assign the current roomInstance
|
||||
var hb = CreateDefaultHeartbeat(playerid, platform, deviceClasses);
|
||||
hb.roomInstance = roomInstance;
|
||||
hb.isOnline = online;
|
||||
|
||||
_col.Insert(new PlayerHeartbeatData
|
||||
{
|
||||
iD = (ulong)_col.LongCount() + 1,
|
||||
playerid = playerid,
|
||||
Heartbeat = hb
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class Heartbeat
|
||||
{
|
||||
public long serverTime { get; set; }
|
||||
public long playerId { get; set; }
|
||||
public int statusVisibility { get; set; }
|
||||
public int platform { get; set; }
|
||||
public int deviceClass { get; set; }
|
||||
public int vrMovementMode { get; set; }
|
||||
public RoomInstance roomInstance { get; set; } // Can be null
|
||||
public string lastOnline { get; set; } // ISO 8601 string
|
||||
public bool isOnline { get; set; }
|
||||
public string appVersion { get; set; }
|
||||
}
|
||||
|
||||
public class FullRoomInstance
|
||||
{
|
||||
public RoomInstance? roomInstance { get; set; }
|
||||
public string? correlationId { get; set; }
|
||||
}
|
||||
|
||||
public class RoomInstance
|
||||
{
|
||||
public long roomInstanceId { get; set; }
|
||||
public long roomId { get; set; }
|
||||
public long subRoomId { get; set; }
|
||||
public string? location { get; set; }
|
||||
public int roomInstanceType { get; set; }
|
||||
public string? name { get; set; }
|
||||
public int maxCapacity { get; set; }
|
||||
public bool isFull { get; set; }
|
||||
public bool isPrivate { get; set; }
|
||||
public bool isInProgress { get; set; }
|
||||
[JsonIgnore]
|
||||
public string? photonRoomIdYeee { get; set; } // im crack
|
||||
public string? voiceServerId { get; set; }
|
||||
public string? voiceAuthId { get; set; }
|
||||
public int matchmakingPolicy { get; set; }
|
||||
}
|
||||
|
||||
public enum MatchmakingErrorCode
|
||||
{
|
||||
Success,
|
||||
NoSuchGame,
|
||||
PlayerNotOnline,
|
||||
InsufficientSpace,
|
||||
EventNotStarted,
|
||||
EventAlreadyFinished,
|
||||
EventCreatorNotReady,
|
||||
BlockedFromRoom,
|
||||
ProfileLocked,
|
||||
NoBirthday,
|
||||
MarkedForDelete,
|
||||
JuniorNotAllowed,
|
||||
Banned,
|
||||
AlreadyInBestInstance,
|
||||
InsufficientRelationship,
|
||||
UpdateRequired = 16,
|
||||
AlreadyInTargetInstance,
|
||||
RegistrationRequired,
|
||||
UGCNotAllowed,
|
||||
NoSuchRoom,
|
||||
RoomCreatorNotReady,
|
||||
RoomIsNotActive,
|
||||
RoomBlockedByCreator,
|
||||
RoomBlockingCreator,
|
||||
RoomIsPrivate,
|
||||
RoomInstanceIsPrivate,
|
||||
DeviceClassNotSupported = 30,
|
||||
DeviceClassNotSupportedByRoomOwner,
|
||||
VRMovementModeNotSupportedByRoomOwner,
|
||||
EventIsPrivate = 35,
|
||||
RoomInviteExpired = 40,
|
||||
NoAvailableRegion = 45,
|
||||
NotorietyTooPoor = 50,
|
||||
BannedFromRoom = 55
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
using LiteDB;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public static class ImageMetadataDB
|
||||
{
|
||||
private static readonly string DbPath = Path.Combine("Data", "DBs", "Images.db");
|
||||
|
||||
public class FullSavedImage
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public ImageAccessibility Accessibility { get; set; }
|
||||
public bool AccessibilityLocked { get; set; } = false;
|
||||
public int CheerCount { get; set; } = 0;
|
||||
public int CommentCount { get; set; } = 0;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public string? Description { get; set; }
|
||||
public string ImageName { get; set; }
|
||||
public ulong PlayerEventId { get; set; }
|
||||
public ulong PlayerId { get; set; }
|
||||
public int RoomId { get; set; }
|
||||
//public string Description { get; set; } = "";
|
||||
public SavedImageTypeEnum Type { get; set; }
|
||||
public List<ulong> TaggedPlayerIds { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ImageV6
|
||||
{
|
||||
public ImageAccessibility Accessibility { get; set; }
|
||||
public bool AccessibilityLocked { get; set; } = false;
|
||||
public int CheerCount { get; set; } = 0;
|
||||
public int CommentCount { get; set; } = 0;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public string? Description { get; set; }
|
||||
public int Id { get; set; }
|
||||
public string ImageName { get; set; }
|
||||
public ulong PlayerEventId { get; set; }
|
||||
public ulong PlayerId { get; set; }
|
||||
public int RoomId { get; set; }
|
||||
public List<ulong> TaggedPlayerIds { get; set; } = new();
|
||||
public SavedImageTypeEnum Type { get; set; }
|
||||
}
|
||||
|
||||
public class ImagesPlayer
|
||||
{
|
||||
public ImageAccessibility Accessibility { get; set; }
|
||||
public bool AccessibilityLocked { get; set; } = false;
|
||||
public int CheerCount { get; set; } = 0;
|
||||
public int CommentCount { get; set; } = 0;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public string? Description { get; set; }
|
||||
public string ImageName { get; set; }
|
||||
public ulong PlayerEventId { get; set; }
|
||||
public ulong PlayerId { get; set; }
|
||||
public int RoomId { get; set; }
|
||||
public int SavedImageId { get; set; }
|
||||
public SavedImageTypeEnum SavedImageType { get; set; }
|
||||
}
|
||||
|
||||
public class ImageMetadata
|
||||
{
|
||||
public List<ulong> playerIds { get; set; } = new();
|
||||
public SavedImageTypeEnum savedImageType { get; set; }
|
||||
public int roomId { get; set; }
|
||||
public ImageAccessibility accessibility { get; set; }
|
||||
public string? description { get; set; }
|
||||
}
|
||||
|
||||
public enum SavedImageTypeEnum
|
||||
{
|
||||
None, // 0
|
||||
ShareCamera, // 1
|
||||
OutfitThumbnail, // 2
|
||||
RoomThumbnail, // 3
|
||||
ProfileThumbnail, // 4
|
||||
InventionThumbnail, // 5
|
||||
PlayerEventThumbnail, // 6
|
||||
RoomLoadScreen // 7
|
||||
}
|
||||
|
||||
public enum ImageAccessibility
|
||||
{
|
||||
Private = 0,
|
||||
Public = 1,
|
||||
}
|
||||
|
||||
public class Cheer
|
||||
{
|
||||
public ObjectId Id { get; set; }
|
||||
public int SavedImageId { get; set; }
|
||||
public ulong PlayerId { get; set; }
|
||||
}
|
||||
|
||||
public class CheerRequest
|
||||
{
|
||||
public int SavedImageId { get; set; }
|
||||
public bool Cheer { get; set; }
|
||||
}
|
||||
|
||||
// PERF: Keep a thread-safe singleton LiteDatabase instance to avoid reopening files constantly.
|
||||
private static readonly Lazy<LiteDatabase> _dbInstance = new(() => new LiteDatabase(DbPath));
|
||||
public static LiteDatabase GetDb() => _dbInstance.Value;
|
||||
|
||||
public static FullSavedImage InsertImage(FullSavedImage image)
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
col.EnsureIndex(x => x.Id, true);
|
||||
|
||||
// If the image doesn't already have an ID, assign the next sequential ID.
|
||||
if (image.Id == 0)
|
||||
{
|
||||
// Get the highest existing ID and increment by 1.
|
||||
// PERF: This uses descending order to get the latest ID efficiently.
|
||||
var last = col.Query()
|
||||
.OrderByDescending(x => x.Id)
|
||||
.Limit(1)
|
||||
.FirstOrDefault();
|
||||
|
||||
image.Id = (last?.Id ?? 0) + 1;
|
||||
}
|
||||
|
||||
col.Insert(image);
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
public static List<FullSavedImage> GetRoomImages(
|
||||
ulong playerId,
|
||||
int roomId,
|
||||
int sort = 0,
|
||||
int filter = 0,
|
||||
int take = 50,
|
||||
int skip = 0)
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
|
||||
// Base query: only images in this room
|
||||
var query = col.Query()
|
||||
.Where(x => x.RoomId == roomId);
|
||||
|
||||
// Apply filter conditions
|
||||
switch (filter)
|
||||
{
|
||||
case 2: // Only show player's private images
|
||||
query = query.Where(x => x.PlayerId == playerId);
|
||||
break;
|
||||
|
||||
case 3: // Only images that have been cheered
|
||||
query = query.Where(x => x.CheerCount > 0);
|
||||
break;
|
||||
|
||||
// filter == 1 will be handled in sorting
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
if (sort == 0 && filter == 1)
|
||||
{
|
||||
// Special case: newest first when sort=0 & filter=1
|
||||
query = query.OrderByDescending(x => x.CreatedAt);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = sort switch
|
||||
{
|
||||
1 => query.OrderByDescending(x => x.CreatedAt), // Newest first
|
||||
2 => query.OrderByDescending(x => x.CheerCount), // Most cheered
|
||||
3 => query.OrderByDescending(x => x.CommentCount),// Most commented
|
||||
_ => query.OrderBy(x => x.CreatedAt), // Default: oldest first
|
||||
};
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
return query
|
||||
.Skip(skip)
|
||||
.Limit(take)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public static List<FullSavedImage> GetGlobalImages()
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
|
||||
return col.Query()
|
||||
.Where(x => x.Accessibility == ImageAccessibility.Public)
|
||||
.OrderByDescending(x => x.CheerCount)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public static List<ImagesPlayer> GetPlayerImages(ulong playeridthatrequesting, ulong playerid)
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
|
||||
var query = col.Query().Where(x => x.PlayerId == playerid);
|
||||
|
||||
if (playeridthatrequesting != playerid)
|
||||
{
|
||||
query = query.Where(x => x.Accessibility == ImageAccessibility.Public);
|
||||
}
|
||||
|
||||
return query.ToEnumerable()
|
||||
.Select(MapToImagesPlayer)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static ImagesPlayer MapToImagesPlayer(FullSavedImage x)
|
||||
{
|
||||
return new ImagesPlayer
|
||||
{
|
||||
Accessibility = x.Accessibility,
|
||||
AccessibilityLocked = x.AccessibilityLocked,
|
||||
CheerCount = x.CheerCount,
|
||||
CommentCount = x.CommentCount,
|
||||
CreatedAt = x.CreatedAt,
|
||||
Description = x.Description,
|
||||
ImageName = x.ImageName,
|
||||
PlayerEventId = x.PlayerEventId,
|
||||
PlayerId = x.PlayerId,
|
||||
RoomId = x.RoomId,
|
||||
SavedImageId = x.Id,
|
||||
SavedImageType = x.Type
|
||||
};
|
||||
}
|
||||
|
||||
public static void UpdateImageCheerCount(int savedImageId, int cheerCount)
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
var img = col.FindById(savedImageId);
|
||||
if (img != null)
|
||||
{
|
||||
img.CheerCount = cheerCount;
|
||||
col.Update(img);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ToggleCheer(int savedImageId, ulong playerId, bool cheer)
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<Cheer>("cheers");
|
||||
|
||||
var existing = col.FindOne(x => x.SavedImageId == savedImageId && x.PlayerId == playerId);
|
||||
|
||||
if (cheer && existing == null)
|
||||
{
|
||||
col.Insert(new Cheer { SavedImageId = savedImageId, PlayerId = playerId });
|
||||
}
|
||||
else if (!cheer && existing != null)
|
||||
{
|
||||
col.Delete(existing.Id);
|
||||
}
|
||||
|
||||
return col.Exists(x => x.SavedImageId == savedImageId && x.PlayerId == playerId);
|
||||
}
|
||||
|
||||
public static int GetCheerCount(int savedImageId)
|
||||
{
|
||||
var db = GetDb();
|
||||
return db.GetCollection<Cheer>("cheers").Count(x => x.SavedImageId == savedImageId);
|
||||
}
|
||||
|
||||
public static bool HasUserCheered(int savedImageId, ulong playerId)
|
||||
{
|
||||
var db = GetDb();
|
||||
return db.GetCollection<Cheer>("cheers")
|
||||
.Exists(x => x.SavedImageId == savedImageId && x.PlayerId == playerId);
|
||||
}
|
||||
|
||||
private static readonly string OldDir = Path.Combine("Data", "images_metadata");
|
||||
|
||||
public static ImageV6 GetImageByName(string name)
|
||||
{
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
var record = col.FindOne(x => x.ImageName == name);
|
||||
|
||||
if (record == null) return null;
|
||||
|
||||
return new ImageV6
|
||||
{
|
||||
Accessibility = record.Accessibility,
|
||||
AccessibilityLocked = record.AccessibilityLocked,
|
||||
CheerCount = record.CheerCount,
|
||||
CommentCount = record.CommentCount,
|
||||
CreatedAt = record.CreatedAt,
|
||||
Description = record.Description,
|
||||
Id = record.Id,
|
||||
ImageName = record.ImageName,
|
||||
PlayerEventId = record.PlayerEventId,
|
||||
PlayerId = record.PlayerId,
|
||||
RoomId = record.RoomId,
|
||||
TaggedPlayerIds = record.TaggedPlayerIds,
|
||||
Type = record.Type,
|
||||
};
|
||||
}
|
||||
|
||||
public static void DeleteImagesFromPlayerId(ulong playerId)
|
||||
{
|
||||
var db = GetDb();
|
||||
var imageCol = db.GetCollection<FullSavedImage>("images");
|
||||
var cheerCol = db.GetCollection<Cheer>("cheers");
|
||||
|
||||
var imagesToDelete = imageCol.Query().Where(x => x.PlayerId == playerId).ToList();
|
||||
|
||||
if (imagesToDelete.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var img in imagesToDelete)
|
||||
{
|
||||
imageCol.Delete(img.Id);
|
||||
cheerCol.DeleteMany(c => c.SavedImageId == img.Id);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[Delete] Removed {imagesToDelete.Count} images and their cheers for PlayerId {playerId}");
|
||||
}
|
||||
|
||||
public static List<FullSavedImage> GetImagesByIds(List<int> ids)
|
||||
{
|
||||
if (ids == null || ids.Count == 0)
|
||||
return new List<FullSavedImage>();
|
||||
|
||||
var db = GetDb();
|
||||
var col = db.GetCollection<FullSavedImage>("images");
|
||||
|
||||
// Efficient LiteDB WHERE query using Contains()
|
||||
return col.Query()
|
||||
.Where(x => ids.Contains(x.Id))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public static void ImportOldJson()
|
||||
{
|
||||
if (!Directory.Exists(OldDir))
|
||||
{
|
||||
Console.WriteLine($"[Import] Old directory not found: {OldDir}");
|
||||
return;
|
||||
}
|
||||
|
||||
var db = GetDb();
|
||||
var imageCol = db.GetCollection<FullSavedImage>("images");
|
||||
var cheerCol = db.GetCollection<Cheer>("cheers");
|
||||
imageCol.EnsureIndex(x => x.Id, true);
|
||||
|
||||
// PERF: Parallelize JSON import for multiple files.
|
||||
var files = Directory.GetFiles(OldDir, "*.json");
|
||||
var cheerFiles = files.Where(f => f.EndsWith("_cheers.json")).ToArray();
|
||||
var imageFiles = files.Except(cheerFiles).ToArray();
|
||||
|
||||
Parallel.ForEach(imageFiles, file =>
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(file);
|
||||
var images = JsonConvert.DeserializeObject<List<FullSavedImage>>(json);
|
||||
if (images == null) return;
|
||||
|
||||
foreach (var img in images)
|
||||
{
|
||||
if (!imageCol.Exists(x => x.Id == img.Id))
|
||||
{
|
||||
imageCol.Insert(img);
|
||||
Console.WriteLine($"[Import] Imported image {img.Id} from {fileName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[Import] Skipped duplicate image {img.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Import] Failed to import {fileName}: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
Parallel.ForEach(cheerFiles, file =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
int savedImageId = int.Parse(fileName.Replace("_cheers.json", ""));
|
||||
|
||||
string json = File.ReadAllText(file);
|
||||
var cheerList = JsonConvert.DeserializeObject<List<ulong>>(json);
|
||||
if (cheerList == null) return;
|
||||
|
||||
foreach (var pid in cheerList)
|
||||
{
|
||||
if (!cheerCol.Exists(x => x.SavedImageId == savedImageId && x.PlayerId == pid))
|
||||
{
|
||||
cheerCol.Insert(new Cheer { SavedImageId = savedImageId, PlayerId = pid });
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"[Import] Imported {cheerList.Count} cheers for image {savedImageId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Import] Failed to import cheers from {file}: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using LiteDB;
|
||||
using static Rec_rewild_live_rewrite.api.server.Classes.db_class.LeaderboardDBClasses;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class LeaderboardDB
|
||||
{
|
||||
private static readonly LiteDatabase DB = new LiteDatabase(
|
||||
Environment.CurrentDirectory + "/Data/DBs/Leaderboard.db"
|
||||
);
|
||||
|
||||
private static ILiteCollection<Entry> Entries =>
|
||||
DB.GetCollection<Entry>("leaderboard");
|
||||
|
||||
static LeaderboardDB()
|
||||
{
|
||||
// Short index names to avoid LiteDB 32-char name limit
|
||||
Entries.EnsureIndex("pid", x => x.PlayerId);
|
||||
Entries.EnsureIndex("sc", x => x.StatChannel);
|
||||
Entries.EnsureIndex("rid", x => x.RoomId);
|
||||
|
||||
// Score index for sorting
|
||||
Entries.EnsureIndex("score", x => x.Score);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------
|
||||
// Add or Update Stat
|
||||
// ------------------------------------------
|
||||
public static RequestResult SetStat(ulong playerId, int statChannel, ulong roomId, int newValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entry = Entries.FindOne(x =>
|
||||
x.PlayerId == (int)playerId &&
|
||||
x.StatChannel == statChannel &&
|
||||
x.RoomId == (long)roomId
|
||||
);
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
entry = new Entry
|
||||
{
|
||||
PlayerId = (int)playerId,
|
||||
StatChannel = statChannel,
|
||||
RoomId = (long)roomId,
|
||||
Score = newValue
|
||||
};
|
||||
Entries.Insert(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Score = newValue;
|
||||
Entries.Update(entry);
|
||||
}
|
||||
|
||||
RecomputeRanks(statChannel, roomId);
|
||||
return RequestResult.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return RequestResult.InvalidStat;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Recalculate ranks for a specific leaderboard
|
||||
// ------------------------------------------
|
||||
private static void RecomputeRanks(int statChannel, ulong roomId)
|
||||
{
|
||||
var list = Entries.Find(x =>
|
||||
x.StatChannel == statChannel &&
|
||||
x.RoomId == (long)roomId
|
||||
)
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ToList();
|
||||
|
||||
int rank = 1;
|
||||
foreach (var e in list)
|
||||
{
|
||||
e.Rank = rank++;
|
||||
Entries.Update(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Get Player Rank
|
||||
// ------------------------------------------
|
||||
public static Entry? GetPlayerRank(ulong playerId, int statChannel, ulong roomId)
|
||||
{
|
||||
return Entries.FindOne(x =>
|
||||
x.PlayerId == (int)playerId &&
|
||||
x.StatChannel == statChannel &&
|
||||
x.RoomId == (long)roomId
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Nearby Scores Window
|
||||
// ------------------------------------------
|
||||
public static List<Entry> GetNearbyScores(
|
||||
ulong playerId,
|
||||
int statChannel,
|
||||
ulong roomId,
|
||||
int windowSize)
|
||||
{
|
||||
var me = GetPlayerRank(playerId, statChannel, roomId);
|
||||
if (me == null)
|
||||
{
|
||||
return Entries.Find(x =>
|
||||
x.StatChannel == statChannel &&
|
||||
x.RoomId == (long)roomId
|
||||
)
|
||||
.OrderBy(x => x.Rank)
|
||||
.Take(windowSize * 2 + 1)
|
||||
.ToList();
|
||||
|
||||
}
|
||||
|
||||
int start = Math.Max(me.Rank - windowSize, 1);
|
||||
int end = me.Rank + windowSize;
|
||||
|
||||
|
||||
return Entries.Find(x =>
|
||||
x.StatChannel == statChannel &&
|
||||
x.RoomId == (long)roomId &&
|
||||
x.Rank >= start &&
|
||||
x.Rank <= end
|
||||
)
|
||||
.OrderBy(x => x.Rank)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Range Get — GetRanks API
|
||||
// ------------------------------------------
|
||||
public static List<Entry> GetRanks(
|
||||
int statChannel,
|
||||
ulong roomId,
|
||||
int rankStart,
|
||||
int rankEnd)
|
||||
{
|
||||
return Entries.Find(x =>
|
||||
x.StatChannel == statChannel &&
|
||||
x.RoomId == (long)roomId &&
|
||||
x.Rank >= rankStart &&
|
||||
x.Rank <= rankEnd
|
||||
)
|
||||
.OrderBy(x => x.Rank)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using LiteDB;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Rec_rewild_live_rewrite.api.server.Classes;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public static class LinkDB
|
||||
{
|
||||
private static readonly string _dbPath = "Data/DBs/Links.db";
|
||||
private static readonly string _collection = "actionlinks";
|
||||
|
||||
public static ActionLink Insert(ActionLink link)
|
||||
{
|
||||
using var db = new LiteDatabase(_dbPath);
|
||||
var col = db.GetCollection<ActionLink>(_collection);
|
||||
col.Insert(link);
|
||||
return link;
|
||||
}
|
||||
|
||||
public static ActionLink? Get(string linkIdOrCreatorCode)
|
||||
{
|
||||
using var db = new LiteDatabase(_dbPath);
|
||||
var col = db.GetCollection<ActionLink>(_collection);
|
||||
|
||||
ActionLink? link = col.FindById(linkIdOrCreatorCode);
|
||||
|
||||
// If not found by ID, try as a creator code (case-insensitive)
|
||||
if (link == null)
|
||||
{
|
||||
string code = linkIdOrCreatorCode.Trim().ToLowerInvariant();
|
||||
var player = PlayerDB.GetInfluencerByCreatorCode(code);
|
||||
return new ActionLink // {"creatorPlayerId":4213153,"data":"{\"Type\":6}","isValid":true}
|
||||
{
|
||||
creatorPlayerId = player.player.Id,
|
||||
data = JsonConvert.SerializeObject(new { Type = (int)LinkType.Influencer }),
|
||||
isValid = true
|
||||
};
|
||||
}
|
||||
|
||||
if (link == null)
|
||||
return null;
|
||||
|
||||
// Automatically invalidate expired links
|
||||
if (link.ExpiresAt < DateTime.UtcNow && link.isValid)
|
||||
{
|
||||
link.isValid = false;
|
||||
col.Update(link);
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
|
||||
public static bool Consume(string linkId, bool alwayValid = false)
|
||||
{
|
||||
using var db = new LiteDatabase(_dbPath);
|
||||
var col = db.GetCollection<ActionLink>(_collection);
|
||||
var link = col.FindById(linkId);
|
||||
if (link == null || !link.isValid || link.ExpiresAt < DateTime.UtcNow)
|
||||
return false;
|
||||
|
||||
if (!alwayValid)
|
||||
{
|
||||
link.isValid = false;
|
||||
}
|
||||
col.Update(link);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<ActionLink> GetAll()
|
||||
{
|
||||
using var db = new LiteDatabase(_dbPath);
|
||||
var col = db.GetCollection<ActionLink>(_collection);
|
||||
return col.FindAll().ToList();
|
||||
}
|
||||
|
||||
public static void DeleteLinksFromPlayerId(ulong playerId)
|
||||
{
|
||||
using var db = new LiteDatabase(_dbPath);
|
||||
var col = db.GetCollection<ActionLink>(_collection);
|
||||
try
|
||||
{
|
||||
col.DeleteMany(x => x.creatorPlayerId == playerId);
|
||||
Console.WriteLine("deleted links from playerid " + playerId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2025
File diff suppressed because it is too large
Load Diff
+1218
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
using LiteDB;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
|
||||
|
||||
namespace Rec_rewild_live_rewrite.api.dbs
|
||||
{
|
||||
public class SettingsDB
|
||||
{
|
||||
private static readonly string _dbPath = Path.Combine(Environment.CurrentDirectory, "Data", "DBs", "Settings.db");
|
||||
private static readonly LiteDatabase _db = new LiteDatabase(_dbPath);
|
||||
|
||||
private static ILiteCollection<PlayerSetting> PlayerSettings => _db.GetCollection<PlayerSetting>("players_settings");
|
||||
|
||||
public static List<Setting> LoadSettings(ulong playerId)
|
||||
{
|
||||
return PlayerSettings.FindById(playerId)?.Settings ?? new List<Setting>();
|
||||
}
|
||||
|
||||
public static void SaveSettings(List<Setting> settings, ulong playerId)
|
||||
{
|
||||
if (playerId == 0) return;
|
||||
|
||||
var player = new PlayerSetting
|
||||
{
|
||||
PlayerId = playerId,
|
||||
Settings = settings
|
||||
};
|
||||
|
||||
PlayerSettings.Upsert(player); // ✅ SAFE
|
||||
}
|
||||
|
||||
|
||||
public static void SetPlayerSetting(string key, string value, ulong playerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key) || playerId == 0)
|
||||
return;
|
||||
|
||||
if (key is "SplitTestAssignedSegments" or "Growth.LastEmailPromptTime")
|
||||
return;
|
||||
|
||||
var col = PlayerSettings;
|
||||
|
||||
var player = col.FindById(playerId) ?? new PlayerSetting
|
||||
{
|
||||
PlayerId = playerId,
|
||||
Settings = CreateDefaultSettings()
|
||||
};
|
||||
|
||||
var setting = player.Settings.FirstOrDefault(s => s.Key == key);
|
||||
if (setting != null)
|
||||
setting.Value = value;
|
||||
else
|
||||
player.Settings.Add(new Setting { Key = key, Value = value });
|
||||
|
||||
col.Upsert(player); // 🔥 KEY LINE
|
||||
}
|
||||
|
||||
public static void DeletePlayerKey(string key, ulong playerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key) || playerId == 0)
|
||||
return;
|
||||
|
||||
var col = PlayerSettings;
|
||||
|
||||
var player = col.FindById(playerId);
|
||||
if (player == null || player.Settings == null)
|
||||
return;
|
||||
|
||||
player.Settings.RemoveAll(s => s.Key == key);
|
||||
|
||||
col.Upsert(player);
|
||||
}
|
||||
|
||||
public static string? GetPlayerSettingValue(string key, ulong playerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key) || playerId == 0)
|
||||
return null;
|
||||
|
||||
// ignored keys
|
||||
if (key == "SplitTestAssignedSegments")
|
||||
return null;
|
||||
|
||||
if (key == "Growth.LastEmailPromptTime")
|
||||
return null;
|
||||
|
||||
var settings = LoadSettings(playerId);
|
||||
var setting = settings.Find(s => s.Key == key);
|
||||
|
||||
return setting?.Value;
|
||||
}
|
||||
|
||||
public static void SetPlayerSettingFromJson(string jsonData, ulong playerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(jsonData) || playerId == 0)
|
||||
return;
|
||||
|
||||
Setting? setting;
|
||||
try
|
||||
{
|
||||
setting = JsonConvert.DeserializeObject<Setting>(jsonData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to parse setting JSON: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (setting == null || string.IsNullOrEmpty(setting.Key) || setting.Key == "SplitTestAssignedSegments")
|
||||
return;
|
||||
|
||||
SetPlayerSetting(setting.Key, setting.Value ?? "", playerId);
|
||||
}
|
||||
|
||||
public static void DeleteSettings(ulong playerId)
|
||||
{
|
||||
if (playerId == 0) return;
|
||||
|
||||
var col = PlayerSettings;
|
||||
col.DeleteMany(x => x.PlayerId == playerId);
|
||||
}
|
||||
|
||||
public static List<Setting> CreateDefaultSettings() => new List<Setting>
|
||||
{
|
||||
new Setting { Key = "MOD_BLOCKED_TIME", Value = "0" },
|
||||
new Setting { Key = "MOD_BLOCKED_DURATION", Value = "0" },
|
||||
new Setting { Key = "PlayerSessionCount", Value = "0" },
|
||||
new Setting { Key = "ShowRoomCenter", Value = "1" },
|
||||
new Setting { Key = "QualitySettings", Value = "1" },
|
||||
new Setting { Key = "Recroom.OOBE", Value = "1" },
|
||||
new Setting { Key = "VoiceFilter", Value = "0" },
|
||||
new Setting { Key = "VIGNETTED_TELEPORT_ENABLED", Value = "0" },
|
||||
new Setting { Key = "CONTINUOUS_ROTATION_MODE", Value = "0" },
|
||||
new Setting { Key = "ROTATION_INCREMENT", Value = "0" },
|
||||
new Setting { Key = "ROTATE_IN_PLACE_ENABLED", Value = "0" },
|
||||
new Setting { Key = "TeleportBuffer", Value = "0" },
|
||||
new Setting { Key = "VoiceChat", Value = "1" },
|
||||
new Setting { Key = "PersonalBubble", Value = "0" },
|
||||
new Setting { Key = "ShowNames", Value = "1" },
|
||||
new Setting { Key = "H.264 plugin", Value = "1" },
|
||||
new Setting { Key = "Recroom.AccountCreation.HasStarted", Value = "true" },
|
||||
new Setting { Key = "Recroom.AccountCreation.HasFinished", Value = "false" },
|
||||
new Setting { Key = "Recroom.AccountCreation.HasChosenUsername", Value = "false" },
|
||||
new Setting { Key = "Recroom.AccountCreation.HasCreatedPassword", Value = "false" },
|
||||
new Setting { Key = "TUTORIAL_COMPLETE_MASK", Value = "0" },
|
||||
new Setting { Key = "BACKPACK_FAVORITE_TOOL", Value = "-1" }
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user