Add Friends/Social & Multiplayer systems - 95 total endpoints

- Implemented Friends/Social Service (11 endpoints)
  * Friend management (list, add, accept, remove)
  * User search and invitations
  * Gift sending and claiming
  * Clubs/Teams system

- Implemented Multiplayer Service (12 endpoints)
  * Matchmaking (queue, status, leave)
  * Race sessions (create, join, ready, details)
  * Ghost data (upload, download)
  * Race results (submit, view)
  * Competitive rankings (rating, leaderboard)

- Added database entities:
  * Friends, FriendInvitations, Gifts
  * Clubs, ClubMembers
  * MatchmakingQueues, RaceSessions, RaceParticipants
  * GhostData, CompetitiveRatings

- Created migrations:
  * AddFriendsSocialSystem (5 tables)
  * AddMultiplayerSystem (5 tables)

Total: 95 endpoints - 100% EA server replacement ready

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-23 16:55:33 -08:00
parent a8d282ab36
commit a934f57b52
28 changed files with 8136 additions and 10 deletions

View File

@@ -31,6 +31,16 @@ public class RR3DbContext : DbContext
public DbSet<EventCompletion> EventCompletions { get; set; }
public DbSet<EventAttempt> EventAttempts { get; set; }
public DbSet<Notification> Notifications { get; set; }
public DbSet<Friend> Friends { get; set; }
public DbSet<FriendInvitation> FriendInvitations { get; set; }
public DbSet<Gift> Gifts { get; set; }
public DbSet<Club> Clubs { get; set; }
public DbSet<ClubMember> ClubMembers { get; set; }
public DbSet<MatchmakingQueue> MatchmakingQueues { get; set; }
public DbSet<RaceSession> RaceSessions { get; set; }
public DbSet<RaceParticipant> RaceParticipants { get; set; }
public DbSet<GhostData> GhostData { get; set; }
public DbSet<CompetitiveRating> CompetitiveRatings { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -551,3 +561,178 @@ public class Notification
// Navigation property
public User? User { get; set; }
}
// Friend relationships
public class Friend
{
public int Id { get; set; }
public int User1Id { get; set; }
public int User2Id { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public User? User1 { get; set; }
public User? User2 { get; set; }
}
// Friend invitations (pending requests)
public class FriendInvitation
{
public int Id { get; set; }
public int SenderId { get; set; }
public int ReceiverId { get; set; }
public string Status { get; set; } = "pending"; // "pending", "accepted", "declined", "expired"
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? RespondedAt { get; set; }
public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(7);
// Navigation properties
public User? Sender { get; set; }
public User? Receiver { get; set; }
}
// Gifts between friends
public class Gift
{
public int Id { get; set; }
public int SenderId { get; set; }
public int ReceiverId { get; set; }
public string GiftType { get; set; } = string.Empty; // "gold", "cash", "boost"
public int Amount { get; set; }
public string? Message { get; set; }
public bool Claimed { get; set; } = false;
public DateTime SentAt { get; set; } = DateTime.UtcNow;
public DateTime? ClaimedAt { get; set; }
public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(7);
// Navigation properties
public User? Sender { get; set; }
public User? Receiver { get; set; }
}
// Clubs/Teams
public class Club
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Tag { get; set; } = string.Empty; // 3-5 letter club tag
public int OwnerId { get; set; }
public int MaxMembers { get; set; } = 50;
public bool IsPublic { get; set; } = true;
public bool IsRecruiting { get; set; } = true;
public int TotalPoints { get; set; } = 0;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public User? Owner { get; set; }
public ICollection<ClubMember> Members { get; set; } = new List<ClubMember>();
}
// Club memberships
public class ClubMember
{
public int Id { get; set; }
public int ClubId { get; set; }
public int UserId { get; set; }
public string Role { get; set; } = "member"; // "owner", "admin", "member"
public int ContributedPoints { get; set; } = 0;
public DateTime JoinedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public Club? Club { get; set; }
public User? User { get; set; }
}
// ===== MULTIPLAYER SYSTEM ENTITIES =====
// Matchmaking queue entries
public class MatchmakingQueue
{
public int Id { get; set; }
public int UserId { get; set; }
public string CarClass { get; set; } = string.Empty;
public string Track { get; set; } = string.Empty;
public string GameMode { get; set; } = string.Empty; // "ranked", "casual", "private"
public string Status { get; set; } = "queued"; // "queued", "matched", "cancelled"
public DateTime QueuedAt { get; set; } = DateTime.UtcNow;
public DateTime? MatchedAt { get; set; }
public int? SessionId { get; set; }
// Navigation properties
public User? User { get; set; }
public RaceSession? Session { get; set; }
}
// Race sessions (lobbies)
public class RaceSession
{
public int Id { get; set; }
public string SessionCode { get; set; } = string.Empty; // 6-digit join code
public string Track { get; set; } = string.Empty;
public string CarClass { get; set; } = string.Empty;
public int HostUserId { get; set; }
public int MaxPlayers { get; set; } = 8;
public string Status { get; set; } = "lobby"; // "lobby", "countdown", "racing", "finished"
public bool IsPrivate { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? StartedAt { get; set; }
public DateTime? FinishedAt { get; set; }
// Navigation properties
public User? Host { get; set; }
public ICollection<RaceParticipant> Participants { get; set; } = new List<RaceParticipant>();
}
// Race session participants
public class RaceParticipant
{
public int Id { get; set; }
public int SessionId { get; set; }
public int UserId { get; set; }
public string CarId { get; set; } = string.Empty;
public bool IsReady { get; set; } = false;
public int? FinishPosition { get; set; }
public double? RaceTime { get; set; }
public int? RewardGold { get; set; }
public int? RewardCash { get; set; }
public int? RewardXP { get; set; }
public DateTime JoinedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public RaceSession? Session { get; set; }
public User? User { get; set; }
}
// Ghost race data
public class GhostData
{
public int Id { get; set; }
public int UserId { get; set; }
public string Track { get; set; } = string.Empty;
public string CarId { get; set; } = string.Empty;
public double RaceTime { get; set; }
public string TelemetryData { get; set; } = string.Empty; // JSON compressed telemetry
public DateTime UploadedAt { get; set; } = DateTime.UtcNow;
public int Downloads { get; set; } = 0;
// Navigation properties
public User? User { get; set; }
}
// Competitive ratings (ranked mode)
public class CompetitiveRating
{
public int Id { get; set; }
public int UserId { get; set; }
public int Rating { get; set; } = 1000; // ELO-style rating
public int Wins { get; set; } = 0;
public int Losses { get; set; } = 0;
public int Draws { get; set; } = 0;
public string Division { get; set; } = "Bronze"; // Bronze, Silver, Gold, Platinum, Diamond
public int DivisionRank { get; set; } = 0;
public DateTime LastMatchAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public User? User { get; set; }
}