Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 42 additions & 44 deletions database/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ async def init_default_quests(default_quests_list):
"reward_exp": q[3],
"target_count": q[4],
"quest_type": q[5],
"quest_category": q[6],
"is_active": True,
}
},
Expand All @@ -696,85 +697,75 @@ async def init_default_quests(default_quests_list):
print("✅ Default Quests synced to MongoDB")


async def get_active_quest(user_id: str, q_type: str):
"""Retrieves the user's current active quest status."""
async def get_active_quest(user_id: str, q_key: str):
"""Retrieves the user's current active quest status.

q_key is one of: daily_message, weekly_message, daily_megabox, weekly_megabox
"""
if db is None:
return None

user_q = await db.user_quests.find_one({"_id": user_id})
if not user_q:
return None

quest_entry = user_q.get(q_type)
quest_entry = user_q.get(q_key)
if not quest_entry:
return None

# Check expiration (Daily vs Weekly)
period = q_key.split("_")[0] # "daily" or "weekly"
now = datetime.utcnow()
stored_date = quest_entry.get("date_assigned")

# Safety check if date is missing
if not stored_date:
return None

is_expired = False
if q_type == "daily":
# Expired if the stored date is NOT today
if period == "daily":
if stored_date.date() != now.date():
is_expired = True
elif q_type == "weekly":
# Expired if the stored week number is NOT this week
elif period == "weekly":
if stored_date.isocalendar()[1] != now.isocalendar()[1]:
is_expired = True

if is_expired:
return None # Time for a new one!
return None

# FIX: Return the quest even if it's completed, so we don't assign a new one today.
return quest_entry


async def assign_random_quest(user_id: str, q_type: str):
"""Picks a random active quest from the DB and assigns it to the user."""
async def assign_random_quest(user_id: str, q_key: str):
"""Picks a random active quest from the DB and assigns it to the user.

q_key is one of: daily_message, weekly_message, daily_megabox, weekly_megabox
"""
if db is None:
return None

# 1. Simplified Query: Just look for the type (ignores is_active type mismatch)
# We also fetch EVERYTHING to see what's actually in there.
period, category = q_key.split("_", 1) # e.g. "daily", "message"

cursor = db.quests.find({})
all_quests = await cursor.to_list(length=None)

# Filter in Python to be safe (handles "daily" vs "Daily" and 1 vs True)
matching_quests = []
for q in all_quests:
# Check 'quest_type' (or 'type' if legacy)
db_type = q.get("quest_type", q.get("type", "unknown"))

if str(db_type).lower() == q_type.lower():
matching_quests.append(q)
matching_quests = [
q
for q in all_quests
if str(q.get("quest_type", "")).lower() == period
and str(q.get("quest_category", "")).lower() == category
]

# 2. Debugging Output if empty
if not matching_quests:
print(f"⚠️ No matching '{q_type}' quests found!")
print(f" └─ Total Quests in DB: {len(all_quests)}")
if all_quests:
print(f" └─ Example Quest Keys: {list(all_quests[0].keys())}")
print(
f" └─ Example Quest Type: {all_quests[0].get('quest_type', 'MISSING')}"
)
print(f"⚠️ No matching quests found for key '{q_key}'")
return None

# 3. Pick one randomly
import random

quest = random.choice(matching_quests)

# 4. Create the new user entry
new_entry = {
"quest_id": quest["_id"],
"name": quest["name"],
"description": quest["description"],
# Handle 'target' vs 'target_count' mismatch safely
"target_count": quest.get("target_count", quest.get("target", 100)),
"reward_tokens": quest.get("reward_tokens", 0),
"reward_exp": quest.get("reward_exp", 0),
Expand All @@ -783,41 +774,48 @@ async def assign_random_quest(user_id: str, q_type: str):
"date_assigned": datetime.utcnow(),
}

# 5. Save to user_quests
await db.user_quests.update_one(
{"_id": user_id}, {"$set": {q_type: new_entry}}, upsert=True
{"_id": user_id}, {"$set": {q_key: new_entry}}, upsert=True
)

return new_entry


async def update_quest_progress(user_id: str, q_type: str, amount: int = 1):
"""Increments progress and checks for completion."""
async def reset_user_quests(user_id: str):
"""Deletes a user's quest assignments so they get freshly assigned on next /quests."""
if db is None:
return
await db.user_quests.delete_one({"_id": user_id})


async def update_quest_progress(user_id: str, q_key: str, amount: int = 1):
"""Increments progress and checks for completion.

q_key is one of: daily_message, weekly_message, daily_megabox, weekly_megabox
"""
if db is None:
return False, None

user_q = await db.user_quests.find_one({"_id": user_id})
if not user_q or q_type not in user_q:
if not user_q or q_key not in user_q:
return False, None

quest = user_q[q_type]
quest = user_q[q_key]
if quest["completed"]:
return False, None

new_progress = quest["progress"] + amount
target = quest["target_count"]

if new_progress >= target:
# Complete!
await db.user_quests.update_one(
{"_id": user_id},
{"$set": {f"{q_type}.progress": target, f"{q_type}.completed": True}},
{"$set": {f"{q_key}.progress": target, f"{q_key}.completed": True}},
)
return True, quest
else:
# Update
await db.user_quests.update_one(
{"_id": user_id}, {"$inc": {f"{q_type}.progress": amount}}
{"_id": user_id}, {"$inc": {f"{q_key}.progress": amount}}
)
return False, None

Expand Down
12 changes: 12 additions & 0 deletions features/brawl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,12 @@ async def megabox(self, interaction: discord.Interaction):
embed.set_footer(text=SUPERCELL_DISCLAIMER)
await interaction.followup.send(embed=embed)

