Complete Records/Leaderboards + Time Trials systems (100%)
RECORDS & LEADERBOARDS (5/5 endpoints - 100%):
- Created LeaderboardsController with 5 endpoints
- GET /synergy/leaderboards/timetrials/{trialId}
- GET /synergy/leaderboards/career/{series}/{event}
- GET /synergy/leaderboards/global/top100
- GET /synergy/leaderboards/player/{synergyId}/records
- GET /synergy/leaderboards/compare/{synergyId1}/{synergyId2}
Added LeaderboardEntry and PersonalRecord models and database tables.
Migration applied: AddLeaderboardsAndRecords
Updated RewardsController.SubmitTimeTrial to track personal bests,
update leaderboards, and award 50 gold bonus for improvements.
Updated ProgressionController.CompleteCareerEvent similarly for
career event personal records.
TIME TRIALS (6/6 endpoints - 100%):
- GET /synergy/rewards/timetrials - List with time remaining
- GET /synergy/rewards/timetrials/{id} - Details with stats
- POST /synergy/rewards/timetrials/{id}/submit - Submit with PB tracking
- GET /synergy/rewards/timetrials/player/{synergyId}/results - History
- POST /synergy/rewards/timetrials/{id}/claim - Claim bonuses
- GET /synergy/leaderboards/timetrials/{id} - Leaderboards (above)
Added navigation properties to TimeTrialResult for easier queries.
Server progress: 66/73 endpoints (90%)
Two complete systems: Records/Leaderboards + Time Trials
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -300,8 +300,12 @@ public class ProgressionController : ControllerBase
|
||||
cp.SeriesName == completion.SeriesName &&
|
||||
cp.EventName == completion.EventName);
|
||||
|
||||
bool isFirstCompletion = false;
|
||||
double? previousBestTime = null;
|
||||
|
||||
if (progress == null)
|
||||
{
|
||||
isFirstCompletion = true;
|
||||
progress = new CareerProgress
|
||||
{
|
||||
UserId = user.Id,
|
||||
@@ -312,36 +316,114 @@ public class ProgressionController : ControllerBase
|
||||
};
|
||||
_context.CareerProgress.Add(progress);
|
||||
}
|
||||
else
|
||||
{
|
||||
previousBestTime = progress.BestTime > 0 ? progress.BestTime : null;
|
||||
}
|
||||
|
||||
// Update progress
|
||||
progress.Completed = true;
|
||||
progress.StarsEarned = Math.Max(progress.StarsEarned, completion.StarsEarned);
|
||||
|
||||
bool isNewBestTime = progress.BestTime == 0 || completion.RaceTime < progress.BestTime;
|
||||
progress.BestTime = progress.BestTime == 0 ? completion.RaceTime :
|
||||
Math.Min(progress.BestTime, completion.RaceTime);
|
||||
progress.CompletedAt = DateTime.UtcNow;
|
||||
|
||||
// Track personal record for career event
|
||||
var recordCategory = $"{completion.SeriesName}/{completion.EventName}";
|
||||
var personalRecord = await _context.PersonalRecords
|
||||
.FirstOrDefaultAsync(pr => pr.SynergyId == completion.SynergyId &&
|
||||
pr.RecordType == "Career" &&
|
||||
pr.RecordCategory == recordCategory);
|
||||
|
||||
bool isNewPersonalBest = false;
|
||||
double? improvement = null;
|
||||
|
||||
if (personalRecord == null)
|
||||
{
|
||||
// First attempt
|
||||
isNewPersonalBest = true;
|
||||
personalRecord = new Data.PersonalRecord
|
||||
{
|
||||
SynergyId = completion.SynergyId,
|
||||
RecordType = "Career",
|
||||
RecordCategory = recordCategory,
|
||||
TrackName = completion.TrackName,
|
||||
CarName = completion.CarName,
|
||||
BestTimeSeconds = completion.RaceTime,
|
||||
AchievedAt = DateTime.UtcNow,
|
||||
TotalAttempts = 1
|
||||
};
|
||||
_context.PersonalRecords.Add(personalRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
personalRecord.TotalAttempts++;
|
||||
|
||||
if (completion.RaceTime < personalRecord.BestTimeSeconds)
|
||||
{
|
||||
isNewPersonalBest = true;
|
||||
improvement = personalRecord.BestTimeSeconds - completion.RaceTime;
|
||||
|
||||
personalRecord.BestTimeSeconds = completion.RaceTime;
|
||||
personalRecord.AchievedAt = DateTime.UtcNow;
|
||||
personalRecord.ImprovementSeconds = improvement;
|
||||
personalRecord.CarName = completion.CarName;
|
||||
}
|
||||
}
|
||||
|
||||
// Add/update leaderboard entry if new personal best
|
||||
if (isNewPersonalBest)
|
||||
{
|
||||
var leaderboardEntry = new Data.LeaderboardEntry
|
||||
{
|
||||
SynergyId = completion.SynergyId,
|
||||
PlayerName = user.SynergyId,
|
||||
RecordType = "Career",
|
||||
RecordCategory = recordCategory,
|
||||
TrackName = completion.TrackName,
|
||||
CarName = completion.CarName,
|
||||
TimeSeconds = completion.RaceTime,
|
||||
SubmittedAt = DateTime.UtcNow
|
||||
};
|
||||
_context.LeaderboardEntries.Add(leaderboardEntry);
|
||||
}
|
||||
|
||||
// Award rewards
|
||||
int goldReward = completion.StarsEarned * 10; // 10 gold per star
|
||||
int cashReward = completion.StarsEarned * 2000; // 2000 cash per star
|
||||
int xpReward = completion.StarsEarned * 100; // 100 XP per star
|
||||
|
||||
// Bonus for personal best
|
||||
if (isNewPersonalBest && previousBestTime.HasValue)
|
||||
{
|
||||
goldReward += 50;
|
||||
}
|
||||
|
||||
user.Gold = (user.Gold ?? 0) + goldReward;
|
||||
user.Cash = (user.Cash ?? 0) + cashReward;
|
||||
user.Experience = (user.Experience ?? 0) + xpReward;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(new
|
||||
// Calculate global rank
|
||||
int globalRank = await _context.LeaderboardEntries
|
||||
.Where(e => e.RecordType == "Career" &&
|
||||
e.RecordCategory == recordCategory &&
|
||||
e.TimeSeconds < completion.RaceTime)
|
||||
.CountAsync() + 1;
|
||||
|
||||
return Ok(new RecordSubmissionResponse
|
||||
{
|
||||
success = true,
|
||||
stars = completion.StarsEarned,
|
||||
goldEarned = goldReward,
|
||||
cashEarned = cashReward,
|
||||
xpEarned = xpReward,
|
||||
bestTime = progress.BestTime,
|
||||
totalGold = user.Gold,
|
||||
totalCash = user.Cash,
|
||||
totalExperience = user.Experience
|
||||
Success = true,
|
||||
IsNewPersonalBest = isNewPersonalBest,
|
||||
IsNewGlobalRecord = globalRank == 1,
|
||||
GlobalRank = globalRank,
|
||||
PreviousBestTime = previousBestTime,
|
||||
Improvement = improvement,
|
||||
GoldEarned = goldReward,
|
||||
CashEarned = cashReward
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user