Work on rob

feature/currency-improvements
stijndcl 2024-02-20 16:21:51 +01:00
parent d46a31a7be
commit a21eb51e6d
7 changed files with 203 additions and 8 deletions

View File

@ -47,6 +47,9 @@ async def invest(session: AsyncSession, user_id: int, amount: Union[str, int]) -
if amount == "all": if amount == "all":
amount = bank.dinks amount = bank.dinks
if bank.dinks <= 0:
return 0
# Don't allow investing more dinks than you own # Don't allow investing more dinks than you own
amount = min(bank.dinks, int(amount)) amount = min(bank.dinks, int(amount))
@ -158,11 +161,14 @@ async def upgrade_rob(session: AsyncSession, user_id: int) -> int:
async def gamble_dinks( async def gamble_dinks(
session: AsyncSession, user_id: int, amount: Union[str, int], payout_factor: int, won: bool session: AsyncSession, user_id: int, amount: Union[str, int], payout_factor: int, won: bool
) -> int: ) -> int:
"""Gamble some of your Dinks""" """Gamble some of your Didier Dinks"""
bank = await get_bank(session, user_id) bank = await get_bank(session, user_id)
if amount == "all": if amount == "all":
amount = bank.dinks amount = bank.dinks
if bank.dinks <= 0:
return 0
amount = min(int(amount), bank.dinks) amount = min(int(amount), bank.dinks)
sign = 1 if won else -1 sign = 1 if won else -1
@ -170,3 +176,16 @@ async def gamble_dinks(
await add_dinks(session, user_id, sign * amount * factor) await add_dinks(session, user_id, sign * amount * factor)
return amount * factor return amount * factor
async def rob(session: AsyncSession, amount: int, robber_id: int, robbed_id: int):
"""Rob another user's Didier Dinks"""
robber = await get_bank(session, robber_id)
robbed = await get_bank(session, robbed_id)
robber.dinks += amount
robbed.dinks -= amount
session.add(robber)
session.add(robbed)
await session.commit()

View File

@ -0,0 +1,28 @@
from datetime import datetime
from typing import Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.schemas import Jail
__all__ = ["get_jail", "get_user_jail", "imprison"]
async def get_jail(session: AsyncSession) -> list[Jail]:
"""Get the entire Didier Jail"""
statement = select(Jail)
return list((await session.execute(statement)).scalars().all())
async def get_user_jail(session: AsyncSession, user_id: int) -> Optional[Jail]:
"""Check how long a given user is still in jail for"""
statement = select(Jail).where(Jail.user_id == user_id)
return (await session.execute(statement)).scalar_one_or_none()
async def imprison(session: AsyncSession, user_id: int, until: datetime):
"""Put a user in Didier Jail"""
jail = Jail(user_id=user_id, until=until)
session.add(jail)
await session.commit()

View File

@ -23,6 +23,7 @@ __all__ = [
"Event", "Event",
"FreeGame", "FreeGame",
"GitHubLink", "GitHubLink",
"Jail",
"Link", "Link",
"MemeTemplate", "MemeTemplate",
"NightlyData", "NightlyData",
@ -199,6 +200,18 @@ class GitHubLink(Base):
user: Mapped[User] = relationship(back_populates="github_links", uselist=False, lazy="selectin") user: Mapped[User] = relationship(back_populates="github_links", uselist=False, lazy="selectin")
class Jail(Base):
"""A user sitting in Didier Jail"""
__tablename__ = "jail"
jail_entry_i: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
until: Mapped[datetime] = mapped_column(nullable=False)
user: Mapped[User] = relationship(back_populates="jail", uselist=False, lazy="selectin")
class Link(Base): class Link(Base):
"""Useful links that go useful places""" """Useful links that go useful places"""
@ -328,6 +341,9 @@ class User(Base):
github_links: Mapped[List[GitHubLink]] = relationship( github_links: Mapped[List[GitHubLink]] = relationship(
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan" back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
) )
jail: Mapped[Optional[Jail]] = relationship(
back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan"
)
nightly_data: Mapped[NightlyData] = relationship( nightly_data: Mapped[NightlyData] = relationship(
back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan" back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan"
) )

View File

@ -1,6 +1,14 @@
import math import math
__all__ = ["capacity_upgrade_price", "interest_upgrade_price", "rob_upgrade_price"] __all__ = [
"capacity_upgrade_price",
"interest_upgrade_price",
"rob_upgrade_price",
"jail_chance",
"jail_time",
"rob_amount",
"rob_chance",
]
def interest_upgrade_price(level: int) -> int: def interest_upgrade_price(level: int) -> int:
@ -25,3 +33,34 @@ def rob_upgrade_price(level: int) -> int:
growth_rate = 1.9 growth_rate = 1.9
return math.floor(base_cost * (growth_rate**level)) return math.floor(base_cost * (growth_rate**level))
def jail_chance(level: int) -> float:
"""Calculate the chance that you'll end up in jail"""
base_chance = 0.35
growth_rate = 1.15
return max(0.0, base_chance - (growth_rate**level))
def jail_time(level: int) -> int:
"""Calculate the time in hours you'll have to spend in jail"""
base_hours = 2
growth_rate = 1.27
return math.floor(base_hours + growth_rate**level)
def rob_amount(level: int) -> int:
"""Calculate the maximum amount of Didier Dinks that you can rob"""
base_amount = 250
growth_rate = 1.4
return math.floor(base_amount * (growth_rate**level))
def rob_chance(level: int) -> float:
"""Calculate the chances of a robbing attempt being successful"""
base_chance = 0.25
return base_chance + 2.1 * level

View File

@ -1,17 +1,25 @@
# flake8: noqa: E800 # flake8: noqa: E800
import asyncio
import math
import random
import typing import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
import settings
from database.crud import currency as crud from database.crud import currency as crud
from database.crud.jail import get_user_jail
from database.exceptions.currency import DoubleNightly, NotEnoughDinks from database.exceptions.currency import DoubleNightly, NotEnoughDinks
from database.utils.math.currency import ( from database.utils.math.currency import (
capacity_upgrade_price, capacity_upgrade_price,
interest_upgrade_price, interest_upgrade_price,
rob_amount,
rob_chance,
rob_upgrade_price, rob_upgrade_price,
) )
from didier import Didier from didier import Didier
from didier.utils.discord import colours
from didier.utils.discord.checks import is_owner from didier.utils.discord.checks import is_owner
from didier.utils.discord.converters import abbreviated_number from didier.utils.discord.converters import abbreviated_number
from didier.utils.types.string import pluralize from didier.utils.types.string import pluralize
@ -21,10 +29,12 @@ class Currency(commands.Cog):
"""Everything Dinks-related.""" """Everything Dinks-related."""
client: Didier client: Didier
_rob_lock: asyncio.Lock
def __init__(self, client: Didier): def __init__(self, client: Didier):
super().__init__() super().__init__()
self.client = client self.client = client
self._rob_lock = asyncio.Lock()
@commands.command(name="award") # type: ignore[arg-type] @commands.command(name="award") # type: ignore[arg-type]
@commands.check(is_owner) @commands.check(is_owner)
@ -61,9 +71,9 @@ class Currency(commands.Cog):
await ctx.reply(embed=embed, mention_author=False) await ctx.reply(embed=embed, mention_author=False)
# @bank.group( # type: ignore[arg-type] @bank.group( # type: ignore[arg-type]
# name="upgrade", aliases=["u", "upgrades"], case_insensitive=True, invoke_without_command=True name="upgrade", aliases=["u", "upgrades"], case_insensitive=True, invoke_without_command=True
# ) )
async def bank_upgrades(self, ctx: commands.Context): async def bank_upgrades(self, ctx: commands.Context):
"""List the upgrades you can buy & their prices.""" """List the upgrades you can buy & their prices."""
async with self.client.postgres_session as session: async with self.client.postgres_session as session:
@ -83,7 +93,7 @@ class Currency(commands.Cog):
await ctx.reply(embed=embed, mention_author=False) await ctx.reply(embed=embed, mention_author=False)
# @bank_upgrades.command(name="capacity", aliases=["c"]) # type: ignore[arg-type] @bank_upgrades.command(name="capacity", aliases=["c"]) # type: ignore[arg-type]
async def bank_upgrade_capacity(self, ctx: commands.Context): async def bank_upgrade_capacity(self, ctx: commands.Context):
"""Upgrade the capacity level of your bank.""" """Upgrade the capacity level of your bank."""
async with self.client.postgres_session as session: async with self.client.postgres_session as session:
@ -94,7 +104,7 @@ class Currency(commands.Cog):
await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False) await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False)
await self.client.reject_message(ctx.message) await self.client.reject_message(ctx.message)
# @bank_upgrades.command(name="interest", aliases=["i"]) # type: ignore[arg-type] @bank_upgrades.command(name="interest", aliases=["i"]) # type: ignore[arg-type]
async def bank_upgrade_interest(self, ctx: commands.Context): async def bank_upgrade_interest(self, ctx: commands.Context):
"""Upgrade the interest level of your bank.""" """Upgrade the interest level of your bank."""
async with self.client.postgres_session as session: async with self.client.postgres_session as session:
@ -105,7 +115,7 @@ class Currency(commands.Cog):
await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False) await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False)
await self.client.reject_message(ctx.message) await self.client.reject_message(ctx.message)
# @bank_upgrades.command(name="rob", aliases=["r"]) # type: ignore[arg-type] @bank_upgrades.command(name="rob", aliases=["r"]) # type: ignore[arg-type]
async def bank_upgrade_rob(self, ctx: commands.Context): async def bank_upgrade_rob(self, ctx: commands.Context):
"""Upgrade the rob level of your bank.""" """Upgrade the rob level of your bank."""
async with self.client.postgres_session as session: async with self.client.postgres_session as session:
@ -182,6 +192,70 @@ class Currency(commands.Cog):
"You've already claimed your Didier Nightly today.", mention_author=False, ephemeral=True "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 cause undefined behaviour
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
)
rob_roll = random.random()
success_chance = rob_chance(robber.rob_level)
success = rob_roll <= success_chance
if success:
max_rob_amount = random.uniform(0.15, 1.0) * rob_amount(robber.rob_level)
robbed_amount = min(robbed.dinks, math.floor(max_rob_amount))
await crud.rob(session, robbed_amount, ctx.author.id, member.id)
return await ctx.reply(
f"{ctx.author.display_name} has robbed **{robbed_amount}** Didier Dinks from {member.display_name}!",
mention_author=False,
)
@commands.hybrid_command(name="jail")
async def jail(self, ctx: commands.Context):
"""Check how long you're still in jail for"""
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)
async def setup(client: Didier): async def setup(client: Didier):
"""Load the cog""" """Load the cog"""

