Skip to content

Commit eba6689

Browse files
committed
ENHANCEMENTS:
- Competitive: /trueskill regenerate added
1 parent 0310817 commit eba6689

File tree

2 files changed

+84
-44
lines changed

2 files changed

+84
-44
lines changed

plugins/competitive/commands.py

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import psycopg
66

77
from core import Plugin, utils, get_translation, Node, Group, Report
8+
from datetime import datetime
89
from discord import app_commands
910
from plugins.competitive import rating
1011
from psycopg.rows import dict_row
@@ -27,52 +28,71 @@ async def install(self) -> bool:
2728
async def init_trueskill(self):
2829
# we need to calculate the TrueSkill values for players
2930
self.log.warning("Calculating TrueSkill values for players... Please do NOT stop your bot!")
30-
ratings: dict[str, Rating] = dict()
31-
async with self.apool.connection() as conn:
32-
async with conn.cursor(row_factory=dict_row) as cursor:
33-
size = 1000
34-
await cursor.execute("""
35-
SELECT p1.discord_id AS init_discord_id, m.init_id,
36-
p2.discord_id AS target_discord_id, m.target_id
37-
FROM missionstats m, players p1, players p2
38-
WHERE p1.ucid = m.init_id
39-
AND p2.ucid = m.target_id
40-
AND event = 'S_EVENT_KILL' AND init_id != '-1' AND target_id != '-1'
41-
AND init_side <> target_side
42-
AND init_cat = 'Airplanes' AND target_cat = 'Airplanes'
43-
ORDER BY id
44-
""")
45-
rows = await cursor.fetchmany(size=size)
46-
while len(rows) > 0:
47-
for row in rows:
48-
init_id = row['init_discord_id'] if row['init_discord_id'] != -1 else row['init_id']
49-
target_id = row['target_discord_id'] if row['target_discord_id'] != -1 else row['target_id']
50-
if init_id not in ratings:
51-
ratings[init_id] = rating.create_rating()
52-
if target_id not in ratings:
53-
ratings[target_id] = rating.create_rating()
54-
ratings[init_id], ratings[target_id] = rating.rate_1vs1(
55-
ratings[init_id], ratings[target_id])
56-
rows = await cursor.fetchmany(size=size)
57-
async with conn.transaction():
58-
for player_id, skill in ratings.items():
59-
if isinstance(player_id, str):
60-
await conn.execute("""
61-
INSERT INTO trueskill (player_ucid, skill_mu, skill_sigma)
62-
VALUES (%s, %s, %s)
63-
ON CONFLICT (player_ucid) DO UPDATE
64-
SET skill_mu = EXCLUDED.skill_mu, skill_sigma = EXCLUDED.skill_sigma
65-
""", (player_id, skill.mu, skill.sigma))
66-
else:
67-
cursor = await conn.execute("SELECT ucid FROM players WHERE discord_id = %s", (player_id, ))
31+
ratings: dict[str, Rating] = {}
32+
33+
batch_size = 10000 # missionstats rows per batch
34+
35+
last_id = 0
36+
total_processed = 0
37+
38+
while True:
39+
async with self.apool.connection() as conn:
40+
async with conn.transaction():
41+
async with conn.cursor(row_factory=dict_row) as cursor:
42+
await cursor.execute("""
43+
SELECT id, init_id, target_id, time
44+
FROM missionstats
45+
WHERE event = 'S_EVENT_KILL'
46+
AND init_id != '-1'
47+
AND target_id != '-1'
48+
AND init_side <> target_side
49+
AND init_cat = 'Airplanes'
50+
AND target_cat = 'Airplanes'
51+
AND id > %s
52+
ORDER BY id
53+
LIMIT %s
54+
""", (last_id, batch_size))
6855
rows = await cursor.fetchall()
56+
if not rows:
57+
break
58+
59+
# Compute rating changes and collect upserts for this batch
60+
insert_params: list[tuple[str, float, float, datetime]] = []
6961
for row in rows:
70-
await conn.execute("""
71-
INSERT INTO trueskill (player_ucid, skill_mu, skill_sigma)
72-
VALUES (%s, %s, %s)
62+
init_id = row['init_id']
63+
target_id = row['target_id']
64+
65+
if init_id not in ratings:
66+
ratings[init_id] = rating.create_rating()
67+
if target_id not in ratings:
68+
ratings[target_id] = rating.create_rating()
69+
70+
ratings[init_id], ratings[target_id] = rating.rate_1vs1(
71+
ratings[init_id], ratings[target_id]
72+
)
73+
74+
init_rating = ratings[init_id]
75+
target_rating = ratings[target_id]
76+
t = row['time']
77+
78+
insert_params.append((init_id, float(init_rating.mu), float(init_rating.sigma), t))
79+
insert_params.append((target_id, float(target_rating.mu), float(target_rating.sigma), t))
80+
81+
last_id = row['id']
82+
83+
if insert_params:
84+
await cursor.executemany("""
85+
INSERT INTO trueskill (player_ucid, skill_mu, skill_sigma, time)
86+
VALUES (%s, %s, %s, %s)
7387
ON CONFLICT (player_ucid) DO UPDATE
74-
SET skill_mu = EXCLUDED.skill_mu, skill_sigma = EXCLUDED.skill_sigma
75-
""", (row[0], skill.mu, skill.sigma))
88+
SET skill_mu = EXCLUDED.skill_mu,
89+
skill_sigma = EXCLUDED.skill_sigma,
90+
time = EXCLUDED.time
91+
""", insert_params)
92+
93+
total_processed += len(rows)
94+
self.log.info(f"- {total_processed} missionstats rows processed (up to id {last_id})")
95+
7696
self.log.info("TrueSkill values calculated.")
7797

