Skip to content

Commit

Permalink
Add all-time leaderboards
Browse files Browse the repository at this point in the history
  • Loading branch information
fatsbrown authored Aug 30, 2024
1 parent f064b1a commit c6b45fa
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 17 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ To enable support for multiple users perform the steps below:
* You can teleport to a bookmark using the teleport icon on the action bar.
</details>

### All-time leaderboards

To enable all-time leaderboards (override 60 minutes live results and 90 days personal records), create a file ``all_time_leaderboards.txt`` in the ``storage`` directory.

## Community Discord server and Strava club

Please join the community supported [Discord](https://discord.gg/GMdn8F8) server and [Strava](https://www.strava.com/clubs/zoffline) club.
Expand Down
49 changes: 32 additions & 17 deletions zwift_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def make_dir(name):
if os.path.exists(GHOST_PROFILE_FILE):
with open(GHOST_PROFILE_FILE) as f:
GHOST_PROFILE = json.load(f)
ALL_TIME_LEADERBOARDS = os.path.exists("%s/all_time_leaderboards.txt" % STORAGE_DIR)
MULTIPLAYER = os.path.exists("%s/multiplayer.txt" % STORAGE_DIR)
if MULTIPLAYER:
if not make_dir(LOGS_DIR):
Expand Down Expand Up @@ -2132,7 +2133,7 @@ def player_playbacks_player_playbacks(player_id, segment_id, option):
pb_type = playback_pb2.PlaybackType.Value(request.args.get('type'))
query = "SELECT * FROM playback WHERE player_id = :p AND segment_id = :s AND type = :t"
args = {"p": player_id, "s": segment_id, "t": pb_type}
if after != '18446744065933551616':
if after != '18446744065933551616' and not ALL_TIME_LEADERBOARDS:
query += " AND world_time > :a"
args.update({"a": after})
if before != '0':
Expand Down Expand Up @@ -3345,13 +3346,13 @@ def api_personal_records_my_records():

where_stmt = "WHERE segment_id = :s AND player_id = :p"
args = {"s": segment_id, "p": current_user.player_id}
if from_date:
if from_date and not ALL_TIME_LEADERBOARDS:
where_stmt += " AND strftime('%s', finish_time_str) > strftime('%s', :f)"
args.update({"f": from_date})
if to_date:
where_stmt += " AND strftime('%s', finish_time_str) < strftime('%s', :t)"
args.update({"t": to_date})
rows = db.session.execute(sqlalchemy.text("SELECT * FROM segment_result %s" % where_stmt), args).mappings()
rows = db.session.execute(sqlalchemy.text("SELECT * FROM segment_result %s ORDER BY elapsed_ms LIMIT 100" % where_stmt), args).mappings()
for row in rows:
result = results.segment_results.add()
row_to_protobuf(row, result, ['server_realm', 'course_id', 'segment_id', 'event_subgroup_id', 'finish_time_str', 'f14', 'time', 'player_type', 'f22', 'f23'])
Expand Down Expand Up @@ -3498,20 +3499,31 @@ def api_route_results_completion_stats_all():
return jsonify(response)


def add_segment_results(results, rows):
for row in rows:
result = results.segment_results.add()
row_to_protobuf(row, result, ['f14', 'time', 'player_type', 'f22'])
if ALL_TIME_LEADERBOARDS and result.world_time <= world_time() - 60 * 60 * 1000:
result.player_id += 100000 # avoid taking the jersey
result.world_time = world_time() # otherwise client filters it out

@app.route('/live-segment-results-service/leaders', methods=['GET'])
def live_segment_results_service_leaders():
results = segment_result_pb2.SegmentResults()
results.server_realm = 0
results.segment_id = 0
where_stmt = ""
args = {}
if not ALL_TIME_LEADERBOARDS:
where_stmt = "WHERE world_time > :w"
args = {"w": world_time() - 60 * 60 * 1000}
stmt = sqlalchemy.text("""SELECT s1.* FROM segment_result s1
JOIN (SELECT s.player_id, s.segment_id, MIN(s.elapsed_ms) AS min_time
FROM segment_result s WHERE world_time > :w GROUP BY s.player_id, s.segment_id) s2
ON s2.player_id = s1.player_id AND s2.min_time = s1.elapsed_ms
GROUP BY s1.player_id, s1.elapsed_ms ORDER BY s1.segment_id, s1.elapsed_ms LIMIT 1000""")
rows = db.session.execute(stmt, {"w": world_time()-60*60*1000}).mappings()
for row in rows:
result = results.segment_results.add()
row_to_protobuf(row, result, ['f14', 'time', 'player_type', 'f22'])
FROM segment_result s %s GROUP BY s.player_id, s.segment_id) s2
ON s2.player_id = s1.player_id AND s2.min_time = s1.elapsed_ms
GROUP BY s1.player_id, s1.elapsed_ms ORDER BY s1.segment_id, s1.elapsed_ms LIMIT 100""" % where_stmt)
rows = db.session.execute(stmt, args).mappings()
add_segment_results(results, rows)
return results.SerializeToString(), 200


Expand All @@ -3521,15 +3533,18 @@ def live_segment_results_service_leaderboard_segment_id(segment_id):
results = segment_result_pb2.SegmentResults()
results.server_realm = 0
results.segment_id = segment_id
where_stmt = "WHERE segment_id = :s"
args = {"s": segment_id}
if not ALL_TIME_LEADERBOARDS:
where_stmt += " AND world_time > :w"
args.update({"w": world_time() - 60 * 60 * 1000})
stmt = sqlalchemy.text("""SELECT s1.* FROM segment_result s1
JOIN (SELECT s.player_id, MIN(s.elapsed_ms) AS min_time
FROM segment_result s WHERE segment_id = :s AND world_time > :w GROUP BY s.player_id) s2
ON s2.player_id = s1.player_id AND s2.min_time = s1.elapsed_ms
GROUP BY s1.player_id, s1.elapsed_ms ORDER BY s1.elapsed_ms LIMIT 1000""")
rows = db.session.execute(stmt, {"s": segment_id, "w": world_time()-60*60*1000}).mappings()
for row in rows:
result = results.segment_results.add()
row_to_protobuf(row, result, ['f14', 'time', 'player_type', 'f22'])
FROM segment_result s %s GROUP BY s.player_id) s2
ON s2.player_id = s1.player_id AND s2.min_time = s1.elapsed_ms
GROUP BY s1.player_id, s1.elapsed_ms ORDER BY s1.elapsed_ms LIMIT 100""" % where_stmt)
rows = db.session.execute(stmt, args).mappings()
add_segment_results(results, rows)
return results.SerializeToString(), 200


Expand Down

0 comments on commit c6b45fa

Please sign in to comment.