Skip to content

Commit e9d20c4

Browse files
committed
feat: auto-clear vote after 10 min match inactivity
1 parent f76685f commit e9d20c4

1 file changed

Lines changed: 95 additions & 1 deletion

File tree

cogs/ranked.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ def __init__(self, game, alliance_size: int, api_short: str, full_game_name: str
235235
self.password_channel_id = None # type: int | None
236236
self.elo_history: list = []
237237
self.game_scores: list = []
238+
self.clear_vote_task: Optional[asyncio.Task] = None
238239

239240
try:
240241
self.game_icon = game_logos[game]
@@ -335,6 +336,49 @@ async def on_timeout(self):
335336
self.stop()
336337

337338

339+
class ClearVoteView(View):
340+
def __init__(self, ranked_cog, guild: discord.Guild, match: XrcGame):
341+
super().__init__(timeout=120)
342+
self.ranked_cog = ranked_cog
343+
self.guild = guild
344+
self.match = match
345+
self.votes: set = set()
346+
self.total_players = len(match.game.red | match.game.blue)
347+
self.cleared = False
348+
self.message: Optional[discord.Message] = None
349+
350+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
351+
if self.match.red_role in interaction.user.roles or self.match.blue_role in interaction.user.roles:
352+
return True
353+
await interaction.response.send_message("You're not in this match.", ephemeral=True)
354+
return False
355+
356+
@discord.ui.button(label="Vote to Clear", style=ButtonStyle.red)
357+
async def vote_clear(self, interaction: discord.Interaction, button: discord.ui.Button):
358+
if interaction.user.id in self.votes:
359+
await interaction.response.send_message("You already voted to clear.", ephemeral=True)
360+
return
361+
self.votes.add(interaction.user.id)
362+
needed = self.total_players // 2 + 1
363+
if len(self.votes) >= needed:
364+
self.cleared = True
365+
self.stop()
366+
await interaction.response.send_message(
367+
f"Match cleared by vote ({len(self.votes)}/{self.total_players}).")
368+
await self.ranked_cog.do_clear_match(self.guild, self.match)
369+
else:
370+
await interaction.response.send_message(
371+
f"Vote recorded ({len(self.votes)}/{self.total_players}, need {needed} to clear).",
372+
ephemeral=True)
373+
374+
async def on_timeout(self):
375+
if not self.cleared and self.message:
376+
try:
377+
await self.message.edit(content="Auto-clear vote expired.", view=None)
378+
except Exception:
379+
pass
380+
381+
338382
async def remove_roles(guild: discord.Guild, qdata: XrcGame):
339383
for role in [qdata.red_role, qdata.blue_role]:
340384
if role:
@@ -1023,7 +1067,49 @@ async def move_player(player, channel):
10231067

10241068
asyncio.create_task(self.update_ranked_display())
10251069

1070+
if match.clear_vote_task and not match.clear_vote_task.done():
1071+
match.clear_vote_task.cancel()
1072+
match.clear_vote_task = asyncio.create_task(self._auto_clear_check(match, ctx.guild))
1073+
1074+
async def _auto_clear_check(self, match: XrcGame, guild: discord.Guild):
1075+
await asyncio.sleep(600)
1076+
1077+
is_active = any(match in queue.matches for queue in game_queues.values())
1078+
if not is_active or match.red_series >= 2 or match.blue_series >= 2:
1079+
return
1080+
1081+
if not match.password_channel_id:
1082+
return
1083+
password_channel = guild.get_channel(match.password_channel_id)
1084+
if not password_channel:
1085+
return
1086+
1087+
total = len(match.game.red | match.game.blue)
1088+
needed = total // 2 + 1
1089+
embed = discord.Embed(
1090+
title="Match Inactivity",
1091+
description=(
1092+
f"This match has been inactive for 10 minutes.\n"
1093+
f"Vote to clear if the match is not happening.\n"
1094+
f"**{needed}/{total}** votes needed to clear."
1095+
),
1096+
color=discord.Color.orange()
1097+
)
1098+
view = ClearVoteView(self, guild, match)
1099+
try:
1100+
msg = await password_channel.send(
1101+
f"{match.red_role.mention} {match.blue_role.mention}",
1102+
embed=embed,
1103+
view=view
1104+
)
1105+
view.message = msg
1106+
except Exception as e:
1107+
logger.error(f"Error sending auto-clear vote for {match.full_game_name}: {e}")
1108+
10261109
async def do_clear_match(self, guild: discord.Guild, match: XrcGame):
1110+
if match.clear_vote_task and not match.clear_vote_task.done():
1111+
match.clear_vote_task.cancel()
1112+
10271113
if match.server_port:
10281114
server_actions = self.bot.get_cog('ServerActions')
10291115
server_actions.stop_server_process(match.server_port)
@@ -1689,6 +1775,12 @@ async def submit(self, interaction: discord.Interaction, red_score: int, blue_sc
16891775
embed=summary_embed
16901776
)
16911777
await self.handle_game_end(interaction, qdata, current_match, embed)
1778+
else:
1779+
if current_match.clear_vote_task and not current_match.clear_vote_task.done():
1780+
current_match.clear_vote_task.cancel()
1781+
current_match.clear_vote_task = asyncio.create_task(
1782+
self._auto_clear_check(current_match, interaction.guild)
1783+
)
16921784

16931785
# Helper methods
16941786
def find_current_match(self, user_roles):
@@ -1793,7 +1885,9 @@ def fmt(names, totals):
17931885
return embed
17941886

17951887
async def handle_game_end(self, interaction, qdata, current_match, embed):
1796-
# Get lobby channel
1888+
if current_match.clear_vote_task and not current_match.clear_vote_task.done():
1889+
current_match.clear_vote_task.cancel()
1890+
17971891
lobby = self.bot.get_channel(LOBBY_VC_ID)
17981892

17991893
# Move all members to lobby, then delete voice channels

0 commit comments

Comments
 (0)