didier/didier/cogs/currency.py

337 lines
14 KiB
Python
Raw Normal View History

# flake8: noqa: E800
2024-02-20 16:21:51 +01:00
import asyncio
import math
import random
2022-07-01 14:05:00 +02:00
import typing
2024-02-20 18:20:41 +01:00
from datetime import timedelta
2022-07-01 14:05:00 +02:00
2022-06-30 21:17:48 +02:00
import discord
from discord.ext import commands
2024-02-20 16:21:51 +01:00
import settings
2022-06-30 21:17:48 +02:00
from database.crud import currency as crud
2024-03-01 14:18:58 +01:00
from database.crud import users
2024-03-04 00:48:17 +01:00
from database.crud.jail import (
delete_prisoner_by_id,
get_jail_entry_by_id,
get_user_jail,
imprison,
)
2024-03-01 14:18:58 +01:00
from database.exceptions.currency import (
DoubleNightly,
NotEnoughDinks,
SavingsCapExceeded,
)
2022-07-11 22:23:38 +02:00
from database.utils.math.currency import (
capacity_upgrade_price,
2024-03-01 14:18:58 +01:00
interest_rate,
2022-07-11 22:23:38 +02:00
interest_upgrade_price,
2024-02-20 18:20:41 +01:00
jail_chance,
jail_time,
2024-02-20 16:21:51 +01:00
rob_amount,
rob_chance,
2022-07-11 22:23:38 +02:00
rob_upgrade_price,
2024-03-01 14:18:58 +01:00
savings_cap,
2022-07-11 22:23:38 +02:00
)
2022-06-30 21:33:37 +02:00
from didier import Didier
2024-02-20 16:21:51 +01:00
from didier.utils.discord import colours
2022-06-30 21:17:48 +02:00
from didier.utils.discord.checks import is_owner
2022-07-01 14:05:00 +02:00
from didier.utils.discord.converters import abbreviated_number
2024-03-04 00:48:17 +01:00
from didier.utils.timer import JailTimer
2024-02-20 18:20:41 +01:00
from didier.utils.types.datetime import tz_aware_now
2022-06-30 21:17:48 +02:00
from didier.utils.types.string import pluralize
class Currency(commands.Cog):
2022-09-19 17:23:37 +02:00
"""Everything Dinks-related."""
2022-06-30 21:17:48 +02:00
client: Didier
2024-03-04 00:48:17 +01:00
_jail_timer: JailTimer
2024-02-20 16:21:51 +01:00
_rob_lock: asyncio.Lock
2022-06-30 21:17:48 +02:00
def __init__(self, client: Didier):
super().__init__()
self.client = client
2024-03-04 00:48:17 +01:00
self._jail_timer = JailTimer(client)
2024-02-20 16:21:51 +01:00
self._rob_lock = asyncio.Lock()
2022-06-30 21:17:48 +02:00
@commands.command(name="award") # type: ignore[arg-type]
2022-06-30 21:17:48 +02:00
@commands.check(is_owner)
2022-08-28 22:15:03 +02:00
async def award(
self,
ctx: commands.Context,
user: discord.User,
amount: typing.Annotated[int, abbreviated_number],
):
2022-09-19 17:23:37 +02:00
"""Award a user `amount` Didier Dinks."""
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
2022-06-30 21:17:48 +02:00
await crud.add_dinks(session, user.id, amount)
2024-03-01 14:18:58 +01:00
plural = pluralize("Didier Dink", amount)
await ctx.reply(
f"{ctx.author.display_name} has awarded **{user.display_name}** with **{amount}** {plural}.",
mention_author=False,
)
2022-06-30 21:17:48 +02:00
2022-09-19 01:28:18 +02:00
@commands.group(name="bank", aliases=["b"], case_insensitive=True, invoke_without_command=True)
2022-06-30 21:17:48 +02:00
async def bank(self, ctx: commands.Context):
2022-09-19 17:23:37 +02:00
"""Show your Didier Bank information."""
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
2024-03-01 14:18:58 +01:00
user = await users.get_or_add_user(session, ctx.author.id)
bank = user.bank
savings = user.savings
embed = discord.Embed(title="Bank of Didier", colour=discord.Colour.blue())
embed.set_author(name=ctx.author.display_name)
if ctx.author.avatar is not None:
embed.set_thumbnail(url=ctx.author.avatar.url)
2024-03-01 14:18:58 +01:00
embed.add_field(name="Interest rate", value=round(interest_rate(bank.interest_level), 2))
embed.add_field(name="Maximum capacity", value=round(savings_cap(bank.capacity_level), 2))
embed.add_field(name="Currently saved", value=savings.saved, inline=False)
embed.add_field(name="Daily minimum", value=savings.daily_minimum, inline=False)
await ctx.reply(embed=embed, mention_author=False)
2024-02-20 16:21:51 +01:00
@bank.group( # type: ignore[arg-type]
name="upgrade", aliases=["u", "upgrades"], case_insensitive=True, invoke_without_command=True
)
async def bank_upgrades(self, ctx: commands.Context):
2022-09-19 17:23:37 +02:00
"""List the upgrades you can buy & their prices."""
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
bank = await crud.get_bank(session, ctx.author.id)
embed = discord.Embed(title="Bank upgrades", colour=discord.Colour.blue())
embed.add_field(
name=f"Interest ({bank.interest_level})", value=str(interest_upgrade_price(bank.interest_level))
)
embed.add_field(
name=f"Capacity ({bank.capacity_level})", value=str(capacity_upgrade_price(bank.capacity_level))
)
embed.add_field(name=f"Rob ({bank.rob_level})", value=str(rob_upgrade_price(bank.rob_level)))
embed.set_footer(text="Didier Bank Upgrade [Category]")
await ctx.reply(embed=embed, mention_author=False)
2022-06-30 21:17:48 +02:00
2024-02-20 16:21:51 +01:00
@bank_upgrades.command(name="capacity", aliases=["c"]) # type: ignore[arg-type]
2022-07-03 17:44:16 +02:00
async def bank_upgrade_capacity(self, ctx: commands.Context):
2022-09-19 17:23:37 +02:00
"""Upgrade the capacity level of your bank."""
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
2022-07-03 17:44:16 +02:00
try:
await crud.upgrade_capacity(session, ctx.author.id)
await ctx.message.add_reaction("")
except NotEnoughDinks:
await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False)
2022-07-03 17:44:16 +02:00
await self.client.reject_message(ctx.message)
2024-02-20 16:21:51 +01:00
@bank_upgrades.command(name="interest", aliases=["i"]) # type: ignore[arg-type]
2022-07-03 17:44:16 +02:00
async def bank_upgrade_interest(self, ctx: commands.Context):
2022-09-19 17:23:37 +02:00
"""Upgrade the interest level of your bank."""
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
2022-07-03 17:44:16 +02:00
try:
await crud.upgrade_interest(session, ctx.author.id)
await ctx.message.add_reaction("")
except NotEnoughDinks:
await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False)
2022-07-03 17:44:16 +02:00
await self.client.reject_message(ctx.message)
2024-02-20 16:21:51 +01:00
@bank_upgrades.command(name="rob", aliases=["r"]) # type: ignore[arg-type]
2022-07-03 17:44:16 +02:00
async def bank_upgrade_rob(self, ctx: commands.Context):
2022-09-19 17:23:37 +02:00
"""Upgrade the rob level of your bank."""
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
2022-07-03 17:44:16 +02:00
try:
await crud.upgrade_rob(session, ctx.author.id)
await ctx.message.add_reaction("")
except NotEnoughDinks:
await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False)
2022-07-03 17:44:16 +02:00
await self.client.reject_message(ctx.message)
@commands.hybrid_command(name="dinks") # type: ignore[arg-type]
2022-06-30 21:17:48 +02:00
async def dinks(self, ctx: commands.Context):
2022-09-19 17:23:37 +02:00
"""Check your Didier Dinks."""
2024-02-20 18:20:41 +01:00
async with ctx.typing(), self.client.postgres_session as session:
2022-06-30 21:17:48 +02:00
bank = await crud.get_bank(session, ctx.author.id)
2024-03-01 14:18:58 +01:00
plural = pluralize("Didier Dink", bank.dinks)
await ctx.reply(f"You have **{bank.dinks}** {plural}.", mention_author=False)
@commands.command(name="save", aliases=["deposit", "dep", "s"]) # type: ignore[arg-type]
async def save(self, ctx: commands.Context, amount: typing.Annotated[typing.Union[str, int], abbreviated_number]):
"""Add `amount` Didier Dinks into your bank's savings account.
2022-09-19 15:59:12 +02:00
2022-09-19 17:42:51 +02:00
The `amount`-argument can take both raw numbers, and abbreviations of big numbers. Additionally, passing
`all` or `*` as the value will invest all of your Didier Dinks.
2022-09-19 15:59:12 +02:00
Example usage:
```
2024-03-01 14:18:58 +01:00
didier save all
didier save 500
didier save 25k
didier save 5.3b
2022-09-19 15:59:12 +02:00
```
"""
if isinstance(amount, int) and amount <= 0:
return await ctx.reply("Amount of Didier Dinks to invest must be a strictly positive integer.")
2022-07-25 20:33:20 +02:00
async with self.client.postgres_session as session:
2024-03-01 14:18:58 +01:00
try:
saved = await crud.save(session, ctx.author.id, amount)
except SavingsCapExceeded:
return await ctx.reply(
"You have already exceeded the savings cap for your level. Upgrade your bank's capacity to save "
"more."
)
2022-07-03 18:35:30 +02:00
2024-03-01 14:18:58 +01:00
plural = pluralize("Didier Dink", saved)
2024-03-01 14:18:58 +01:00
if saved == 0:
await ctx.reply("You don't have any Didier Dinks to invest.", mention_author=False)
else:
await ctx.reply(f"You have saved **{saved}** {plural}.", mention_author=False)
@commands.command(name="withdraw", aliases=["undeposit", "unsave", "w"]) # type: ignore[arg-type]
async def withdraw(
self, ctx: commands.Context, amount: typing.Annotated[typing.Union[str, int], abbreviated_number]
):
2024-03-01 14:18:58 +01:00
"""Withdraw some of your Didier Dinks from your bank's savings account."""
if isinstance(amount, int) and amount <= 0:
return await ctx.reply("Amount of Didier Dinks to invest must be a strictly positive integer.")
async with self.client.postgres_session as session:
withdrawn = await crud.withdraw(session, ctx.author.id, amount)
2024-03-01 14:18:58 +01:00
plural = pluralize("Didier Dink", withdrawn)
if withdrawn == 0:
await ctx.reply("You don't have any Didier Dinks to withdraw.", mention_author=False)
else:
await ctx.reply(f"You have withdrawn **{withdrawn}** {plural}.", mention_author=False)
2022-07-03 18:35:30 +02:00
@commands.hybrid_command(name="nightly") # type: ignore[arg-type]
2022-06-30 21:17:48 +02:00
async def nightly(self, ctx: commands.Context):
2022-09-19 15:59:12 +02:00
"""Claim nightly Didier Dinks."""
2024-02-20 18:20:41 +01:00
async with ctx.typing(), self.client.postgres_session as session:
2022-06-30 21:17:48 +02:00
try:
await crud.claim_nightly(session, ctx.author.id)
2024-02-12 22:55:35 +01:00
await ctx.reply(
f"You've claimed your daily **{crud.NIGHTLY_AMOUNT}** Didier Dinks.", mention_author=False
)
2022-06-30 21:17:48 +02:00
except DoubleNightly:
await ctx.reply(
"You've already claimed your Didier Nightly today.", mention_author=False, ephemeral=True
)
2022-06-30 21:17:48 +02:00
2024-02-20 16:21:51 +01:00
@commands.hybrid_command(name="rob") # type: ignore[arg-type]
@commands.cooldown(rate=1, per=10 * 60.0, type=commands.BucketType.user)
@commands.guild_only()
async def rob(self, ctx: commands.Context, member: discord.Member):
"""Attempt to rob another user of their Dinks"""
if member == ctx.author:
return await ctx.reply("You can't rob yourself.", mention_author=False, ephemeral=True)
if member == self.client.user:
return await ctx.reply(settings.DISCORD_BOOS_REACT, mention_author=False)
if member.bot:
return await ctx.reply("You can't rob bots.", mention_author=False, ephemeral=True)
# Use a Lock for robbing to avoid race conditions when robbing the same person twice
2024-02-20 18:20:41 +01:00
# This would lead to undefined behaviour
# Typing() must come first for slash commands
2024-02-20 16:21:51 +01:00
async with ctx.typing(), self._rob_lock, self.client.postgres_session as session:
robber = await crud.get_bank(session, ctx.author.id)
robbed = await crud.get_bank(session, member.id)
if robber.dinks <= 0:
return await ctx.reply(
"You can't rob without dinks. Just stop being poor lol", mention_author=False, ephemeral=True
)
if robbed.dinks <= 0:
return await ctx.reply(
f"{member.display_name} doesn't have any dinks to rob.", mention_author=False, ephemeral=True
)
2024-02-20 18:20:41 +01:00
jail = await get_user_jail(session, ctx.author.id)
if jail is not None:
return await ctx.reply("You can't rob when in jail.", mention_author=False, ephemeral=True)
# Here be RNG
2024-02-20 16:21:51 +01:00
rob_roll = random.random()
success_chance = rob_chance(robber.rob_level)
success = rob_roll <= success_chance
2024-02-20 18:20:41 +01:00
max_rob_amount = math.floor(random.uniform(0.20, 1.0) * rob_amount(robber.rob_level))
robbed_amount = min(robbed.dinks, max_rob_amount)
2024-02-20 16:21:51 +01:00
if success:
2024-02-20 18:20:41 +01:00
await crud.rob(session, robbed_amount, ctx.author.id, member.id, robber_bank=robber, robbed_bank=robbed)
2024-02-20 16:21:51 +01:00
return await ctx.reply(
f"{ctx.author.display_name} has robbed **{robbed_amount}** Didier Dinks from {member.display_name}!",
mention_author=False,
)
2024-02-20 18:20:41 +01:00
# Remove the amount of Dinks you would've stolen
# Increase the sentence if you can't afford it
lost_dinks = await crud.deduct_dinks(session, ctx.author.id, max_rob_amount, bank=robber)
couldnt_afford = lost_dinks < robbed_amount
punishment_factor = (float(max_rob_amount) / float(lost_dinks)) if couldnt_afford else 1.0
punishment_factor = min(punishment_factor, 2)
to_jail = couldnt_afford or random.random() <= jail_chance(robber.rob_level)
if to_jail:
jail_t = jail_time(robber.rob_level) * punishment_factor
until = tz_aware_now() + timedelta(hours=jail_t)
2024-03-04 00:48:17 +01:00
jail = await imprison(session, ctx.author.id, until)
self._jail_timer.maybe_replace_task(jail)
2024-02-20 18:20:41 +01:00
return await ctx.reply(
f"Robbery attempt failed! You've lost {lost_dinks} Didier Dinks, "
f"and have been sent to Didier Jail until <t:{until.timestamp()}:f>"
)
return await ctx.reply(f"Robbery attempt failed! You've lost {lost_dinks} Didier Dinks.")
2024-02-20 16:21:51 +01:00
@commands.hybrid_command(name="jail")
async def jail(self, ctx: commands.Context):
"""Check how long you're still in jail for"""
2024-02-20 18:20:41 +01:00
async with ctx.typing():
async with self.client.postgres_session as session:
entry = await get_user_jail(session, ctx.author.id)
2024-02-20 16:21:51 +01:00
2024-02-20 18:20:41 +01:00
if entry is None:
embed = discord.Embed(
title="Didier Jail", colour=colours.error_red(), description="You're not currently in jail."
)
2024-02-20 16:21:51 +01:00
2024-02-20 18:20:41 +01:00
return await ctx.reply(embed=embed, mention_author=False, ephemeral=True)
2024-02-20 16:21:51 +01:00
2024-02-20 18:20:41 +01:00
embed = discord.Embed(
title="Didier Jail",
colour=colours.jail_gray(),
description=f"You will be released <t:{entry.until.timestamp()}:R>.",
)
2024-02-20 16:21:51 +01:00
2024-02-20 18:20:41 +01:00
return await ctx.reply(embed=embed, mention_author=False)
2024-02-20 16:21:51 +01:00
2024-03-04 00:48:17 +01:00
@commands.Cog.listener()
async def on_jail_release(self, jail_id: int):
"""Custom listener called when a jail timer ends"""
async with self.client.postgres_session as session:
entry = await get_jail_entry_by_id(session, jail_id)
if entry is None:
return await self.client.log_error(f"Unable to find jail entry with id {jail_id}.", log_to_discord=True)
await delete_prisoner_by_id(session, jail_id)
await self._jail_timer.update()
2022-06-30 21:17:48 +02:00
async def setup(client: Didier):
2022-06-30 21:33:37 +02:00
"""Load the cog"""
2022-06-30 21:17:48 +02:00
await client.add_cog(Currency(client))