[Bank] Allow bank managers to set the maximum allowed balance in the bank (#2926)

* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

Signed-off-by: Guy <guyreis96@gmail.com>

* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

Signed-off-by: Guy <guyreis96@gmail.com>

* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

Signed-off-by: Guy <guyreis96@gmail.com>

* Rename method 🤦

Signed-off-by: Guy <guyreis96@gmail.com>

* Updated this to be aware of #2925

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Addressed Flames review + Fixed 1 bug in errors.py + `[p]leaderboard` and `[p]bank balance` will set the users balance to max balance if the bank maxbal is lower than the previous user balance

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Missed this clarification

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* address Flames review

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
This commit is contained in:
Draper 2019-08-30 02:05:31 +01:00 committed by Michael H
parent b490942bcd
commit e04eed4a89
6 changed files with 159 additions and 26 deletions

View File

@ -0,0 +1 @@
Removed :cons:`bank.MAX_BALANCE`, use :meth:`bank.get_max_balance()` from now.

View File

@ -0,0 +1 @@
`[p]bankset maxbal` can be used to set the maximum bank balance.

View File

@ -86,11 +86,12 @@ class Bank(commands.Cog):
settings = _( settings = _(
"Bank settings:\n\nBank name: {bank_name}\nCurrency: {currency_name}\n" "Bank settings:\n\nBank name: {bank_name}\nCurrency: {currency_name}\n"
"Default balance: {default_balance}" "Default balance: {default_balance}\nMaximum allowed balance: {maximum_bal}"
).format( ).format(
bank_name=bank_name, bank_name=bank_name,
currency_name=currency_name, currency_name=currency_name,
default_balance=humanize_number(default_balance), default_balance=humanize_number(default_balance),
maximum_bal=humanize_number(await bank.get_max_balance(ctx.guild)),
) )
await ctx.send(box(settings)) await ctx.send(box(settings))
@ -130,4 +131,21 @@ class Bank(commands.Cog):
await bank.set_currency_name(name, ctx.guild) await bank.set_currency_name(name, ctx.guild)
await ctx.send(_("Currency name has been set to: {name}").format(name=name)) await ctx.send(_("Currency name has been set to: {name}").format(name=name))
@bankset.command(name="maxbal")
@check_global_setting_guildowner()
async def bankset_maxbal(self, ctx: commands.Context, *, amount: int):
"""Set the maximum balance a user can get."""
try:
await bank.set_max_balance(amount, ctx.guild)
except ValueError:
# noinspection PyProtectedMember
return await ctx.send(
_("Amount must be greater than zero and less than {max}.").format(
max=humanize_number(bank._MAX_BALANCE)
)
)
await ctx.send(
_("Maximum balance has been set to: {amount}").format(amount=humanize_number(amount))
)
# ENDSECTION # ENDSECTION

View File

@ -1,7 +1,7 @@
import calendar import calendar
import logging import logging
import random import random
from collections import defaultdict, deque from collections import defaultdict, deque, namedtuple
from enum import Enum from enum import Enum
from typing import cast, Iterable from typing import cast, Iterable
@ -20,6 +20,7 @@ T_ = Translator("Economy", __file__)
logger = logging.getLogger("red.economy") logger = logging.getLogger("red.economy")
NUM_ENC = "\N{COMBINING ENCLOSING KEYCAP}" NUM_ENC = "\N{COMBINING ENCLOSING KEYCAP}"
MOCK_MEMBER = namedtuple("Member", "id guild")
class SMReel(Enum): class SMReel(Enum):
@ -159,7 +160,10 @@ class Economy(commands.Cog):
bal = await bank.get_balance(user) bal = await bank.get_balance(user)
currency = await bank.get_currency_name(ctx.guild) currency = await bank.get_currency_name(ctx.guild)
max_bal = await bank.get_max_balance(ctx.guild)
if bal > max_bal:
bal = max_bal
await bank.set_balance(user, bal)
await ctx.send( await ctx.send(
_("{user}'s balance is {num} {currency}").format( _("{user}'s balance is {num} {currency}").format(
user=user.display_name, num=humanize_number(bal), currency=currency user=user.display_name, num=humanize_number(bal), currency=currency
@ -363,6 +367,7 @@ class Economy(commands.Cog):
""" """
guild = ctx.guild guild = ctx.guild
author = ctx.author author = ctx.author
max_bal = await bank.get_max_balance(ctx.guild)
if top < 1: if top < 1:
top = 10 top = 10
if await bank.is_global() and show_global: if await bank.is_global() and show_global:
@ -372,6 +377,9 @@ class Economy(commands.Cog):
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild) bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
try: try:
bal_len = len(humanize_number(bank_sorted[0][1]["balance"])) bal_len = len(humanize_number(bank_sorted[0][1]["balance"]))
bal_len_max = len(humanize_number(max_bal))
if bal_len > bal_len_max:
bal_len = bal_len_max
# first user is the largest we'll see # first user is the largest we'll see
except IndexError: except IndexError:
return await ctx.send(_("There are no accounts in the bank.")) return await ctx.send(_("There are no accounts in the bank."))
@ -394,8 +402,12 @@ class Economy(commands.Cog):
if await ctx.bot.is_owner(ctx.author): if await ctx.bot.is_owner(ctx.author):
user_id = f"({str(acc[0])})" user_id = f"({str(acc[0])})"
name = f"{acc[1]['name']} {user_id}" name = f"{acc[1]['name']} {user_id}"
balance = humanize_number(acc[1]["balance"])
balance = acc[1]["balance"]
if balance > max_bal:
balance = max_bal
await bank.set_balance(MOCK_MEMBER(acc[0], guild), balance)
balance = humanize_number(balance)
if acc[0] != author.id: if acc[0] != author.id:
temp_msg += ( temp_msg += (
f"{f'{humanize_number(pos)}.': <{pound_len+2}} " f"{f'{humanize_number(pos)}.': <{pound_len+2}} "
@ -560,7 +572,8 @@ class Economy(commands.Cog):
"Slot cooldown: {slot_time}\n" "Slot cooldown: {slot_time}\n"
"Payday amount: {payday_amount}\n" "Payday amount: {payday_amount}\n"
"Payday cooldown: {payday_time}\n" "Payday cooldown: {payday_time}\n"
"Amount given at account registration: {register_amount}" "Amount given at account registration: {register_amount}\n"
"Maximum allowed balance: {maximum_bal}"
).format( ).format(
slot_min=humanize_number(await conf.SLOT_MIN()), slot_min=humanize_number(await conf.SLOT_MIN()),
slot_max=humanize_number(await conf.SLOT_MAX()), slot_max=humanize_number(await conf.SLOT_MAX()),
@ -568,6 +581,7 @@ class Economy(commands.Cog):
payday_time=humanize_number(await conf.PAYDAY_TIME()), payday_time=humanize_number(await conf.PAYDAY_TIME()),
payday_amount=humanize_number(await conf.PAYDAY_CREDITS()), payday_amount=humanize_number(await conf.PAYDAY_CREDITS()),
register_amount=humanize_number(await bank.get_default_balance(guild)), register_amount=humanize_number(await bank.get_default_balance(guild)),
maximum_bal=humanize_number(await bank.get_max_balance(guild)),
) )
) )
) )
@ -639,9 +653,13 @@ class Economy(commands.Cog):
async def paydayamount(self, ctx: commands.Context, creds: int): async def paydayamount(self, ctx: commands.Context, creds: int):
"""Set the amount earned each payday.""" """Set the amount earned each payday."""
guild = ctx.guild guild = ctx.guild
if creds <= 0 or creds > bank.MAX_BALANCE: max_balance = await bank.get_max_balance(ctx.guild)
await ctx.send(_("Har har so funny.")) if creds <= 0 or creds > max_balance:
return return await ctx.send(
_("Amount must be greater than zero and less than {maxbal}.").format(
maxbal=humanize_number(max_balance)
)
)
credits_name = await bank.get_currency_name(guild) credits_name = await bank.get_currency_name(guild)
if await bank.is_global(): if await bank.is_global():
await self.config.PAYDAY_CREDITS.set(creds) await self.config.PAYDAY_CREDITS.set(creds)
@ -657,9 +675,13 @@ class Economy(commands.Cog):
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int): async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
"""Set the amount earned each payday for a role.""" """Set the amount earned each payday for a role."""
guild = ctx.guild guild = ctx.guild
if creds <= 0 or creds > bank.MAX_BALANCE: max_balance = await bank.get_max_balance(ctx.guild)
await ctx.send(_("Har har so funny.")) if creds <= 0 or creds > max_balance:
return return await ctx.send(
_("Amount must be greater than zero and less than {maxbal}.").format(
maxbal=humanize_number(max_balance)
)
)
credits_name = await bank.get_currency_name(guild) credits_name = await bank.get_currency_name(guild)
if await bank.is_global(): if await bank.is_global():
await ctx.send(_("The bank must be per-server for per-role paydays to work.")) await ctx.send(_("The bank must be per-server for per-role paydays to work."))
@ -676,10 +698,16 @@ class Economy(commands.Cog):
async def registeramount(self, ctx: commands.Context, creds: int): async def registeramount(self, ctx: commands.Context, creds: int):
"""Set the initial balance for new bank accounts.""" """Set the initial balance for new bank accounts."""
guild = ctx.guild guild = ctx.guild
if creds < 0: max_balance = await bank.get_max_balance(ctx.guild)
creds = 0
credits_name = await bank.get_currency_name(guild) credits_name = await bank.get_currency_name(guild)
await bank.set_default_balance(creds, guild) try:
await bank.set_default_balance(creds, guild)
except ValueError:
return await ctx.send(
_("Amount must be greater than or equal to zero and less than {maxbal}.").format(
maxbal=humanize_number(max_balance)
)
)
await ctx.send( await ctx.send(
_("Registering an account will now give {num} {currency}.").format( _("Registering an account will now give {num} {currency}.").format(
num=humanize_number(creds), currency=credits_name num=humanize_number(creds), currency=credits_name

View File

@ -12,7 +12,6 @@ from .i18n import Translator
_ = Translator("Bank API", __file__) _ = Translator("Bank API", __file__)
__all__ = [ __all__ = [
"MAX_BALANCE",
"Account", "Account",
"get_balance", "get_balance",
"set_balance", "set_balance",
@ -30,20 +29,28 @@ __all__ = [
"set_currency_name", "set_currency_name",
"get_default_balance", "get_default_balance",
"set_default_balance", "set_default_balance",
"get_max_balance",
"set_max_balance",
"cost", "cost",
"AbortPurchase", "AbortPurchase",
] ]
MAX_BALANCE = 2 ** 63 - 1 _MAX_BALANCE = 2 ** 63 - 1
_DEFAULT_GLOBAL = { _DEFAULT_GLOBAL = {
"is_global": False, "is_global": False,
"bank_name": "Twentysix bank", "bank_name": "Twentysix bank",
"currency": "credits", "currency": "credits",
"default_balance": 100, "default_balance": 100,
"max_balance": _MAX_BALANCE,
} }
_DEFAULT_GUILD = {"bank_name": "Twentysix bank", "currency": "credits", "default_balance": 100} _DEFAULT_GUILD = {
"bank_name": "Twentysix bank",
"currency": "credits",
"default_balance": 100,
"max_balance": _MAX_BALANCE,
}
_DEFAULT_MEMBER = {"name": "", "balance": 0, "created_at": 0} _DEFAULT_MEMBER = {"name": "", "balance": 0, "created_at": 0}
@ -186,10 +193,11 @@ async def set_balance(member: discord.Member, amount: int) -> int:
""" """
if amount < 0: if amount < 0:
raise ValueError("Not allowed to have negative balance.") raise ValueError("Not allowed to have negative balance.")
if amount > MAX_BALANCE: max_bal = await get_max_balance(member.guild)
if amount > max_bal:
currency = await get_currency_name(member.guild) currency = await get_currency_name(member.guild)
raise errors.BalanceTooHigh( raise errors.BalanceTooHigh(
user=member.display_name, max_balance=MAX_BALANCE, currency_name=currency user=member.display_name, max_balance=max_bal, currency_name=currency
) )
if await is_global(): if await is_global():
group = _conf.user(member) group = _conf.user(member)
@ -329,10 +337,12 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
) )
) )
if await get_balance(to) + amount > MAX_BALANCE: max_bal = await get_max_balance(to.guild)
if await get_balance(to) + amount > max_bal:
currency = await get_currency_name(to.guild) currency = await get_currency_name(to.guild)
raise errors.BalanceTooHigh( raise errors.BalanceTooHigh(
user=to.display_name, max_balance=MAX_BALANCE, currency_name=currency user=to.display_name, max_balance=max_bal, currency_name=currency
) )
await withdraw_credits(from_, amount) await withdraw_credits(from_, amount)
@ -635,6 +645,75 @@ async def set_currency_name(name: str, guild: discord.Guild = None) -> str:
return name return name
async def get_max_balance(guild: discord.Guild = None) -> int:
"""Get the max balance for the bank.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the max balance for (required if bank is
guild-specific).
Returns
-------
int
The maximum allowed balance.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if await is_global():
return await _conf.max_balance()
elif guild is not None:
return await _conf.guild(guild).max_balance()
else:
raise RuntimeError("Guild must be provided.")
async def set_max_balance(amount: int, guild: discord.Guild = None) -> int:
"""Set the maximum balance for the bank.
Parameters
----------
amount : int
The new maximum balance.
guild : `discord.Guild`, optional
The guild to set the max balance for (required if bank is
guild-specific).
Returns
-------
int
The new maximum balance.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
ValueError
If the amount is less than 0 or higher than 2 ** 63 - 1.
"""
if not (0 < amount <= _MAX_BALANCE):
raise ValueError(
"Amount must be greater than zero and less than {max}.".format(
max=humanize_number(_MAX_BALANCE, override_locale="en_US")
)
)
if await is_global():
await _conf.max_balance.set(amount)
elif guild is not None:
await _conf.guild(guild).max_balance.set(amount)
else:
raise RuntimeError(
"Guild must be provided if setting the maximum balance of a guild-specific bank."
)
return amount
async def get_default_balance(guild: discord.Guild = None) -> int: async def get_default_balance(guild: discord.Guild = None) -> int:
"""Get the current default balance amount. """Get the current default balance amount.
@ -684,12 +763,18 @@ async def set_default_balance(amount: int, guild: discord.Guild = None) -> int:
RuntimeError RuntimeError
If the bank is guild-specific and guild was not provided. If the bank is guild-specific and guild was not provided.
ValueError ValueError
If the amount is invalid. If the amount is less than 0 or higher than the max allowed balance.
""" """
amount = int(amount) amount = int(amount)
if amount < 0: max_bal = await get_max_balance(guild)
raise ValueError("Amount must be greater than zero.")
if not (0 < amount <= max_bal):
raise ValueError(
"Amount must be greater than zero and less than {max}.".format(
max=humanize_number(max_bal, override_locale="en_US")
)
)
if await is_global(): if await is_global():
await _conf.default_balance.set(amount) await _conf.default_balance.set(amount)
@ -714,7 +799,7 @@ def cost(amount: int):
You can intentionally refund by raising `AbortPurchase` You can intentionally refund by raising `AbortPurchase`
(this error will be consumed and not show to users) (this error will be consumed and not show to users)
Other exceptions will propogate and will be handled by Red's (and/or Other exceptions will propagate and will be handled by Red's (and/or
any other configured) error handling. any other configured) error handling.
""" """
if not isinstance(amount, int) or amount < 0: if not isinstance(amount, int) or amount < 0:

View File

@ -46,7 +46,7 @@ class BalanceTooHigh(BankError, OverflowError):
self.currency_name = currency_name self.currency_name = currency_name
def __str__(self) -> str: def __str__(self) -> str:
return _("{user}'s balance cannot rise above max {currency}.").format( return _("{user}'s balance cannot rise above {max} {currency}.").format(
user=self.user, max=humanize_number(self.max_balance), currency=self.currency_name user=self.user, max=humanize_number(self.max_balance), currency=self.currency_name
) )