quests_cog = self.bot.get_cog("Quests")
if quests_cog:
await quests_cog.process_quest_update(
user_id, interaction.channel, "megabox"
)

@app_commands.command(name="starrdrop", description="Open a random Starr Drop!")
async def starrdrop(self, interaction: discord.Interaction):
await interaction.response.defer()
Expand Down Expand Up @@ -729,6 +735,12 @@ async def starrdrop(self, interaction: discord.Interaction):
embed.set_footer(text=SUPERCELL_DISCLAIMER)
await interaction.followup.send(embed=embed)

quests_cog = self.bot.get_cog("Quests")
if quests_cog:
await quests_cog.process_quest_update(
user_id, interaction.channel, "megabox"
)

@app_commands.command(
name="brawlers", description="View your collection with levels"
)
Expand Down
106 changes: 80 additions & 26 deletions features/quests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
get_active_quest,
assign_random_quest,
update_quest_progress,
reset_user_quests,
get_user_balance,
update_user_balance,
get_leveling_data,
Expand All @@ -23,22 +24,59 @@

# --- DEFAULT QUESTS CONFIGURATION ---
DEFAULT_QUESTS = [
# Daily Quests
# Name | Desc | Tokens | XP | Target | Type
("Daily Chatter", "Send 80 messages today.", 50, 100, 80, "daily"),
("Quick Convo", "Send 160 messages today.", 115, 200, 160, "daily"),
("Engaged Today", "Send 240 messages today.", 250, 300, 240, "daily"),
# Weekly Quests
("Weekly Regular", "Send 500 messages this week.", 225, 1000, 500, "weekly"),
# Name | Desc | Tokens | XP | Target | Type | Category
# Daily Message Quests
("Daily Chatter", "Send 80 messages today.", 50, 100, 80, "daily", "message"),
("Quick Convo", "Send 160 messages today.", 115, 200, 160, "daily", "message"),
("Engaged Today", "Send 240 messages today.", 250, 300, 240, "daily", "message"),
# Daily Megabox Quests
(
"Mega Box Maniac",
"Open 100 Mega Boxes or Starr Drops today.",
50,
100,
100,
"daily",
"megabox",
),
# Weekly Message Quests
(
"Weekly Regular",
"Send 500 messages this week.",
225,
1000,
500,
"weekly",
"message",
),
(
"Consistent Contributor",
"Send 750 messages this week.",
400,
2000,
750,
"weekly",
"message",
),
(
"Server Pillar",
"Send 1000 messages this week.",
640,
3000,
1000,
"weekly",
"message",
),
# Weekly Megabox Quests
(
"Mega Box Grinder",
"Open 500 Mega Boxes or Starr Drops this week.",
250,
500,
500,
"weekly",
"megabox",
),
("Server Pillar", "Send 1000 messages this week.", 640, 3000, 1000, "weekly"),
]


Expand Down Expand Up @@ -67,26 +105,24 @@ async def cog_load(self):

async def process_quest_update(self, user_id, channel, action_type="message"):
"""Checks daily/weekly quests for progress."""
for q_type in ["daily", "weekly"]:
if action_type == "message":
q_keys = ["daily_message", "weekly_message"]
elif action_type == "megabox":
q_keys = ["daily_megabox", "weekly_megabox"]
else:
return

for q_key in q_keys:
# 1. Get or Assign Quest
quest = await get_active_quest(user_id, q_type)
quest = await get_active_quest(user_id, q_key)
if not quest:
quest = await assign_random_quest(user_id, q_type)
quest = await assign_random_quest(user_id, q_key)

if not quest:
continue

# 2. Filter: Only process message updates since invite quests are gone
if (
action_type == "message"
and "message" not in quest["description"].lower()
):
continue
if action_type == "invite":
continue # Explicitly ignore invites

# 3. Update Progress
completed, q_data = await update_quest_progress(user_id, q_type)
# 2. Update Progress
completed, q_data = await update_quest_progress(user_id, q_key)

# 4. Handle Completion
if completed and q_data:
Expand Down Expand Up @@ -172,12 +208,18 @@ async def quests(self, interaction: discord.Interaction):
color=discord.Color.blue(),
)

for q_type in ["daily", "weekly"]:
quest = await get_active_quest(user_id, q_type)
quest_slots = [
("daily_message", "Daily Message Quest"),
("weekly_message", "Weekly Message Quest"),
("daily_megabox", "Daily Megabox Quest"),
("weekly_megabox", "Weekly Megabox Quest"),
]

for q_key, title in quest_slots:
quest = await get_active_quest(user_id, q_key)
if not quest:
quest = await assign_random_quest(user_id, q_type)
quest = await assign_random_quest(user_id, q_key)

title = f"{q_type.capitalize()} Quest"
if quest:
# Progress Bar Logic
prog = quest.get("progress", 0)
Expand Down Expand Up @@ -206,6 +248,18 @@ async def quests(self, interaction: discord.Interaction):

await interaction.response.send_message(embed=embed)

@app_commands.command(
name="reset-quests", description="[STAFF] Reset a user's quest assignments."
)
@app_commands.default_permissions(administrator=True)
@app_commands.describe(user="The user whose quests to reset.")
async def resetquests(self, interaction: discord.Interaction, user: discord.Member):
await reset_user_quests(str(user.id))
await interaction.response.send_message(
f"✅ Quest assignments reset for {user.mention}. They'll get new quests on next `/quests`.",
ephemeral=True,
)


async def setup(bot):
await bot.add_cog(Quests(bot))
Loading