feat: Add Russian Roulette feature betwwen two players
Some checks failed
CD / Deploy to VPS (push) Has been cancelled
Some checks failed
CD / Deploy to VPS (push) Has been cancelled
This commit is contained in:
212
cogs/roulette.py
Normal file
212
cogs/roulette.py
Normal file
@@ -0,0 +1,212 @@
|
||||
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())
|
||||
Reference in New Issue
Block a user