mirror of https://github.com/stijndcl/didier
337 lines
14 KiB
Python
337 lines
14 KiB
Python
# flake8: noqa: E800
|
|
import asyncio
|
|
import math
|
|
import random
|
|
import typing
|
|
from datetime import timedelta
|
|
|
|
import discord
|
|
from discord.ext import commands
|
|
|
|
import settings
|
|
from database.crud import currency as crud
|
|
from database.crud import users
|
|
from database.crud.jail import (
|
|
delete_prisoner_by_id,
|
|
get_jail_entry_by_id,
|
|
get_user_jail,
|
|
imprison,
|
|
)
|
|
from database.exceptions.currency import (
|
|
DoubleNightly,
|
|
NotEnoughDinks,
|
|
SavingsCapExceeded,
|
|
)
|
|
from database.utils.math.currency import (
|
|
capacity_upgrade_price,
|
|
interest_rate,
|
|
interest_upgrade_price,
|
|
jail_chance,
|
|
jail_time,
|
|
rob_amount,
|
|
rob_chance,
|
|
rob_upgrade_price,
|
|
savings_cap,
|
|
)
|
|
from didier import Didier
|
|
from didier.utils.discord import colours
|
|
from didier.utils.discord.checks import is_owner
|
|
from didier.utils.discord.converters import abbreviated_number
|
|
from didier.utils.timer import JailTimer
|
|
from didier.utils.types.datetime import tz_aware_now
|
|
from didier.utils.types.string import pluralize
|
|
|
|
|
|
class Currency(commands.Cog):
|
|
"""Everything Dinks-related."""
|
|
|
|
client: Didier
|
|
|
|
_jail_timer: JailTimer
|
|
_rob_lock: asyncio.Lock
|
|
|
|
def __init__(self, client: Didier):
|
|
super().__init__()
|
|
self.client = client
|
|
self._jail_timer = JailTimer(client)
|
|
self._rob_lock = asyncio.Lock()
|
|
|
|
@commands.command(name="award") # type: ignore[arg-type]
|
|
@commands.check(is_owner)
|
|
async def award(
|
|
self,
|
|
ctx: commands.Context,
|
|
user: discord.User,
|
|
amount: typing.Annotated[int, abbreviated_number],
|
|
):
|
|
"""Award a user `amount` Didier Dinks."""
|
|
async with self.client.postgres_session as session:
|
|
await crud.add_dinks(session, user.id, amount)
|
|
|
|
plural = pluralize("Didier Dink", amount)
|
|
await ctx.reply(
|
|
f"{ctx.author.display_name} has awarded **{user.display_name}** with **{amount}** {plural}.",
|
|
mention_author=False,
|
|
)
|
|
|
|
@commands.group(name="bank", aliases=["b"], case_insensitive=True, invoke_without_command=True)
|
|
async def bank(self, ctx: commands.Context):
|
|
"""Show your Didier Bank information."""
|
|
async with self.client.postgres_session as session:
|
|
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)
|
|
|
|
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)
|
|
|
|
@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):
|
|
"""List the upgrades you can buy & their prices."""
|
|
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)
|
|
|
|
@bank_upgrades.command(name="capacity", aliases=["c"]) # type: ignore[arg-type]
|
|
async def bank_upgrade_capacity(self, ctx: commands.Context):
|
|
"""Upgrade the capacity level of your bank."""
|
|
async with self.client.postgres_session as session:
|
|
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)
|
|
await self.client.reject_message(ctx.message)
|
|
|
|
@bank_upgrades.command(name="interest", aliases=["i"]) # type: ignore[arg-type]
|
|
async def bank_upgrade_interest(self, ctx: commands.Context):
|
|
"""Upgrade the interest level of your bank."""
|
|
async with self.client.postgres_session as session:
|
|
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)
|
|
await self.client.reject_message(ctx.message)
|
|
|
|
@bank_upgrades.command(name="rob", aliases=["r"]) # type: ignore[arg-type]
|
|
async def bank_upgrade_rob(self, ctx: commands.Context):
|
|
"""Upgrade the rob level of your bank."""
|
|
async with self.client.postgres_session as session:
|
|
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)
|
|
await self.client.reject_message(ctx.message)
|
|
|
|
@commands.hybrid_command(name="dinks") # type: ignore[arg-type]
|
|
async def dinks(self, ctx: commands.Context):
|
|
"""Check your Didier Dinks."""
|
|
async with ctx.typing(), self.client.postgres_session as session:
|
|
bank = await crud.get_bank(session, ctx.author.id)
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Example usage:
|
|
```
|
|
didier save all
|
|
didier save 500
|
|
didier save 25k
|
|
didier save 5.3b
|
|
```
|
|
"""
|
|
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:
|
|
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."
|
|
)
|
|
|
|
plural = pluralize("Didier Dink", saved)
|
|
|
|
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]
|
|
):
|
|
"""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)
|
|
|
|
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)
|
|
|
|
@commands.hybrid_command(name="nightly") # type: ignore[arg-type]
|
|
async def nightly(self, ctx: commands.Context):
|
|
"""Claim nightly Didier Dinks."""
|
|
async with ctx.typing(), self.client.postgres_session as session:
|
|
try:
|
|
await crud.claim_nightly(session, ctx.author.id)
|
|
await ctx.reply(
|
|
f"You've claimed your daily **{crud.NIGHTLY_AMOUNT}** Didier Dinks.", mention_author=False
|
|
)
|
|
except DoubleNightly:
|
|
await ctx.reply(
|
|
"You've already claimed your Didier Nightly today.", mention_author=False, ephemeral=True
|
|
)
|
|
|
|
@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
|
|
# This would lead to undefined behaviour
|
|
# Typing() must come first for slash commands
|
|
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
|
|
)
|
|
|
|
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
|
|
rob_roll = random.random()
|
|
success_chance = rob_chance(robber.rob_level)
|
|
success = rob_roll <= success_chance
|
|
max_rob_amount = math.floor(random.uniform(0.20, 1.0) * rob_amount(robber.rob_level))
|
|
robbed_amount = min(robbed.dinks, max_rob_amount)
|
|
|
|
if success:
|
|
await crud.rob(session, robbed_amount, ctx.author.id, member.id, robber_bank=robber, robbed_bank=robbed)
|
|
return await ctx.reply(
|
|
f"{ctx.author.display_name} has robbed **{robbed_amount}** Didier Dinks from {member.display_name}!",
|
|
mention_author=False,
|
|
)
|
|
|
|
# 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)
|
|
jail = await imprison(session, ctx.author.id, until)
|
|
self._jail_timer.maybe_replace_task(jail)
|
|
|
|
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.")
|
|
|
|
@commands.hybrid_command(name="jail")
|
|
async def jail(self, ctx: commands.Context):
|
|
"""Check how long you're still in jail for"""
|
|
async with ctx.typing():
|
|
async with self.client.postgres_session as session:
|
|
entry = await get_user_jail(session, ctx.author.id)
|
|
|
|
if entry is None:
|
|
embed = discord.Embed(
|
|
title="Didier Jail", colour=colours.error_red(), description="You're not currently in jail."
|
|
)
|
|
|
|
return await ctx.reply(embed=embed, mention_author=False, ephemeral=True)
|
|
|
|
embed = discord.Embed(
|
|
title="Didier Jail",
|
|
colour=colours.jail_gray(),
|
|
description=f"You will be released <t:{entry.until.timestamp()}:R>.",
|
|
)
|
|
|
|
return await ctx.reply(embed=embed, mention_author=False)
|
|
|
|
@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()
|
|
|
|
|
|
async def setup(client: Didier):
|
|
"""Load the cog"""
|
|
await client.add_cog(Currency(client))
|