Files
botmafieux/cogs/roulette.py
Mathéo Nebulo9 DESSAUVAGES d14ca4ab3e
Some checks failed
CD / Deploy to VPS (push) Has been cancelled
feat: Add Russian Roulette feature betwwen two players
2026-07-14 16:12:16 +02:00

212 lines
8.0 KiB
Python

import asyncio
import datetime
import random
import discord
from discord import option
from discord.ext import commands
class RouletteCog(commands.Cog):
def __init__(self, bot: discord.Bot):
self.bot = bot
self.cooldown = 60 * 3
self.active_games = {}
def _build_view(self, game: dict) -> discord.ui.View:
view = discord.ui.View(timeout=None)
if game["status"] == "pending":
async def accept_callback(interaction: discord.Interaction):
if interaction.user.id != game["target"].id:
await interaction.response.send_message(
"Only the challenged member can accept this duel.",
ephemeral=True,
)
return
game["status"] = "ready"
game["current_player"] = game["author"]
if game.get("message"):
await game["message"].edit(
content=(
f"{game['author'].mention} challenged {game['target'].mention} to a Russian roulette duel.\n"
f"{game['target'].mention} accepted. {game['current_player'].mention}, click Fire!"
),
view=self._build_view(game),
)
await interaction.response.defer()
async def decline_callback(interaction: discord.Interaction):
if interaction.user.id != game["target"].id:
await interaction.response.send_message(
"Only the challenged member can decline this duel.",
ephemeral=True,
)
return
game["status"] = "declined"
self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None)
if game.get("message"):
await game["message"].edit(
content=f"{game['target'].mention} declined the challenge.",
view=None,
)
await interaction.response.defer()
accept_button = discord.ui.Button(label="Accept", style=discord.ButtonStyle.green)
accept_button.callback = accept_callback
view.add_item(accept_button)
decline_button = discord.ui.Button(label="Decline", style=discord.ButtonStyle.red)
decline_button.callback = decline_callback
view.add_item(decline_button)
elif game["status"] in {"ready", "playing"}:
async def fire_callback(interaction: discord.Interaction):
if interaction.user.id != game["current_player"].id:
await interaction.response.send_message(
f"Wait for {game['current_player'].mention} to fire.",
ephemeral=True,
)
return
if interaction.user.id in game["fired"]:
await interaction.response.send_message(
"You already fired once. Only one shot per player is allowed.",
ephemeral=True,
)
return
game["fired"].add(interaction.user.id)
is_shot = random.randint(1, 6) == 1
if is_shot:
game["status"] = "finished"
self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None)
await self._mute_member(interaction.user, 60)
if game.get("message"):
await game["message"].edit(
content=(
f"💥 {interaction.user.mention} pulled the trigger and got shot.\n"
f"They were muted for 60 seconds."
),
view=None,
)
else:
if len(game["fired"]) >= 2:
game["status"] = "finished"
self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None)
if game.get("message"):
await game["message"].edit(
content=(
f"🔫 {interaction.user.mention} survived and both players have fired once.\n"
f"No one was shot. The duel ends in a draw."
),
view=None,
)
else:
game["status"] = "playing"
game["current_player"] = game["target"] if interaction.user.id == game["author"].id else game["author"]
if game.get("message"):
await game["message"].edit(
content=(
f"🔫 {interaction.user.mention} survived.\n"
f"Next turn: {game['current_player'].mention}"
),
view=self._build_view(game),
)
await interaction.response.defer()
fire_button = discord.ui.Button(label="Fire", style=discord.ButtonStyle.danger)
fire_button.callback = fire_callback
view.add_item(fire_button)
return view
async def _mute_member(self, member: discord.Member, seconds: int = 60):
until = discord.utils.utcnow() + datetime.timedelta(seconds=seconds)
try:
await member.timeout(until=until, reason="Russian roulette loss")
except AttributeError:
await member.edit(timed_out_until=until, reason="Russian roulette loss")
@commands.slash_command(
name="roulette",
description="Challenge other server members. The loser is getting muted for 1 minute",
)
@option(
name="target",
description="The member to challenge",
input_type=discord.Member,
required=True,
)
async def roulette(
self,
ctx: discord.ApplicationContext,
target: discord.Member,
):
author = ctx.author
self_test = author.id == target.id
if self_test:
target = author
game = {
"author": author,
"target": target,
"status": "pending",
"current_player": None,
"guild_id": ctx.guild_id,
"message": None,
"fired": set(),
}
self.active_games[(ctx.guild_id, author.id, target.id)] = game
await ctx.send_response(
f"{author.mention} challenged {target.mention} to a Russian roulette duel.\n"
f"The target has 60 seconds to accept or decline.",
view=self._build_view(game),
)
if self_test:
await ctx.send_followup(
"**Self-challenge enabled for testing.**\n"
"The game will start immediately and you can test the flow.",
ephemeral=True,
)
try:
game["message"] = await ctx.interaction.original_response()
except Exception:
game["message"] = None
async def timeout_handler():
try:
await asyncio.sleep(60)
except asyncio.CancelledError:
return
if game.get("status") == "pending":
game["status"] = "timeout"
self.active_games.pop((ctx.guild_id, author.id, target.id), None)
await self._mute_member(target, 60)
if game.get("message"):
await game["message"].edit(
content=f"{target.mention} did not respond in time and was muted for 60 seconds.",
view=None,
)
asyncio.create_task(timeout_handler())