7898
async def update_ucid(self, conn: psycopg.AsyncConnection, old_ucid: str, new_ucid: str) -> None:
@@ -259,6 +279,26 @@ async def delete(self, interaction: discord.Interaction,
259279
# noinspection PyUnresolvedReferences
260280
await interaction.followup.send(_("TrueSkill:tm: ratings deleted."), ephemeral=True)
261281

282+
@trueskill.command(description=_('Regenerate TrueSkill:tm: ratings'))
283+
@utils.app_has_role('Admin')
284+
@app_commands.guild_only()
285+
async def regenerate(self, interaction: discord.Interaction):
286+
if not await utils.yn_question(interaction,
287+
question=_("Do you really want to regenerate **all** TrueSkill:tm: ratings?"),
288+
message=_("This can take a while.")):
289+
await interaction.followup.send(_("Aborted."), ephemeral=True)
290+
return
291+
ephemeral = utils.get_ephemeral(interaction)
292+
async with self.apool.connection() as conn:
293+
async with conn.transaction():
294+
await conn.execute("TRUNCATE trueskill CASCADE")
295+
await conn.execute("TRUNCATE trueskill_hist CASCADE")
296+
await interaction.followup.send(_("TrueSkill:tm: ratings deleted.\nGenerating new values now ..."),
297+
ephemeral=ephemeral)
298+
channel = interaction.channel
299+
await self.init_trueskill()
300+
await channel.send(_("TrueSkill:tm: ratings regenerated."))
301+
262302

263303
async def setup(bot: DCSServerBot):
264304
await bot.add_cog(Competitive(bot, CompetitiveListener))

plugins/competitive/reports/trueskill_hist.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
"type": "SQLTable",
88
"params": {
9-
"sql": "SELECT ROUND(skill_mu, 2) AS \"TrueSkill μ\", ROUND(skill_sigma, 2) AS \"TrueSkill σ\" FROM trueskill WHERE player_ucid = '{ucid}'"
9+
"sql": "SELECT ROUND(skill_mu - 3 * skill_sigma, 2) AS \"Trueskill\", ROUND(skill_mu, 2) AS \"TrueSkill μ\", ROUND(skill_sigma, 2) AS \"TrueSkill σ\" FROM trueskill WHERE player_ucid = '{ucid}'"
1010
}
1111
},
1212
{

0 commit comments

Comments
 (0)