11#nullable enable
2- using System . Diagnostics . CodeAnalysis ;
32using LBPUnion . ProjectLighthouse . Database ;
43using LBPUnion . ProjectLighthouse . Extensions ;
54using LBPUnion . ProjectLighthouse . Helpers ;
@@ -30,26 +29,14 @@ public ScoreController(DatabaseContext database)
3029 this . database = database ;
3130 }
3231
33- private string [ ] getFriendUsernames ( int userId , string username )
32+ private static int [ ] GetFriendIds ( int userId )
3433 {
3534 UserFriendData ? store = UserFriendStore . GetUserFriendData ( userId ) ;
36- if ( store == null ) return new [ ] { username , } ;
35+ List < int > ? friendIds = store ? . FriendIds ;
36+ friendIds ??= new List < int > ( ) ;
37+ friendIds . Add ( userId ) ;
3738
38- List < string > friendNames = new ( )
39- {
40- username ,
41- } ;
42-
43- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
44- foreach ( int friendId in store . FriendIds )
45- {
46- string ? friendUsername = this . database . Users . Where ( u => u . UserId == friendId )
47- . Select ( u => u . Username )
48- . FirstOrDefault ( ) ;
49- if ( friendUsername != null ) friendNames . Add ( friendUsername ) ;
50- }
51-
52- return friendNames . ToArray ( ) ;
39+ return friendIds . Distinct ( ) . ToArray ( ) ;
5340 }
5441
5542 [ HttpPost ( "scoreboard/{slotType}/{id:int}" ) ]
@@ -144,41 +131,41 @@ public async Task<IActionResult> SubmitScore(string slotType, int id, int childI
144131
145132 await this . database . SaveChangesAsync ( ) ;
146133
147- string playerIdCollection = string . Join ( ',' , score . PlayerIds ) ;
148-
149134 ScoreEntity ? existingScore = await this . database . Scores . Where ( s => s . SlotId == slot . SlotId )
150135 . Where ( s => s . ChildSlotId == 0 || s . ChildSlotId == childId )
151- . Where ( s => s . PlayerIdCollection == playerIdCollection )
136+ . Where ( s => s . UserId == token . UserId )
152137 . Where ( s => s . Type == score . Type )
153138 . FirstOrDefaultAsync ( ) ;
154139 if ( existingScore != null )
155140 {
156141 existingScore . Points = Math . Max ( existingScore . Points , score . Points ) ;
142+ existingScore . Timestamp = TimeHelper . TimestampMillis ;
157143 }
158144 else
159145 {
160146 ScoreEntity playerScore = new ( )
161147 {
162- PlayerIdCollection = playerIdCollection ,
148+ UserId = token . UserId ,
163149 Type = score . Type ,
164150 Points = score . Points ,
165151 SlotId = slotId ,
166152 ChildSlotId = childId ,
153+ Timestamp = TimeHelper . TimestampMillis ,
167154 } ;
168155 this . database . Scores . Add ( playerScore ) ;
169156 }
170157
171158 await this . database . SaveChangesAsync ( ) ;
172159
173- return this . Ok ( this . getScores ( new LeaderboardOptions
160+ return this . Ok ( await this . GetScores ( new LeaderboardOptions
174161 {
175162 RootName = "scoreboardSegment" ,
176163 PageSize = 5 ,
177164 PageStart = - 1 ,
178165 SlotId = slotId ,
179166 ChildSlotId = childId ,
180167 ScoreType = score . Type ,
181- TargetUsername = username ,
168+ TargetUser = token . UserId ,
182169 TargetPlayerIds = null ,
183170 } ) ) ;
184171 }
@@ -189,8 +176,6 @@ public async Task<IActionResult> Lbp1Leaderboards(string slotType, int id)
189176 {
190177 GameTokenEntity token = this . GetToken ( ) ;
191178
192- string username = await this . database . UsernameFromGameToken ( token ) ;
193-
194179 if ( slotType == "developer" ) id = await SlotHelper . GetPlaceholderSlotId ( this . database , id , SlotType . Developer ) ;
195180
196181 LeaderboardOptions options = new ( )
@@ -199,7 +184,7 @@ public async Task<IActionResult> Lbp1Leaderboards(string slotType, int id)
199184 PageStart = 1 ,
200185 ScoreType = - 1 ,
201186 SlotId = id ,
202- TargetUsername = username ,
187+ TargetUser = token . UserId ,
203188 RootName = "scoreboardSegment" ,
204189 } ;
205190 if ( ! HttpMethods . IsPost ( this . Request . Method ) )
@@ -208,7 +193,7 @@ public async Task<IActionResult> Lbp1Leaderboards(string slotType, int id)
208193 for ( int i = 1 ; i <= 4 ; i ++ )
209194 {
210195 options . ScoreType = i ;
211- ScoreboardResponse response = this . getScores ( options ) ;
196+ ScoreboardResponse response = await this . GetScores ( options ) ;
212197 scoreboardResponses . Add ( new PlayerScoreboardResponse ( response . Scores , i ) ) ;
213198 }
214199 return this . Ok ( new MultiScoreboardResponse ( scoreboardResponses ) ) ;
@@ -217,9 +202,9 @@ public async Task<IActionResult> Lbp1Leaderboards(string slotType, int id)
217202 GameScore ? score = await this . DeserializeBody < GameScore > ( ) ;
218203 if ( score == null ) return this . BadRequest ( ) ;
219204 options . ScoreType = score . Type ;
220- options . TargetPlayerIds = this . getFriendUsernames ( token . UserId , username ) ;
205+ options . TargetPlayerIds = GetFriendIds ( token . UserId ) ;
221206
222- return this . Ok ( this . getScores ( options ) ) ;
207+ return this . Ok ( await this . GetScores ( options ) ) ;
223208 }
224209
225210 [ HttpGet ( "friendscores/{slotType}/{slotId:int}/{type:int}" ) ]
@@ -230,96 +215,95 @@ public async Task<IActionResult> FriendScores(string slotType, int slotId, int?
230215
231216 if ( pageSize <= 0 ) return this . BadRequest ( ) ;
232217
233- string username = await this . database . UsernameFromGameToken ( token ) ;
234-
235218 if ( SlotHelper . IsTypeInvalid ( slotType ) ) return this . BadRequest ( ) ;
236219
237220 if ( slotType == "developer" ) slotId = await SlotHelper . GetPlaceholderSlotId ( this . database , slotId , SlotType . Developer ) ;
238221
239- string [ ] friendIds = this . getFriendUsernames ( token . UserId , username ) ;
222+ int [ ] friendIds = GetFriendIds ( token . UserId ) ;
240223
241- return this . Ok ( this . getScores ( new LeaderboardOptions
224+ return this . Ok ( await this . GetScores ( new LeaderboardOptions
242225 {
243226 RootName = "scores" ,
244227 PageSize = pageSize ,
245228 PageStart = pageStart ,
246229 SlotId = slotId ,
247230 ChildSlotId = childId ,
248231 ScoreType = type ,
249- TargetUsername = username ,
232+ TargetUser = token . UserId ,
250233 TargetPlayerIds = friendIds ,
251234 } ) ) ;
252235 }
253236
254237 [ HttpGet ( "topscores/{slotType}/{slotId:int}/{type:int}" ) ]
255238 [ HttpGet ( "topscores/{slotType}/{slotId:int}/{childId:int}/{type:int}" ) ]
256- [ SuppressMessage ( "ReSharper" , "PossibleMultipleEnumeration" ) ]
257239 public async Task < IActionResult > TopScores ( string slotType , int slotId , int ? childId , int type , [ FromQuery ] int pageStart = - 1 , [ FromQuery ] int pageSize = 5 )
258240 {
259241 GameTokenEntity token = this . GetToken ( ) ;
260242
261243 if ( pageSize <= 0 ) return this . BadRequest ( ) ;
262244
263- string username = await this . database . UsernameFromGameToken ( token ) ;
264-
265245 if ( SlotHelper . IsTypeInvalid ( slotType ) ) return this . BadRequest ( ) ;
266246
267247 if ( slotType == "developer" ) slotId = await SlotHelper . GetPlaceholderSlotId ( this . database , slotId , SlotType . Developer ) ;
268248
269- return this . Ok ( this . getScores ( new LeaderboardOptions
249+ return this . Ok ( await this . GetScores ( new LeaderboardOptions
270250 {
271251 RootName = "scores" ,
272252 PageSize = pageSize ,
273253 PageStart = pageStart ,
274254 SlotId = slotId ,
275255 ChildSlotId = childId ,
276256 ScoreType = type ,
277- TargetUsername = username ,
257+ TargetUser = token . UserId ,
278258 TargetPlayerIds = null ,
279259 } ) ) ;
280260 }
281261
282262 private class LeaderboardOptions
283263 {
284- public int SlotId { get ; set ; }
264+ public int SlotId { get ; init ; }
285265 public int ScoreType { get ; set ; }
286- public string TargetUsername { get ; set ; } = "" ;
287- public int PageStart { get ; set ; } = - 1 ;
288- public int PageSize { get ; set ; } = 5 ;
289- public string RootName { get ; set ; } = "scores" ;
290- public string [ ] ? TargetPlayerIds ;
266+ public int TargetUser { get ; init ; }
267+ public int PageStart { get ; init ; } = - 1 ;
268+ public int PageSize { get ; init ; } = 5 ;
269+ public string RootName { get ; init ; } = "scores" ;
270+ public int [ ] ? TargetPlayerIds ;
291271 public int ? ChildSlotId ;
292272 }
293273
294- private ScoreboardResponse getScores ( LeaderboardOptions options )
274+ private async Task < ScoreboardResponse > GetScores ( LeaderboardOptions options )
295275 {
296- // This is hella ugly but it technically assigns the proper rank to a score
297- // var needed for Anonymous type returned from SELECT
298- var rankedScores = this . database . Scores . Where ( s => s . SlotId == options . SlotId )
276+ IQueryable < ScoreEntity > scoreQuery = this . database . Scores . Where ( s => s . SlotId == options . SlotId )
299277 . Where ( s => options . ScoreType == - 1 || s . Type == options . ScoreType )
300278 . Where ( s => s . ChildSlotId == 0 || s . ChildSlotId == options . ChildSlotId )
301- . AsEnumerable ( )
302- . Where ( s => options . TargetPlayerIds == null ||
303- options . TargetPlayerIds . Any ( id => s . PlayerIdCollection . Split ( "," ) . Contains ( id ) ) )
304- . OrderByDescending ( s => s . Points )
305- . ThenBy ( s => s . ScoreId )
306- . ToList ( )
307- . Select ( ( s , rank ) => new
279+ . Where ( s => options . TargetPlayerIds == null || options . TargetPlayerIds . Contains ( s . UserId ) ) ;
280+
281+ // First find if you have a score on a level to find scores around it
282+ var myScore = await scoreQuery . Where ( s => s . UserId == options . TargetUser )
283+ . Select ( s => new
308284 {
309285 Score = s ,
310- Rank = rank + 1 ,
311- } )
312- . ToList ( ) ;
286+ Rank = scoreQuery . Count ( s2 => s2 . Points > s . Points ) + 1 ,
287+ } ) . FirstOrDefaultAsync ( ) ;
313288
289+ int skipAmt = options . PageStart != - 1 || myScore == null ? options . PageStart - 1 : myScore . Rank - 3 ;
314290
315- // Find your score, since even if you aren't in the top list your score is pinned
316- var myScore = rankedScores . Where ( rs => rs . Score . PlayerIdCollection . Split ( "," ) . Contains ( options . TargetUsername ) ) . MaxBy ( rs => rs . Score . Points ) ;
291+ var rankedScores = scoreQuery . OrderByDescending ( s => s . Points )
292+ . ThenBy ( s => s . Timestamp )
293+ . ThenBy ( s => s . ScoreId )
294+ . Skip ( Math . Max ( 0 , skipAmt ) )
295+ . Take ( Math . Min ( options . PageSize , 30 ) )
296+ . Select ( s => new
297+ {
298+ Score = s ,
299+ Rank = scoreQuery . Count ( s2 => s2 . Points > s . Points ) + 1 ,
300+ } )
301+ . ToList ( ) ;
317302
318- // Paginated viewing: if not requesting pageStart, get results around user
319- var pagedScores = rankedScores . Skip ( options . PageStart != - 1 || myScore == null ? options . PageStart - 1 : myScore . Rank - 3 ) . Take ( Math . Min ( options . PageSize , 30 ) ) ;
303+ int totalScores = scoreQuery . Count ( ) ;
320304
321- List < GameScore > gameScores = pagedScores . ToSerializableList ( ps => GameScore . CreateFromEntity ( ps . Score , ps . Rank ) ) ;
305+ List < GameScore > gameScores = rankedScores . ToSerializableList ( ps => GameScore . CreateFromEntity ( ps . Score , ps . Rank ) ) ;
322306
323- return new ScoreboardResponse ( options . RootName , gameScores , rankedScores . Count , myScore ? . Score . Points ?? 0 , myScore ? . Rank ?? 0 ) ;
307+ return new ScoreboardResponse ( options . RootName , gameScores , totalScores , myScore ? . Score . Points ?? 0 , myScore ? . Rank ?? 0 ) ;
324308 }
325309}
0 commit comments