diff --git a/adventure/abc.py b/adventure/abc.py index 5531a190..2c159d12 100644 --- a/adventure/abc.py +++ b/adventure/abc.py @@ -2,6 +2,7 @@ import asyncio from abc import ABC, abstractmethod +from types import SimpleNamespace from typing import TYPE_CHECKING, Any, Dict, List, Literal, MutableMapping, Optional, Union import discord @@ -39,6 +40,7 @@ def __init__(self, *_args): self.config: Config self.bot: Red self.settings: Dict[Any, Any] + self.emojis: SimpleNamespace self._ready: asyncio.Event self._adventure_countdown: dict self._rewards: dict diff --git a/adventure/adventure.py b/adventure/adventure.py index da599d9c..e320f296 100755 --- a/adventure/adventure.py +++ b/adventure/adventure.py @@ -93,7 +93,7 @@ async def red_delete_data_for_user( user_id ).clear() # This will only ever touch the separate currency, leaving bot economy to be handled by core. - __version__ = "4.0.4" + __version__ = "4.0.5" def __init__(self, bot: Red): self.bot = bot @@ -871,7 +871,7 @@ async def _simple(self, ctx: commands.Context, adventure_msg, challenge: str = N if easy_mode: if transcended: # Shows Transcended on Easy mode - new_challenge = _("Transcended {}").format(challenge.replace("Ascended", "")) + new_challenge = _("Transcended {}").format(challenge.replace("Ascended ", "")) no_monster = False if monster_roster[challenge]["boss"]: timer = 60 * 5 diff --git a/adventure/charsheet.py b/adventure/charsheet.py index ee1086b3..1559131a 100644 --- a/adventure/charsheet.py +++ b/adventure/charsheet.py @@ -64,6 +64,20 @@ def __init__(self, **kwargs): def __str__(self): return self.rarity.as_str(self.name) + def __eq__(self, other: object) -> bool: + if not isinstance(other, Item): + return False + return ( + other.name == self.name + and other.rarity is self.rarity + and other.dex == self.dex + and other.luck == self.luck + and other.att == self.att + and other.int == self.int + and other.cha == self.cha + and other.slot is self.slot + ) + @property def ansi(self) -> str: return self.rarity.as_ansi(self.name) @@ -88,7 +102,9 @@ def row(self, player_level: int) -> Tuple[Any, ...]: self.luck * (1 if self.slot is not Slot.two_handed else 2), f"{ANSITextColours.red.as_str(str(self.lvl))}" if not can_equip else f"{self.lvl}", self.owned, - f"[{self.degrade}]" if self.rarity in ["legendary", "event", "ascended"] and self.degrade >= 0 else "N/A", + f"[{self.degrade}]" + if self.rarity in [Rarities.legendary, Rarities.event, Rarities.ascended] and self.degrade >= 0 + else "N/A", self.set or "N/A", ) @@ -150,7 +166,6 @@ def remove_markdowns(item): def from_json(cls, ctx: commands.Context, data: dict): name = "".join(data.keys()) data = data[name] - rarity = "normal" if name.startswith("."): name = name.replace("_", " ").replace(".", "") rarity = "rare" @@ -190,7 +205,7 @@ def from_json(cls, ctx: commands.Context, data: dict): elif name.startswith("{Event:'"): name = name.replace("{Event:'", "").replace("''}", "") rarity = "event" - rarity = data["rarity"] if "rarity" in data else rarity + rarity = data.get("rarity", "normal") att = data["att"] if "att" in data else 0 dex = data["dex"] if "dex" in data else 0 inter = data["int"] if "int" in data else 0 @@ -728,17 +743,12 @@ def _sort(item): final.append(sorted(tmp[slot_name], key=_sort)) return final - async def looted(self, how_many: int = 1, exclude: Set[Union[str, Rarities]] = set()) -> List[Tuple[str, int]]: - if not exclude: - exclude = {Rarities.normal, Rarities.rare, Rarities.epic, Rarities.forged} - else: - for rarity in exclude: - if isinstance(rarity, Rarities): - exclude.add(rarity) - else: - exclude.add(Rarities.get_from_name(rarity)) + async def looted(self, how_many: int = 1, exclude: Optional[Set[Rarities]] = None) -> List[Tuple[str, int]]: + if exclude is None: + exclude = {Rarities.forged, Rarities.event} exclude.add(Rarities.forged) - items = [i for n, i in self.backpack.items() if i.rarity not in exclude] + exclude.add(Rarities.event) + items = [i for i in self.backpack.values() if i.rarity not in exclude] looted_so_far = 0 looted = [] if not items: @@ -747,7 +757,8 @@ async def looted(self, how_many: int = 1, exclude: Set[Union[str, Rarities]] = s while how_many > looted_so_far: if looted_so_far >= how_many: break - if count >= 5: + if count >= 10: + # Our max items stolen is 10 now break item = random.choice(items) if not bool(random.getrandbits(1)): @@ -776,7 +787,7 @@ async def make_backpack_tables(self, items: List[List[str]], title: str = "") -> "CHA", "INT", "DEX", - "LUC", + "LUCK", "LVL", "QTY", "DEG", @@ -1534,14 +1545,19 @@ async def rebirth(self, dev_val: int = None) -> dict: self.belt, self.legs, self.boots, - self.left, - self.right, self.ring, self.charm, self.neck, ]: if item and item.to_json() not in list(self.pieces_to_keep.values()): await self.add_to_backpack(item) + if (self.left and self.left.slot is Slot.two_handed) or (self.right and self.right.slot is Slot.two_handed): + if self.left.to_json() not in list(self.pieces_to_keep.values()): + await self.add_to_backpack(self.left) + else: + for item in [self.left, self.right]: + if item and item.to_json() not in list(self.pieces_to_keep.values()): + await self.add_to_backpack(item) forged = 0 for k, v in self.backpack.items(): for n, i in v.to_json().items(): @@ -1606,15 +1622,21 @@ async def rebirth(self, dev_val: int = None) -> dict: def keep_equipped(self): items_to_keep = {} - last_slot = "" for slots in Slot: - if slots is Slot.two_handed: - continue - if last_slot == "two handed": - last_slot = slots + if slots in [Slot.two_handed, Slot.left, Slot.right]: continue + items_to_keep[slots.name] = {} item = getattr(self, slots.name) - items_to_keep[slots] = item.to_json() if self.rebirths >= 30 and item and item.set else {} + if item and item.set and self.rebirths >= 30: + items_to_keep[slots.name] = item.to_json() + if self.left == self.right and self.right is not None: + if self.right.set and self.rebirths >= 30: + items_to_keep["right"] = self.right.to_json() + else: + if self.left and self.left.set and self.rebirths >= 30: + items_to_keep["left"] = self.left.to_json() + if self.right and self.right.set and self.rebirths >= 30: + items_to_keep["right"] = self.right.to_json() self.pieces_to_keep = items_to_keep diff --git a/adventure/class_abilities.py b/adventure/class_abilities.py index d93d10d9..078a37fd 100644 --- a/adventure/class_abilities.py +++ b/adventure/class_abilities.py @@ -51,7 +51,7 @@ async def heroclass( if clz is None: ctx.command.reset_cooldown(ctx) classes = box( - "\n".join(c.class_colour.as_str(c.class_name) for c in HeroClasses), + "\n".join(c.class_colour.as_str(c.class_name) for c in HeroClasses if c is not HeroClasses.hero), lang="ansi", ) await smart_embed( @@ -440,12 +440,10 @@ async def _forage(self, ctx: commands.Context): c.heroclass["cooldown"] = time.time() + cooldown_time await self.config.user(ctx.author).set(await c.to_json(ctx, self.config)) else: - cooldown_time = c.heroclass["cooldown"] - time.time() + cooldown_time = c.heroclass["cooldown"] return await smart_embed( ctx, - _("This command is on cooldown. Try again in {}.").format( - humanize_timedelta(seconds=int(cooldown_time)) if int(cooldown_time) >= 1 else _("1 second") - ), + _("This command is on cooldown. Try again in {}.").format(f""), ) @pet.command(name="free") @@ -513,16 +511,14 @@ async def bless(self, ctx: commands.Context): ), ) else: - cooldown_time = c.heroclass["cooldown"] - time.time() + cooldown_time = c.heroclass["cooldown"] return await smart_embed( ctx, _( "Your hero is currently recovering from the last time " "they used this skill or they have just changed their heroclass. " "Try again in {}." - ).format( - humanize_timedelta(seconds=int(cooldown_time)) if int(cooldown_time) >= 1 else _("1 second") - ), + ).format(f""), ) @commands.hybrid_command() @@ -714,9 +710,7 @@ async def insight(self, ctx: commands.Context): "Your hero is currently recovering from the last time " "they used this skill or they have just changed their heroclass. " "Try again in {}." - ).format( - humanize_timedelta(seconds=int(cooldown_time)) if int(cooldown_time) >= 1 else _("1 second") - ), + ).format(f""), ) @commands.hybrid_command() @@ -758,16 +752,14 @@ async def rage(self, ctx: commands.Context): ), ) else: - cooldown_time = c.heroclass["cooldown"] - time.time() + cooldown_time = c.heroclass["cooldown"] return await smart_embed( ctx, _( "Your hero is currently recovering from the last time " "they used this skill or they have just changed their heroclass. " "Try again in {}." - ).format( - humanize_timedelta(seconds=int(cooldown_time)) if int(cooldown_time) >= 1 else _("1 second") - ), + ).format(f""), ) @commands.hybrid_command() @@ -810,15 +802,13 @@ async def focus(self, ctx: commands.Context): ), ) else: - cooldown_time = c.heroclass["cooldown"] - time.time() + cooldown_time = c.heroclass["cooldown"] return await smart_embed( ctx, _( "Your hero is currently recovering from the " "last time they used this skill. Try again in {}." - ).format( - humanize_timedelta(seconds=int(cooldown_time)) if int(cooldown_time) >= 1 else _("1 second") - ), + ).format(f""), ) @commands.hybrid_command() @@ -859,16 +849,14 @@ async def music(self, ctx: commands.Context): ), ) else: - cooldown_time = c.heroclass["cooldown"] - time.time() + cooldown_time = c.heroclass["cooldown"] return await smart_embed( ctx, _( "Your hero is currently recovering from the last time " "they used this skill or they have just changed their heroclass. " "Try again in {}." - ).format( - humanize_timedelta(seconds=int(cooldown_time)) if int(cooldown_time) >= 1 else _("1 second") - ), + ).format(f""), ) @commands.max_concurrency(1, per=commands.BucketType.user) @@ -899,7 +887,7 @@ async def forge(self, ctx: commands.Context): if "cooldown" not in c.heroclass: c.heroclass["cooldown"] = cooldown_time + 1 if c.heroclass["cooldown"] > time.time(): - cooldown_time = c.heroclass["cooldown"] - time.time() + cooldown_time = c.heroclass["cooldown"] return await smart_embed( ctx, _("This command is on cooldown. Try again in {}").format( diff --git a/adventure/constants.py b/adventure/constants.py index d49df348..8655128a 100644 --- a/adventure/constants.py +++ b/adventure/constants.py @@ -50,6 +50,8 @@ def char_slot(self): @classmethod def get_from_name(cls, name: str) -> Slot: + if name.lower() == "twohanded": + return Slot.two_handed for i in cls: if " " in name: name = name.replace(" ", "_") diff --git a/adventure/converters.py b/adventure/converters.py index 5f60a518..6edc5935 100644 --- a/adventure/converters.py +++ b/adventure/converters.py @@ -100,11 +100,74 @@ def __init__(self, cmd: str, message: str): super().__init__(message=message) -class Stats(Converter): - """This will parse a string for specific keywords like attack and dexterity followed by a - number to create an item object to be added to a users inventory.""" +class RarityConverter(Transformer): + @classmethod + async def convert(cls, ctx: commands.Context, argument: str) -> Optional[Rarities]: + try: + rarity = Rarities.get_from_name(argument) + except KeyError: + raise BadArgument( + _("{rarity} is not a valid rarity, select one of {rarities}").format( + rarity=argument, rarities=humanize_list([i.get_name() for i in Rarities if i.is_chest]) + ) + ) + return rarity + + @classmethod + async def transform(cls, interaction: discord.Interaction, argument: str) -> Optional[Rarities]: + ctx = await interaction.client.get_context(interaction) + return await cls.convert(ctx, argument) + + async def autocomplete(self, interaction: discord.Interaction, current: str) -> List[Choice]: + choices = [] + # cog = interaction.client.get_cog("Adventure") + log.debug(interaction.command) + for rarity in Rarities: + if rarity is Rarities.pet: + continue + if interaction.command and interaction.command.name in ["loot", "convert"] and not rarity.is_chest: + continue + if current.lower() in rarity.get_name().lower(): + choices.append(Choice(name=rarity.get_name(), value=rarity.name)) + return choices - async def convert(self, ctx: commands.Context, argument: str) -> Dict[str, int]: + +class SlotConverter(Transformer): + @classmethod + async def convert(cls, ctx: commands.Context, argument: str) -> Optional[Slot]: + if argument: + try: + return Slot.get_from_name(argument) + except ValueError: + raise BadArgument( + _("{provided} is not a valid slot, select one of {slots}").format( + provided=argument, slots=humanize_list([i.get_name() for i in Slot]) + ) + ) + + return None + + @classmethod + async def transform(cls, interaction: discord.Interaction, argument: str) -> Optional[Slot]: + ctx = await interaction.client.get_context(interaction) + return await cls.convert(ctx, argument) + + async def autocomplete(self, interaction: discord.Interaction, current: str) -> List[Choice]: + return [Choice(name=i.get_name(), value=i.name) for i in Slot if current.lower() in i.get_name().lower()] + + +class Stats(commands.FlagConverter, case_insensitive=True): + attack: Optional[int] = commands.flag(name="att", aliases=["attack"], default=0) + charisma: Optional[int] = commands.flag(name="cha", aliases=["charisma", "diplomacy", "diplo"], default=0) + intelligence: Optional[int] = commands.flag(name="int", aliases=["intelligence"], default=0) + dexterity: Optional[int] = commands.flag(name="dex", aliases=["dexterity"], default=0) + luck: Optional[int] = commands.flag(name="luck", default=0) + rarity: Optional[Rarities] = commands.flag(name="rarity", default=Rarities.normal, converter=RarityConverter) + degrade: Optional[int] = commands.flag(name="degrade", aliases=["deg"], default=3) + level: Optional[int] = commands.flag(name="level", aliases=["lvl"], default=1) + slot: Optional[Slot] = commands.flag(name="slot", default=Slot.right, converter=SlotConverter) + + async def to_json(self, ctx: commands.Context) -> Dict[str, Union[List[str], int, str]]: result = { "slot": ["left"], "att": 0, @@ -116,36 +179,32 @@ async def convert(self, ctx: commands.Context, argument: str) -> Dict[str, int]: "degrade": 0, "lvl": 1, } - possible_stats = dict( - att=ATT.search(argument), - cha=CHA.search(argument), - int=INT.search(argument), - dex=DEX.search(argument), - luck=LUCK.search(argument), - degrade=DEG.search(argument), - lvl=LEVEL.search(argument), - ) - try: - slot = [SLOT.search(argument).group(0)] - if slot == ["twohanded"]: - slot = ["left", "right"] - result["slot"] = slot - except AttributeError: - raise BadArgument(_("No slot position was provided.")) - try: - rarity_re = "|".join(i.name for i in Rarities if i.value < Rarities.pet.value) - result["rarity"] = re.search(rarity_re, argument, flags=re.I).group(0) - except AttributeError: - raise BadArgument(_("No rarity was provided.")) + possible_stats = { + "slot": self.slot.to_json() if self.slot else ["left"], + "att": self.attack, + "cha": self.charisma, + "int": self.intelligence, + "dex": self.dexterity, + "luck": self.luck, + "rarity": self.rarity.name if self.rarity else "normal", + "degrade": self.degrade, + "lvl": self.level, + } for key, value in possible_stats.items(): + if key in ["slot", "rarity"]: + if key == "rarity" and value in ("pet", "forged"): + raise BadArgument(_("How do you plan to create items with those rarities? Not creating item.")) + result[key] = value + continue try: - stat = int(value.group(1)) + stat = int(value) if ( (key not in ["degrade", "lvl"] and stat > 10) or (key == "lvl" and stat < 50) ) and not await ctx.bot.is_owner(ctx.author): raise BadArgument(_("Don't you think that's a bit overpowered? Not creating item.")) - result[key] = stat + result[key] = value except (AttributeError, ValueError): + result[key] = value pass return result @@ -625,62 +684,6 @@ async def convert(self, ctx, argument) -> MutableMapping: } -class SlotConverter(Transformer): - @classmethod - async def convert(cls, ctx: commands.Context, argument: str) -> Optional[Slot]: - if argument: - try: - return Slot.get_from_name(argument) - except ValueError: - raise BadArgument( - _("{provided} is not a valid slot, select one of {slots}").format( - provided=argument, slots=humanize_list([i.get_name() for i in Slot]) - ) - ) - - return None - - @classmethod - async def transform(cls, interaction: discord.Interaction, argument: str) -> Optional[Slot]: - ctx = await interaction.client.get_context(interaction) - return await cls.convert(ctx, argument) - - async def autocomplete(self, interaction: discord.Interaction, current: str) -> List[Choice]: - return [Choice(name=i.get_name(), value=i.name) for i in Slot if current.lower() in i.get_name().lower()] - - -class RarityConverter(Transformer): - @classmethod - async def convert(cls, ctx: commands.Context, argument: str) -> Optional[Rarities]: - try: - rarity = Rarities.get_from_name(argument) - except KeyError: - raise BadArgument( - _("{rarity} is not a valid rarity, select one of {rarities}").format( - rarity=argument, rarities=humanize_list([i.get_name() for i in Rarities if i.is_chest]) - ) - ) - return rarity - - @classmethod - async def transform(cls, interaction: discord.Interaction, argument: str) -> Optional[Rarities]: - ctx = await interaction.client.get_context(interaction) - return await cls.convert(ctx, argument) - - async def autocomplete(self, interaction: discord.Interaction, current: str) -> List[Choice]: - choices = [] - # cog = interaction.client.get_cog("Adventure") - log.debug(interaction.command) - for rarity in Rarities: - if rarity is Rarities.pet: - continue - if interaction.command and interaction.command.name in ["loot", "convert"] and not rarity.is_chest: - continue - if current.lower() in rarity.get_name().lower(): - choices.append(Choice(name=rarity.get_name(), value=rarity.name)) - return choices - - class SkillConverter(Transformer): @classmethod async def convert(cls, ctx: commands.Context, argument: str) -> Skills: diff --git a/adventure/economy.py b/adventure/economy.py index c740e43e..4dfba2f7 100644 --- a/adventure/economy.py +++ b/adventure/economy.py @@ -336,31 +336,34 @@ async def _give_item( ): """[Owner] Adds a custom item to a specified member. - Item names containing spaces must be enclosed in double quotes. `[p]give item @locastan - "fine dagger" 1 att 1 charisma rare twohanded` will give a two handed .fine_dagger with 1 - attack and 1 charisma to locastan. if a stat is not specified it will default to 0, order - does not matter. + Item names containing spaces must be enclosed in double quotes. available stats are: - - `attack` or `att` - - `charisma` or `diplo` - - `charisma` or `cha` - - `intelligence` or `int` - - `dexterity` or `dex` - - `luck` - - `rarity` (one of normal, rare, epic, legendary, set, forged, or event) - - `degrade` (Set to -1 to never degrade on rebirths) - - `level` (lvl) - - `slot` (one of `head`, `neck`, `chest`, `gloves`, `belt`, `legs`, `boots`, `left`, `right` - `ring`, `charm`, `twohanded`) - - `[p]give item @locastan "fine dagger" 1 att 1 charisma -1 degrade 100 level rare twohanded` + - `attack:` or `att:` defaults to 0. + - `charisma:` or `diplo:` or `cha:` defaults to 0. + - `intelligence:` or `int:` defaults to 0. + - `dexterity:` or `dex:` defaults to 0. + - `luck:` defaults to 0. + - `rarity:` (one of `normal`, `rare`, `epic`, `legendary`, `set`, `forged`, or `event`) defaults to normal. + - `degrade:` (Set to -1 to never degrade on rebirths) defaults to 3. + - `level:` or `lvl:` defaults to the calculated level required based on stats. + - `slot:` (one of `head`, `neck`, `chest`, `gloves`, `belt`, `legs`, `boots`, `left`, `right` + `ring`, `charm`, `two handed`) defaults to left. + Example: + ``` + [p]give item @locastan "fine dagger" att: 1 charisma: 1 degrade: -1 level: 100 rarity: rare slot: twohanded + ``` + Will give locastan a 1 attack 1 charisma `.fine_dagger`. """ if item_name.isnumeric(): return await smart_embed(ctx, _("Item names cannot be numbers.")) item_name = re.sub(r"[^\w ]", "", item_name) if user is None: user = ctx.author - new_item = {item_name: stats} + try: + new_item = {item_name: await stats.to_json(ctx)} + except commands.BadArgument as e: + await ctx.send(e) + return item = Item.from_json(ctx, new_item) async with self.get_lock(user): try: @@ -370,14 +373,15 @@ async def _give_item( return await c.add_to_backpack(item) await self.config.user(user).set(await c.to_json(ctx, self.config)) - await ctx.send( - box( - _("An item named {item} has been created and placed in {author}'s backpack.").format( - item=item, author=escape(user.display_name) - ), - lang="ansi", - ) + item_table = await c.make_backpack_tables([item.row(c.lvl)]) + msg = box( + _("An item named {item} has been created and placed in {author}'s backpack.").format( + item=item, author=escape(user.display_name), item_stats=item_table + ), + lang="ansi", ) + msg += item_table[0] + await ctx.send(msg) @give.command(name="loot") async def _give_loot( diff --git a/adventure/game_session.py b/adventure/game_session.py index 82343ef7..f25740a1 100644 --- a/adventure/game_session.py +++ b/adventure/game_session.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import random import time @@ -295,6 +297,10 @@ def __init__( self.action_type = "special_action" self.label_name = "Special Action" + @property + def view(self) -> GameSession: + return super().view + async def send_cooldown(self, interaction: discord.Interaction, c: Character, cooldown_time: int): cooldown_time = int(c.heroclass["cooldown"]) msg = _( @@ -377,16 +383,16 @@ async def send_insight(self, interaction: discord.Interaction, c: Character): hp = session.monster_modified_stats["hp"] diplo = session.monster_modified_stats["dipl"] if roll == 1: - hp = int(hp * self.ATTRIBS[session.attribute][0] * session.monster_stats) - dipl = int(diplo * self.ATTRIBS[session.attribute][1] * session.monster_stats) + hp = int(hp * self.view.cog.ATTRIBS[session.attribute][0] * session.monster_stats) + dipl = int(diplo * self.view.cog.ATTRIBS[session.attribute][1] * session.monster_stats) msg += _( "This monster is **a{attr} {challenge}** ({hp_symbol} {hp}/{dipl_symbol} {dipl}){trans}.\n" ).format( challenge=session.challenge, attr=session.attribute, - hp_symbol=self.emojis.hp, + hp_symbol=self.view.cog.emojis.hp, hp=humanize_number(ceil(hp)), - dipl_symbol=self.emojis.dipl, + dipl_symbol=self.view.cog.emojis.dipl, dipl=humanize_number(ceil(dipl)), trans=f" (**Transcended**) {self.view.cog.emojis.skills.psychic}" if session.transcended @@ -394,25 +400,25 @@ async def send_insight(self, interaction: discord.Interaction, c: Character): ) self.view.exposed = True elif roll >= 0.95: - hp = hp * self.ATTRIBS[session.attribute][0] * session.monster_stats - dipl = diplo * self.ATTRIBS[session.attribute][1] * session.monster_stats + hp = hp * self.view.cog.ATTRIBS[session.attribute][0] * session.monster_stats + dipl = diplo * self.view.cog.ATTRIBS[session.attribute][1] * session.monster_stats msg += _( "This monster is **a{attr} {challenge}** ({hp_symbol} {hp}/{dipl_symbol} {dipl}).\n" ).format( challenge=session.challenge, attr=session.attribute, - hp_symbol=self.emojis.hp, + hp_symbol=self.view.cog.emojis.hp, hp=humanize_number(ceil(hp)), - dipl_symbol=self.emojis.dipl, + dipl_symbol=self.view.cog.emojis.dipl, dipl=humanize_number(ceil(dipl)), ) self.view.exposed = True elif roll >= 0.90: - hp = hp * self.ATTRIBS[session.attribute][0] * session.monster_stats + hp = hp * self.view.cog.ATTRIBS[session.attribute][0] * session.monster_stats msg += _("This monster is **a{attr} {challenge}** ({hp_symbol} {hp}).\n").format( challenge=session.challenge, attr=session.attribute, - hp_symbol=self.emojis.hp, + hp_symbol=self.view.cog.emojis.hp, hp=humanize_number(ceil(hp)), ) self.view.exposed = True diff --git a/adventure/helpers.py b/adventure/helpers.py index 86d5694f..7a2f1e9d 100644 --- a/adventure/helpers.py +++ b/adventure/helpers.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import random import re import time from enum import Enum -from typing import Optional, Union +from typing import TYPE_CHECKING, Optional, Union import discord from discord.ext.commands import CheckFailure @@ -16,6 +18,9 @@ _ = Translator("Adventure", __file__) +if TYPE_CHECKING: + from .abc import AdventureMixin + async def _get_epoch(seconds: int): epoch = time.time() @@ -33,7 +38,7 @@ async def smart_embed( success: Optional[bool] = None, image: Optional[str] = None, ephemeral: bool = False, - cog: Optional[Cog] = None, + cog: Optional[AdventureMixin] = None, interaction: Optional[discord.Interaction] = None, view: Optional[discord.ui.View] = discord.utils.MISSING, embed_colour: Optional[str] = None, diff --git a/adventure/negaverse.py b/adventure/negaverse.py index 5c9ab366..e7dfa0b2 100644 --- a/adventure/negaverse.py +++ b/adventure/negaverse.py @@ -9,12 +9,12 @@ import discord from redbot.core import commands from redbot.core.i18n import Translator -from redbot.core.utils.chat_formatting import bold, box, humanize_number +from redbot.core.utils.chat_formatting import bold, box, humanize_number, pagify from .abc import AdventureMixin from .bank import bank from .charsheet import Character -from .constants import Treasure +from .constants import Rarities, Treasure from .helpers import ConfirmView, escape, is_dev, smart_embed _ = Translator("Adventure", __file__) @@ -171,26 +171,20 @@ async def _negaverse( xp_won = int(xp_won * (min(max(random.randint(0, character.rebirths), 1), 50) / 100 + 1)) xp_won = int(xp_won * (character.gear_set_bonus.get("xpmult", 1) + daymult)) if roll == -2: - looted = "" curr_balance = character.bal await bank.set_balance(ctx.author, 0) offering_value += curr_balance loss_string = _("all of their") loss_state = True + max_items_lost = max(min(10, int(len(character.backpack) * 0.10)), 0) items = await character.looted( - how_many=max(int(10 - roll) // 2, 1), exclude={"event", "normal", "rare", "epic"} + how_many=random.randint(1, max_items_lost), exclude={Rarities.normal, Rarities.epic, Rarities.rare} ) - if items: - item_string = "\n".join([f"{v} x{i}" for v, i in items]) - looted = box(f"{item_string}", lang="ansi") - await self.config.user(ctx.author).set(await character.to_json(ctx, self.config)) + # Devs steal a random amount of items between 1 and 10 legendaries and up always + # Rarities included for dev fails always above epic rarities loss_msg = _(", losing {loss} {currency_name} as {negachar} rifled through their belongings.").format( loss=loss_string, currency_name=currency_name, negachar=bold(negachar) ) - if looted: - loss_msg += _(" {negachar} also stole the following items:\n\n{items}").format( - items=looted, negachar=bold(negachar) - ) await nega_msg.edit( content=_("{content}\n{author} fumbled and died to {negachar}'s savagery{loss_msg}").format( content=nega_msg.content, @@ -200,10 +194,17 @@ async def _negaverse( ), view=None, ) + if items: + item_string = "\n".join([f"{v} {i}" for v, i in items]) + await self.config.user(ctx.author).set(await character.to_json(ctx, self.config)) + looted_msg = _("{negachar} also stole the following items:\n\n{items}").format( + items=item_string, negachar=bold(negachar) + ) + for page in pagify(looted_msg): + await ctx.send(box(page, lang="ansi")) ctx.command.reset_cooldown(ctx) elif roll < 10: loss = round(bal // 3) - looted = "" curr_balance = character.bal try: await bank.withdraw_credits(ctx.author, loss) @@ -214,21 +215,20 @@ async def _negaverse( offering_value += curr_balance loss_string = _("all of their") loss_state = True + max_items_lost = max(min(10, int(len(character.backpack) * 0.10)), 0) + items_to_lose = random.randint(0, max_items_lost) + # crit fails lose between 0 and 10 items based on their total backpack size if character.bal < loss: - items = await character.looted( - how_many=max(int(10 - roll) // 2, 1), exclude={"event", "normal", "rare", "epic"} - ) - if items: - item_string = "\n".join([f"{v} {i}" for v, i in items]) - looted = box(f"{item_string}", lang="ansi") - await self.config.user(ctx.author).set(await character.to_json(ctx, self.config)) + items_to_lose += 1 + items = await character.looted( + how_many=items_to_lose, exclude={Rarities.normal, Rarities.rare, Rarities.epic} + ) + # Rarities included for crit fails always abive epic rarity + loss_msg = _(", losing {loss} {currency_name} as {negachar} rifled through their belongings.").format( loss=loss_string, currency_name=currency_name, negachar=bold(negachar) ) - if looted: - loss_msg += _(" {negachar} also stole the following items:\n\n{items}").format( - items=looted, negachar=bold(negachar) - ) + await nega_msg.edit( content=_("{content}\n{author} fumbled and died to {negachar}'s savagery{loss_msg}").format( content=nega_msg.content, @@ -238,6 +238,15 @@ async def _negaverse( ), view=None, ) + if items: + item_string = "\n".join([f"{v} {i}" for v, i in items]) + await self.config.user(ctx.author).set(await character.to_json(ctx, self.config)) + looted_msg = _("{negachar} also stole the following items:\n\n{items}").format( + items=item_string, negachar=bold(negachar) + ) + for page in pagify(looted_msg): + await ctx.send(box(page, lang="ansi")) + ctx.command.reset_cooldown(ctx) elif roll == 50 and versus < 50: await nega_msg.edit( @@ -304,7 +313,6 @@ async def _negaverse( else: loss = round(bal / (random.randint(10, 25))) curr_balance = character.bal - looted = "" try: await bank.withdraw_credits(ctx.author, loss) offering_value += loss @@ -314,23 +322,22 @@ async def _negaverse( loss_string = _("all of their") offering_value += curr_balance loss_state = True + exclude_list = {i for i in Rarities if i.value not in range(0, ((versus - roll) // 10) + 1)} + # regular fails lose rarities depending on the difference of the roll + # i.e. A near loss should only lose normal quality items but severely losing + # will lose up to ascended if the versus rolled max and you rolled just above crit miss + max_items_lost = max(min(10, int(len(character.backpack) * 0.10)), 0) + # max items to lose based on 10% of the players total items in their backpack + # users with fewer items should lose less but hoarders lose more up to a max of 10 + items_to_lose = random.randint(0, max_items_lost) if character.bal < loss: - items = await character.looted( - how_many=max(int(10 - roll) // 2, 1), exclude={"event", "normal", "rare", "epic"} - ) - if items: - item_string = "\n".join([f"{i} - {v}" for v, i in items]) - looted = box(f"{item_string}", lang="ansi") - await self.config.user(ctx.author).set(await character.to_json(ctx, self.config)) + items_to_lose += 1 + items = await character.looted(how_many=items_to_lose, exclude=exclude_list) loss_msg = _(", losing {loss} {currency_name} as {negachar} looted their backpack.").format( loss=loss_string, currency_name=currency_name, negachar=bold(negachar), ) - if looted: - loss_msg += _(" {negachar} also stole the following items:\n\n{items}").format( - items=looted, negachar=bold(negachar) - ) await nega_msg.edit( content=_("{author} {dice}({roll}) was killed by {negachar} {dice}({versus}){loss_msg}").format( dice=self.emojis.dice, @@ -342,6 +349,14 @@ async def _negaverse( ), view=None, ) + if items: + item_string = "\n".join([f"{v} {i}" for v, i in items]) + await self.config.user(ctx.author).set(await character.to_json(ctx, self.config)) + looted_msg = _("{negachar} also stole the following items:\n\n{items}").format( + items=item_string, negachar=bold(negachar) + ) + for page in pagify(looted_msg): + await ctx.send(box(page, lang="ansi")) ctx.command.reset_cooldown(ctx) finally: lock = self.get_lock(ctx.author)