309 lines
8.5 KiB
Python
309 lines
8.5 KiB
Python
import discord
|
||
from discord import app_commands
|
||
from db import get_db
|
||
from constants import VALORANT_RANKS, WORKOUTS, WORKOUT_CALORIES
|
||
from helpers import apply_rr
|
||
import random
|
||
|
||
def register(tree: app_commands.CommandTree):
|
||
|
||
# -------------------
|
||
# /create
|
||
# -------------------
|
||
@tree.command(name="create", description="Link your Valorant account")
|
||
@app_commands.describe(
|
||
val_tag="VALUSERNAME#TAG",
|
||
rank="Your current Valorant rank",
|
||
rr="Current RR (0-99)"
|
||
)
|
||
@app_commands.choices(
|
||
rank=[app_commands.Choice(name=r, value=r) for r in VALORANT_RANKS]
|
||
)
|
||
async def create(
|
||
interaction: discord.Interaction,
|
||
val_tag: str,
|
||
rank: str,
|
||
rr: int
|
||
):
|
||
db = get_db()
|
||
cur = db.cursor()
|
||
|
||
cur.execute("""
|
||
INSERT INTO users (discord_id, val_tag, rank, rr)
|
||
VALUES (%s, %s, %s, %s)
|
||
ON DUPLICATE KEY UPDATE
|
||
val_tag=VALUES(val_tag),
|
||
rank=VALUES(rank),
|
||
rr=VALUES(rr)
|
||
""", (str(interaction.user.id), val_tag, rank, rr))
|
||
|
||
cur.close()
|
||
db.close()
|
||
|
||
await interaction.response.send_message("✅ Account linked", ephemeral=True)
|
||
|
||
# -------------------
|
||
# /match
|
||
# -------------------
|
||
from constants import ENABLED_WORKOUTS
|
||
|
||
enabled_workouts = [
|
||
app_commands.Choice(name=name, value=name)
|
||
for name in ENABLED_WORKOUTS.keys()
|
||
]
|
||
|
||
@tree.command(name="match", description="Log a Valorant match")
|
||
@app_commands.describe(
|
||
result="Match result",
|
||
rr_change="RR gained or lost",
|
||
kd="Kills/Deaths (e.g. 20/15)",
|
||
workout="Workout assigned for deaths"
|
||
)
|
||
@app_commands.choices(
|
||
result=[
|
||
app_commands.Choice(name="WIN", value="WIN"),
|
||
app_commands.Choice(name="LOSS", value="LOSS")
|
||
],
|
||
workout=enabled_workouts
|
||
)
|
||
async def match(
|
||
interaction: discord.Interaction,
|
||
result: str,
|
||
rr_change: int,
|
||
kd: str,
|
||
workout: str
|
||
):
|
||
try:
|
||
kills, deaths = map(int, kd.split("/"))
|
||
except ValueError:
|
||
await interaction.response.send_message(
|
||
"❌ K/D must be in format `kills/deaths` (e.g. 20/15)",
|
||
ephemeral=True
|
||
)
|
||
return
|
||
|
||
db = get_db()
|
||
cur = db.cursor(dictionary=True)
|
||
|
||
cur.execute(
|
||
"SELECT * FROM users WHERE discord_id=%s",
|
||
(str(interaction.user.id),)
|
||
)
|
||
user = cur.fetchone()
|
||
|
||
if not user:
|
||
await interaction.response.send_message(
|
||
"❌ You must run `/create` first",
|
||
ephemeral=True
|
||
)
|
||
return
|
||
|
||
# Enforce RR direction based on result
|
||
rr_delta = abs(rr_change)
|
||
|
||
if result == "LOSS":
|
||
rr_delta *= -1
|
||
|
||
new_rank, new_rr = apply_rr(
|
||
user["rank"],
|
||
user["rr"],
|
||
rr_delta
|
||
)
|
||
|
||
per_death = ENABLED_WORKOUTS.get(workout, 0)
|
||
|
||
if per_death == 0:
|
||
await interaction.response.send_message(
|
||
"❌ That workout is currently disabled.",
|
||
ephemeral=True
|
||
)
|
||
return
|
||
|
||
# Base workout calculation
|
||
workout_total = deaths * per_death
|
||
|
||
# Loss penalty
|
||
if result == "LOSS":
|
||
workout_total += 10
|
||
|
||
bonus_text = " (+10 loss penalty)" if result == "LOSS" else ""
|
||
|
||
# Calculate calories burned
|
||
cal_per_rep = WORKOUT_CALORIES.get(workout, 0)
|
||
calories_burned = round(workout_total * cal_per_rep, 2)
|
||
|
||
await interaction.response.send_message(
|
||
f"🏆 Match logged\n"
|
||
f"**Result:** {result}\n"
|
||
f"**Rank:** {new_rank} ({new_rr} RR)\n"
|
||
f"**Workout:** {workout} × {workout_total}{bonus_text}\n"
|
||
f"**Calories:** {calories_burned:.1f} Cal"
|
||
)
|
||
|
||
cur.execute("""
|
||
UPDATE users
|
||
SET
|
||
kills = kills + %s,
|
||
deaths = deaths + %s,
|
||
rank = %s,
|
||
rr = %s,
|
||
calories_burned = calories_burned + %s
|
||
WHERE discord_id = %s
|
||
""", (kills, deaths, new_rank, new_rr, float(calories_burned), str(interaction.user.id)))
|
||
|
||
cur.execute("""
|
||
INSERT INTO workouts (discord_id, workout, amount)
|
||
VALUES (%s, %s, %s)
|
||
ON DUPLICATE KEY UPDATE amount = amount + %s
|
||
""", (str(interaction.user.id), workout, workout_total, workout_total))
|
||
|
||
cur.close()
|
||
db.close()
|
||
|
||
await interaction.response.send_message(
|
||
f"🏆 Match logged\n"
|
||
f"**Rank:** {new_rank} ({new_rr} RR)\n"
|
||
f"**Workout:** {workout} × {workout_total}"
|
||
)
|
||
|
||
# -------------------
|
||
# /stats
|
||
# -------------------
|
||
@tree.command(name="stats", description="Show Momentum stats")
|
||
async def stats(
|
||
interaction: discord.Interaction,
|
||
user: discord.User | None = None
|
||
):
|
||
target = user or interaction.user
|
||
|
||
db = get_db()
|
||
cur = db.cursor(dictionary=True)
|
||
|
||
cur.execute(
|
||
"SELECT * FROM users WHERE discord_id=%s",
|
||
(str(target.id),)
|
||
)
|
||
u = cur.fetchone()
|
||
|
||
if not u:
|
||
await interaction.response.send_message(
|
||
"❌ User not found",
|
||
ephemeral=True
|
||
)
|
||
return
|
||
|
||
cur.execute(
|
||
"SELECT workout, amount FROM workouts WHERE discord_id=%s",
|
||
(str(target.id),)
|
||
)
|
||
workout_rows = cur.fetchall()
|
||
|
||
embed = discord.Embed(
|
||
title=f"{target.display_name}'s Momentum Stats",
|
||
color=discord.Color.red()
|
||
)
|
||
|
||
embed.add_field(
|
||
name="Rank",
|
||
value=f"{u['rank']} ({u['rr']} RR)",
|
||
inline=False
|
||
)
|
||
embed.add_field(
|
||
name="Valorant",
|
||
value=u["val_tag"],
|
||
inline=False
|
||
)
|
||
embed.add_field(
|
||
name="Kills / Deaths",
|
||
value=f"{u['kills']} / {u['deaths']}",
|
||
inline=False
|
||
)
|
||
embed.add_field(
|
||
name="Calories Burned",
|
||
value=f"{u.get('calories_burned', 0):.1f} Cal",
|
||
inline=False
|
||
)
|
||
|
||
workout_text = (
|
||
"\n".join(f"{w['workout']}: {w['amount']}" for w in workout_rows)
|
||
if workout_rows else "None"
|
||
)
|
||
|
||
embed.add_field(
|
||
name="Workouts Completed",
|
||
value=workout_text,
|
||
inline=False
|
||
)
|
||
|
||
cur.close()
|
||
db.close()
|
||
|
||
await interaction.response.send_message(embed=embed)
|
||
|
||
# -------------------
|
||
# /workout
|
||
# -------------------
|
||
@tree.command(name="workout", description="Get a random workout")
|
||
async def workout(interaction: discord.Interaction):
|
||
enabled = [w for w, v in WORKOUTS.items() if v > 0]
|
||
choice = random.SystemRandom().choice(enabled)
|
||
|
||
await interaction.response.send_message(
|
||
f"💪 **{choice}**\n"
|
||
f"{WORKOUTS[choice]} per death"
|
||
)
|
||
|
||
# -------------------
|
||
# /edit (admin)
|
||
# -------------------
|
||
@tree.command(name="edit", description="Edit a user's stats (Admin)")
|
||
@app_commands.choices(
|
||
rank=[app_commands.Choice(name=r, value=r) for r in VALORANT_RANKS]
|
||
)
|
||
async def edit(
|
||
interaction: discord.Interaction,
|
||
user: discord.User,
|
||
rank: str | None = None,
|
||
rr: int | None = None,
|
||
val_tag: str | None = None
|
||
):
|
||
if not interaction.user.guild_permissions.administrator:
|
||
await interaction.response.send_message(
|
||
"❌ Admins only",
|
||
ephemeral=True
|
||
)
|
||
return
|
||
|
||
fields = []
|
||
values = []
|
||
|
||
if rank:
|
||
fields.append("rank=%s")
|
||
values.append(rank)
|
||
if rr is not None:
|
||
fields.append("rr=%s")
|
||
values.append(rr)
|
||
if val_tag:
|
||
fields.append("val_tag=%s")
|
||
values.append(val_tag)
|
||
|
||
if not fields:
|
||
await interaction.response.send_message(
|
||
"Nothing to update",
|
||
ephemeral=True
|
||
)
|
||
return
|
||
|
||
values.append(str(user.id))
|
||
|
||
db = get_db()
|
||
cur = db.cursor()
|
||
cur.execute(
|
||
f"UPDATE users SET {', '.join(fields)} WHERE discord_id=%s",
|
||
values
|
||
)
|
||
cur.close()
|
||
db.close()
|
||
|
||
await interaction.response.send_message("✏️ User updated")
|