Browse Source

Season 3 soft reset

Clément Wolf 6 tháng trước cách đây
mục cha
commit
3d77a12d44
10 tập tin đã thay đổi với 68 bổ sung25 xóa
  1. 1 1
      config.json.example
  2. 1 0
      database.js
  3. 14 11
      elo.js
  4. 19 4
      elo_cache.js
  5. 1 1
      nginx.cfg
  6. 2 2
      user.js
  7. 18 0
      views/season3.eta
  8. 1 1
      views/user.eta
  9. 5 4
      website_api.js
  10. 6 1
      website_ssr.js

+ 1 - 1
config.json.example

@@ -7,7 +7,7 @@
     "osu_v2api_client_id": 1234,
     "osu_v2api_client_secret": "get it here: https://osu.ppy.sh/home/account/edit#new-oauth-application",
     "max_lobbies": 1,
-    "games_needed_for_rank": 10,
+    "games_needed_for_rank": 5,
 
     "CONNECT_TO_DISCORD": false,
     "discord_admin": "889603773574578198",

+ 1 - 0
database.js

@@ -12,6 +12,7 @@ db.exec(`
     user_id        INTEGER NOT NULL,
     mode           INTEGER NOT NULL, -- 0 = std, 1 = taiko, 2 = ctb, 3 = mania
 
+    s3_scores      INTEGER NOT NULL,
     total_scores   INTEGER NOT NULL,
     elo            REAL    NOT NULL,
     division       TEXT    NOT NULL,

+ 14 - 11
elo.js

@@ -9,6 +9,7 @@ import {get_division_from_elo, get_rankup_progress, update_the_one} from './elo_
 const get_rating_stmt = db.prepare(`SELECT * FROM rating WHERE user_id = ? AND mode = ?`);
 const update_rating_stmt = db.prepare(`
   UPDATE rating SET
+    s3_scores = ?,
     total_scores = ?,
     elo = ?
   WHERE user_id = ? AND mode = ?`,
@@ -21,13 +22,13 @@ const insert_score_stmt = db.prepare(`INSERT INTO score (
 );
 const better_users_stmt = db.prepare(`
   SELECT COUNT(*) AS nb FROM rating r1, rating r2
-  WHERE r1.total_scores >= ? AND r1.mode = ?
-    AND r2.total_scores >= ? AND r2.mode = ?
+  WHERE r1.s3_scores >= ? AND r1.mode = ?
+    AND r2.s3_scores >= ? AND r2.mode = ?
     AND r1.elo > r2.elo AND r2.user_id = ?`,
 );
 
 
-const nb_active_users_stmt = db.prepare(`SELECT COUNT(*) AS nb FROM rating WHERE total_scores >= ? AND mode = ?`);
+const nb_active_users_stmt = db.prepare(`SELECT COUNT(*) AS nb FROM rating WHERE s3_scores >= ? AND mode = ?`);
 const nb_active_users = [
   nb_active_users_stmt.get(Config.games_needed_for_rank, 0).nb,
   nb_active_users_stmt.get(Config.games_needed_for_rank, 1).nb,
@@ -105,11 +106,10 @@ async function save_game_and_update_rating(lobby, game) {
       player.expected += 1 / (1 + Math.pow(10, (opponent.rating.elo - player.rating.elo) / 480));
     }
 
-    let k_factor = 40;
-    if (player.rating.total_scores >= Config.games_needed_for_rank) k_factor = 32;
-    if (player.rating.elo > 2400) k_factor = 16;
-    else if (player.rating.elo > 2100) k_factor = 24;
-    if (is_weekend) k_factor *= 1.5;
+    let k_factor = 32;
+    if (is_weekend || player.rating.s3_scores < Config.games_needed_for_rank) k_factor *= 1.25;
+    if (player.rating.elo > 2100) k_factor *= 0.75;
+    if (player.rating.elo > 2400) k_factor *= 0.75;
 
     player.new_elo = player.rating.elo + k_factor * (player.actual - player.expected);
     if (player.new_elo < 100) player.new_elo = 100;
@@ -127,15 +127,17 @@ async function save_game_and_update_rating(lobby, game) {
     );
 
     // score.rating is cached, so updating it here
+    if (bancho.connected) score.rating.s3_scores++;
     score.rating.total_scores++;
     score.rating.elo = score.new_elo;
 
     // We also cache the total number of active users for each ruleset :)
-    if (score.rating.total_scores == 10) {
+    if (score.rating.s3_scores == 10) {
       nb_active_users[score.rating.mode]++;
     }
 
     update_rating_stmt.run(
+        score.rating.s3_scores,
         score.rating.total_scores,
         score.rating.elo,
         score.rating.user_id,
@@ -174,7 +176,7 @@ async function save_game_and_update_rating(lobby, game) {
   const rank_changes = [];
   for (const score of game.scores) {
     const player = await get_user_by_id(score.user_id);
-    if (player.ratings[game.mode_int].total_scores < Config.games_needed_for_rank) continue;
+    if (player.ratings[game.mode_int].s3_scores < Config.games_needed_for_rank) continue;
 
     const old_rank_text = player.ratings[game.mode_int].division;
     const new_rank_text = get_division_from_elo(score.rating.elo, game.mode_int);
@@ -222,12 +224,13 @@ function get_user_rank(user_id, ruleset) {
       Config.games_needed_for_rank, ruleset,
       user_id,
   );
-  const is_ranked = rating.total_scores >= Config.games_needed_for_rank;
+  const is_ranked = rating.s3_scores >= Config.games_needed_for_rank;
 
   const info = {
     elo: '???',
     rank_nb: '???',
     text: 'Unranked',
+    s3_scores: rating.s3_scores,
     total_scores: rating.total_scores,
     is_ranked: is_ranked,
   };

+ 19 - 4
elo_cache.js

@@ -25,7 +25,7 @@ const divisions = [
 function update_the_one(user_id, elo, mode) {
   // Check that "The One" is not deranking after a loss
   if (user_id == the_ones[mode].user_id) {
-    const the_one = db.prepare('SELECT user_id, elo FROM rating WHERE mode = ? ORDER BY elo DESC LIMIT 1').get(mode);
+    const the_one = db.prepare('SELECT user_id, elo FROM rating WHERE s3_scores >= ? AND mode = ? ORDER BY elo DESC LIMIT 1').get(Config.games_needed_for_rank, mode);
     division_floors[mode][divisions.length - 1] = the_one.elo;
     the_ones[mode] = {
       user_id: the_one.user_id,
@@ -54,9 +54,15 @@ function update_division_tresholds() {
   const nb_divisions = divisions.length - 1;
   for (let j = 0; j < 4; j++) {
     // Really stupid query but that's the fastest way I found...
-    const elos = db.prepare('SELECT elo FROM rating WHERE total_scores >= ? AND mode = ? ORDER BY elo ASC').all(Config.games_needed_for_rank, j);
+    const elos = db.prepare('SELECT elo FROM rating WHERE s3_scores >= ? AND mode = ? ORDER BY elo ASC').all(Config.games_needed_for_rank, j);
 
-    division_floors[j][0] = 0;
+    // Season just started, we don't know the tresholds yet
+    if (elos.length < 20) {
+      division_floors[j] = [100, 1200, 1300, 1400, 1475, 1525, 1600, 1700, 1775, 1850, 1950, 2050, 2150, 2300, 3000];
+      continue;
+    }
+
+    division_floors[j][0] = 100;
     for (let i = 1; i < nb_divisions; i++) {
       // Turn current division into a value between 0 and 1
       const rank_nb = i / nb_divisions;
@@ -74,7 +80,16 @@ function update_division_tresholds() {
 
   // Update floor for 'The One'
   for (let i = 0; i < 4; i++) {
-    const the_one = db.prepare('SELECT user_id, elo FROM rating WHERE mode = ? ORDER BY elo DESC LIMIT 1').get(i);
+    const the_one = db.prepare('SELECT user_id, elo FROM rating WHERE s3_scores >= ? AND mode = ? ORDER BY elo DESC LIMIT 1').get(Config.games_needed_for_rank, i);
+    if (!the_one) {
+      // New season, no scores yet!
+      the_ones[i] = {
+        user_id: null,
+        elo: 100,
+      };
+      continue;
+    }
+
     division_floors[i][nb_divisions] = the_one.elo;
     the_ones[i] = {
       user_id: the_one.user_id,

+ 1 - 1
nginx.cfg

@@ -5,7 +5,7 @@ server {
 
     location ~ \.(?:ico|png|css|js|webmanifest)(?:\?\d+)?$ {
         root /path/to/the/repository/osu-ranked-lobbies/public;
-        add_header Cache-Control "public, max-age=604800";
+        add_header Cache-Control "public, s-maxage=604800";
     }
 
     location / {

+ 2 - 2
user.js

@@ -7,8 +7,8 @@ import {sync_discord_info} from './discord_updates.js';
 async function init_user(user_id) {
   for (let ruleset = 0; ruleset < 4; ruleset++) {
     db.prepare(`INSERT INTO
-      rating (user_id, mode, total_scores, elo,  division)
-      VALUES (?,       ?,    0,            1500, 'Unranked')
+      rating (user_id, mode, s3_scores, total_scores, elo,  division)
+      VALUES (?,       ?,    0,         0,            1500, 'Unranked')
       RETURNING *`,
     ).run(user_id, ruleset);
   }

+ 18 - 0
views/season3.eta

@@ -0,0 +1,18 @@
+<% layout('main.eta') %>
+
+<%
+  function fancy_elo(elo) {
+    if (elo == '???') {
+      return '???';
+    } else {
+      return Math.round(elo);
+    }
+  }
+%>
+
+<div class="max-w-5xl m-auto">
+  <div class="mt-16 h-96 text-center">
+    <h1>Season 3 just started!</h1>
+    <h3>There are not enough scores for the leaderboard. Come back later...</h3>
+  </div>
+</div>

+ 1 - 1
views/user.eta

@@ -54,7 +54,7 @@
       </svg>`;
   }
 
-  let rankup_percent = (it.rank.elo - it.rank.rankup.floor_elo) / (it.rank.rankup.ceil_elo - it.rank.rankup.floor_elo);
+  let rankup_percent = (it.rank.elo - it.rank.rankup?.floor_elo) / (it.rank.rankup?.ceil_elo - it.rank.rankup?.floor_elo);
   rankup_percent = Math.round(rankup_percent * 100);
 %>
 

+ 5 - 4
website_api.js

@@ -179,6 +179,7 @@ async function get_leaderboard_page(mode, page_num) {
   const PLAYERS_PER_PAGE = 20;
 
   const active_users = get_active_users(mode);
+  if (active_users < 20) return null;
 
   // Fix user-provided page number
   const nb_pages = Math.ceil(active_users / PLAYERS_PER_PAGE);
@@ -194,7 +195,7 @@ async function get_leaderboard_page(mode, page_num) {
   const res = db.prepare(`
     SELECT user.user_id, country_code, username, elo FROM user
     INNER JOIN rating ON user.user_id = rating.user_id
-    WHERE total_scores >= ? AND mode = ?
+    WHERE s3_scores >= ? AND mode = ?
     ORDER BY elo DESC LIMIT ? OFFSET ?`,
   ).all(Config.games_needed_for_rank, mode, PLAYERS_PER_PAGE, offset);
 
@@ -339,7 +340,7 @@ async function register_routes(app) {
   app.all('/api/leaderboard/:pageNum/', async (req, http_res) => {
     try {
       const data = await get_leaderboard_page(get_mode(req), parseInt(req.params.pageNum, 10));
-      http_res.set('Cache-control', 'public, max-age=60');
+      http_res.set('Cache-control', 'public, s-maxage=10');
       http_res.json(data);
     } catch (err) {
       http_res.status(err.http_code || 503).json({error: err.message});
@@ -349,7 +350,7 @@ async function register_routes(app) {
   app.all('/api/user/:userId/', async (req, http_res) => {
     try {
       const data = await get_user_profile(parseInt(req.params.userId, 10), get_mode(req));
-      http_res.set('Cache-control', 'public, max-age=60');
+      http_res.set('Cache-control', 'public, s-maxage=10');
       http_res.json(data);
     } catch (err) {
       http_res.status(err.http_code || 503).json({error: err.message});
@@ -363,7 +364,7 @@ async function register_routes(app) {
           get_mode(req),
           parseInt(req.params.pageNum, 10),
       );
-      http_res.set('Cache-control', 'public, max-age=60');
+      http_res.set('Cache-control', 'public, s-maxage=10');
       http_res.json(data);
     } catch (err) {
       http_res.status(err.http_code || 503).json({error: err.message});

+ 6 - 1
website_ssr.js

@@ -21,13 +21,14 @@ const base = fs.readFileSync('views/main.eta', 'utf-8');
 const user_not_found_page = base.replace('<%~ it.body %>', 'User not found.'); // TODO better looking page
 const faq_page = eta.render('faq.eta');
 const js_page = eta.render('jspage.eta');
+const season3_page = eta.render('season3.eta');
 
 
 function mini_cache(res) {
   if (!Config.IS_PRODUCTION) return;
 
   // Cache that only lasts 10 seconds, to reduce load while still keeping content fresh
-  res.set('Cache-Control', 'public, max-age=10');
+  res.set('Cache-Control', 'public, s-maxage=10');
 }
 
 function get_mode(req) {
@@ -109,6 +110,10 @@ async function render_leaderboard(req, res, page_num) {
   mini_cache(res);
 
   const data = await get_leaderboard_page(get_mode(req), page_num);
+  if (!data) {
+    return res.end(season3_page);
+  }
+
   for (const player of data.players) {
     player.flag = country_code_to_flag_html(player.country_code);
   }