View File

@ -310,6 +310,14 @@ class Didier(commands.Bot):
): ):
return await ctx.reply(str(exception.original), mention_author=False) return await ctx.reply(str(exception.original), mention_author=False)
if isinstance(exception, (discord.app_commands.CommandOnCooldown, commands.CommandOnCooldown)):
return await ctx.reply(
f"You have to wait another {exception.retry_after} seconds before "
f"attempting to use this command again.",
ephemeral=True,
delete_after=10,
)
if isinstance(exception, commands.MessageNotFound): if isinstance(exception, commands.MessageNotFound):
return await ctx.reply("This message could not be found.", ephemeral=True, delete_after=10) return await ctx.reply("This message could not be found.", ephemeral=True, delete_after=10)
@ -332,6 +340,12 @@ class Didier(commands.Bot):
): ):
return await ctx.reply("Invalid arguments.", ephemeral=True, delete_after=10) return await ctx.reply("Invalid arguments.", ephemeral=True, delete_after=10)
if isinstance(exception, commands.NoPrivateMessage):
if ctx.command.name == "rob":
return await ctx.reply("Robbing in DM? That's low, even for you.", ephemeral=True)
return await ctx.reply("This command cannot be used in DMs.", ephemeral=True)
# Print everything that we care about to the logs/stderr # Print everything that we care about to the logs/stderr
await super().on_command_error(ctx, exception) await super().on_command_error(ctx, exception)

View File

@ -7,6 +7,7 @@ __all__ = [
"ghent_university_blue", "ghent_university_blue",
"ghent_university_yellow", "ghent_university_yellow",
"google_blue", "google_blue",
"jail_gray",
"steam_blue", "steam_blue",
"urban_dictionary_green", "urban_dictionary_green",
"xkcd_blue", "xkcd_blue",
@ -41,6 +42,10 @@ def google_blue() -> discord.Colour:
return discord.Colour.from_rgb(66, 133, 244) return discord.Colour.from_rgb(66, 133, 244)
def jail_gray() -> discord.Colour:
return discord.Colour.from_rgb(85, 85, 85)
def steam_blue() -> discord.Colour: def steam_blue() -> discord.Colour:
return discord.Colour.from_rgb(102, 192, 244) return discord.Colour.from_rgb(102, 192, 244)