Beispiel #1
0
    async def statstxt(self,
                       ctx,
                       *,
                       memberOrHeight: typing.Union[discord.Member,
                                                    SV] = None):
        """User stats command, raw text version.

        Get tons of user stats about yourself, a user, or a raw height.
        Stats: current height, current weight, base height, base weight,
        foot length, foot width, toe height, pointer finger length, thumb width,
        fingerprint depth, hair width, multiplier.

        Examples:
        `&statstxt` (defaults to stats about you.)
        `&statstxt @User`
        `&statstxt 10ft`
        """
        if memberOrHeight is None:
            memberOrHeight = ctx.author

        userdata = getUserdata(memberOrHeight)

        stats = proportions.PersonStats(userdata)
        await ctx.send(str(stats))

        logger.info(f"Stats for {memberOrHeight} sent.")
Beispiel #2
0
    async def stats(self, ctx, memberOrHeight: typing.Union[discord.Member, SV] = None, *, customName = None):
        """User stats command.

        Get tons of user stats about yourself, a user, or a raw height.

        Examples:
        `&stats` (defaults to stats about you.)
        `&stats @User`
        `&stats 10ft`
        """
        if memberOrHeight is None:
            memberOrHeight = ctx.author

        if isinstance(memberOrHeight, SV):
            telemetry.SizeViewed(memberOrHeight).save()

        same_user = isinstance(memberOrHeight, discord.Member) and memberOrHeight.id == ctx.author.id
        userdata = getUserdata(memberOrHeight, customName, allow_unreg=same_user)

        stats = proportions.PersonStats(userdata)

        embedtosend = stats.toEmbed(ctx.author.id)
        await ctx.send(embed = embedtosend)

        await showNextStep(ctx, userdata)
Beispiel #3
0
    async def stats(self,
                    ctx,
                    *,
                    memberOrHeight: typing.Union[discord.Member, SV] = None,
                    customName=None):
        """User stats command.

        Get tons of user stats about yourself, a user, or a raw height.
        Stats: current height, current weight, base height, base weight,
        foot length, foot width, toe height, pointer finger length, thumb width,
        fingerprint depth, hair width, multiplier.

        Examples:
        `&stats` (defaults to stats about you.)
        `&stats @User`
        `&stats 10ft`
        """
        if memberOrHeight is None:
            memberOrHeight = ctx.author

        userdata = getUserdata(memberOrHeight, customName)

        stats = proportions.PersonStats(userdata)

        embedtosend = stats.toEmbed()
        if ctx.author.id != userdata.id:
            embedtosend.description = f"Requested by *{ctx.author.display_name}*"
        await ctx.send(embed=embedtosend)

        logger.info(f"Stats for {memberOrHeight} sent.")
Beispiel #4
0
    async def ruler(self, ctx, length: SV, *, who: typing.Union[discord.Member, SV] = None):
        """A distance to a user looks how long to everyone else?

        Examples:
        `&ruler 1mi`
        `&ruler 1ft @DigiDuncan`"""

        if who is None:
            who = ctx.message.author

        userdata = getUserdata(who)
        userstats = proportions.PersonStats(userdata)

        if userdata.height == 0:
            await ctx.send(f"{userdata.tag} doesn't exist...")
            return

        newlength = SV(length / userstats.viewscale)

        e = discord.Embed(
            title = f"{userstats.nickname}'s {length:,.3mu} to the world",
            description = (
                f"To everyone else, {userstats.nickname}'s {length:,.3mu} would look to be **{newlength:,.3mu}.**"
            )
        )

        await ctx.send(embed = e)
Beispiel #5
0
    async def statsas(self, ctx, memberOrHeight: typing.Union[discord.Member, SV] = None,
                      memberOrHeight2: typing.Union[discord.Member, SV] = None, *, customName = None):
        """User stats command with modified bases.

        Get tons of user stats about yourself, a user, or a raw height, as if they were a different height.

        Examples:
        `&statsas 100ft` (defaults to stats about you, if you were a certain height.)
        `&statsas 100ft @User` (get stats about @User if they were a certain height.)
        `&statsas @User @User2` (get stats about @User2 if they were as tall as @User.)
        """
        if memberOrHeight is None:
            raise errors.ArgumentException
        if memberOrHeight2 is None:
            memberOrHeight2 = ctx.author

        if isinstance(memberOrHeight, SV):
            telemetry.SizeViewed(memberOrHeight).save()
        if isinstance(memberOrHeight2, SV):
            telemetry.SizeViewed(memberOrHeight).save()

        userdata = getUserdata(memberOrHeight)
        userdata2 = getUserdata(memberOrHeight2, customName)
        userdata2.nickname = userdata2.nickname + " as " + userdata.nickname
        userdata2.height = userdata.height

        stats = proportions.PersonStats(userdata2)

        embedtosend = stats.toEmbed(ctx.author.id)
        await ctx.send(embed = embedtosend)
Beispiel #6
0
    async def scalewalk(self, ctx, change: Diff, dist: SV, flag=None):
        """Walk a certain distance, scaling by an amount each step you take.
        Accepts addition or subtraction of a certain height, or multiplication/division of a factor.

        Examples:
        `&scalewalk 2x 50m
        `&scalewalk -1mm 20ft"""

        guildid = ctx.guild.id
        userid = ctx.author.id

        userdata = userdb.load(guildid, userid)
        stats = proportions.PersonStats(userdata)

        stepcount, final_inc, final_ratio = get_steps(stats.walksteplength,
                                                      change, dist)

        finalheight = SV(userdata.height / final_ratio)

        symbol = ""
        if change.changetype == "add":
            symbol = "+"
        if change.changetype == "multiply":
            symbol = "x"

        amountstring = ""
        if change.changetype == "add":
            amountstring = f"{symbol}{change.amount:,.3mu}"
        if change.changetype == "multiply":
            amountstring = f"{symbol}{change.amount:,.3}"

        if flag is None:
            e = discord.Embed(
                title=
                f"If {userdata.nickname} walked {dist:,.3mu}, scaling {amountstring} each step...",
                description=
                f"They would now be **{finalheight:,.3mu}** tall after **{stepcount}** steps."
            )
            await ctx.send(embed=e)
        elif flag == "apply":
            userdata.height = finalheight
            userdb.save(userdata)

            e = discord.Embed(
                title=
                f"{userdata.nickname} walked {dist:,.3mu}, scaling {amountstring} each step...",
                description=
                f"They are now **{finalheight:,.3mu}** tall after **{stepcount}** steps."
            )
            await ctx.send(embed=e)
        else:
            raise DigiContextException(f"Invalid flag {flag}.")
Beispiel #7
0
    async def step(self, ctx, steps=None):
        """Step a certain number of times, scaling by the amount set in `&setscalestep`.

        Scales you the amount that you would change depending on the scale factor you
        have set in `&setstepscale`.
        Can take a number, e.g.: `&step 20`
        """

        guildid = ctx.guild.id
        userid = ctx.author.id

        userdata = userdb.load(guildid, userid)
        stats = proportions.PersonStats(userdata)

        if steps is None:
            steps = 1

        steps = tryInt(steps)
        if steps == "car":
            await ctx.send("Cronch.")
            logger.log(EGG, f"{ctx.author.display_name} stepped on a car.")
            return

        if not isinstance(steps, int):
            await ctx.send(f"`{steps}` is not a number.")
            return

        if steps <= 0:
            await ctx.send("You... stand... still.")
            return

        if userdata.currentscalestep is None:
            await ctx.send(
                f"You do not have a stepscale set. Please use `{conf.prefix}setstepscale <amount>` to do so."
            )
            return

        if userdata.currentscalestep.changetype == "add":
            userdata.height += (userdata.currentscalestep.amount * steps)
        elif userdata.currentscalestep.changetype == "multiply":
            userdata.height *= (userdata.currentscalestep.amount**steps)
        else:
            raise ChangeMethodInvalidException(
                "This change type is not yet supported for scale-walking.")

        dist_travelled = get_dist(stats.walksteplength,
                                  userdata.currentscalestep, (steps + 1))
        await ctx.send(
            f"You walked {dist_travelled:,.3mu} in {steps} {'step' if steps == 1 else 'steps'}."
        )

        userdb.save(userdata)
Beispiel #8
0
    async def onewaycompare(
        self,
        ctx,
        *,
        what: typing.Union[DigiObject, discord.Member, SV, str],
        who: typing.Union[discord.Member,
                          SV] = None):  # TODO: Allow a second argument here.
        """See what an object looks like to you.

        Used to see how an object would look at your scale.
        Examples:
        `&lookat man`
        `&look book`
        `&examine building`"""

        if who is None:
            who = ctx.author

        userdata = getUserdata(who)
        userstats = proportions.PersonStats(userdata)

        if isinstance(what, DigiObject):
            oc = what.relativestatsembed(userdata)
            await ctx.send(embed=oc)
            logger.info(
                f"{ctx.author.display_name} looked at {what.article} {what.name}."
            )
            return
        elif isinstance(what, discord.Member) or isinstance(
                what, SV
        ):  # TODO: Make this not literally just a compare. (make one sided)
            compdata = getUserdata(what, "Raw")
            logger.info(f"{ctx.author.display_name} looked at {what}.")
        elif isinstance(what, str) and what in [
                "person", "man", "average", "average person", "average man",
                "average human", "human"
        ]:
            compheight = userstats.avgheightcomp
            compdata = getUserdata(compheight)
        else:
            await ctx.send(
                f"`{what}` is not a valid object, member, or height.")
            logger.info(
                f"{ctx.author.display_name} tried to look at {what}, but that's invalid."
            )
            return
        stats = proportions.PersonComparison(userdata, compdata)
        embedtosend = stats.toEmbed()
        if ctx.author.id != userdata.id:  # Future proofing for when you can do owcs for other people.
            embedtosend.description = f"*Requested by *{ctx.author.display_name}*"
        await ctx.send(embed=embedtosend)
Beispiel #9
0
    async def objectcompare(self, ctx, *, args: str):
        """See what an object looks like to you.

        Used to see how an object would look at your scale.
        Examples:
        `&objectcomapre lego`
        `&objcompare moon as @Kelly`
        """

        argslist = args.rsplit(" as ", 1)
        if len(argslist) == 1:
            what = argslist[0]
            who = None
        else:
            what = argslist[0]
            who = argslist[1]

        mc = MemberConverter()

        what = await parseMany(ctx, what, [DigiObject, mc, SV])
        who = await parseMany(ctx, who, [mc, SV])

        if who is None:
            what = await parseMany(ctx, args, [DigiObject, mc, SV])
            who = ctx.author

        if isinstance(who, SV):
            telemetry.SizeViewed(who).save()

        userdata = getUserdata(who)
        userstats = proportions.PersonStats(userdata)

        if isinstance(what, DigiObject):
            oc = what.relativestatsembed(userdata)
            await ctx.send(embed = oc)
            return
        elif isinstance(what, discord.Member) or isinstance(what, SV):  # TODO: Make this not literally just a compare. (make one sided)
            compdata = getUserdata(what)
        elif isinstance(what, str) and what in ["person", "man", "average", "average person", "average man", "average human", "human"]:
            compheight = userstats.avgheightcomp
            compdata = getUserdata(compheight)
        else:
            telemetry.UnknownObject(str(what)).save()
            await ctx.send(f"`{what}` is not a valid object, member, or height.")
            return
        stats = proportions.PersonComparison(userdata, compdata)
        embedtosend = await stats.toEmbed(ctx.author.id)
        await ctx.send(embed = embedtosend)
Beispiel #10
0
    async def statas(self, ctx, stat, memberOrHeight: typing.Union[discord.Member, SV] = None,
                     memberOrHeight2: typing.Union[discord.Member, SV] = None, *, customName = None):
        """User stat command with custom bases.

        Get a single stat about yourself, a user, or a raw height, as if they were a different height.

        Available stats are: #STATS#

        Examples:
        `&statas weight 100ft` (defaults to stats about you, if you were a certain height.)
        `&statas foot 100ft @User` (get stats about @User if they were a certain height.)
        `&statas speed @User @User2` (get stats about @User2 if they were as tall as @User.)
        """

        if memberOrHeight is None:
            raise errors.ArgumentException
        if memberOrHeight2 is None:
            memberOrHeight2 = ctx.author

        if isinstance(memberOrHeight, SV):
            telemetry.SizeViewed(memberOrHeight).save()
        if isinstance(memberOrHeight2, SV):
            telemetry.SizeViewed(memberOrHeight).save()

        userdata = getUserdata(memberOrHeight)
        userdata2 = getUserdata(memberOrHeight2, customName)
        userdata2.nickname = userdata2.nickname + " as " + userdata.nickname
        userdata2.height = userdata.height

        stats = proportions.PersonStats(userdata2)

        if stat not in statmap.keys():
            await ctx.send(f"The `{stat}` stat is not an available option.")
            return

        try:
            stat = statmap[stat]
        except KeyError:
            await ctx.send(f"`{stat}` is not a stat.")
            return

        stattosend = stats.getFormattedStat(stat)

        if stattosend is None:
            await ctx.send(f"The `{stat}` stat is unavailable for this user.")
            return

        await ctx.send(stattosend)
Beispiel #11
0
    async def stat(self, ctx, stat, memberOrHeight: typing.Union[discord.Member, SV] = None, *, customName = None):
        """User stat command.

        Get a single stat about yourself, a user, or a raw height.

        Available stats are: #STATS#

        Examples:
        `&stat height` (not specifying a user returns a stat about yourself.)
        `&stat weight @User`
        `&stat foot 10ft`
        """

        if memberOrHeight is None:
            memberOrHeight = ctx.author

        if isinstance(memberOrHeight, SV):
            telemetry.SizeViewed(memberOrHeight).save()

        same_user = isinstance(memberOrHeight, discord.Member) and memberOrHeight.id == ctx.author.id
        userdata = getUserdata(memberOrHeight, customName, allow_unreg=same_user)

        stats = proportions.PersonStats(userdata)

        if stat not in statmap.keys():
            await ctx.send(f"The `{stat}` stat is not an available option.")
            return

        try:
            stat = statmap[stat]
        except KeyError:
            await ctx.send(f"`{stat}` is not a stat.")
            return

        stattosend = stats.getFormattedStat(stat)

        if stattosend is None:
            await ctx.send(f"The `{stat}` stat is unavailable for this user.")
            return

        await ctx.send(stattosend)
        await showNextStep(ctx, userdata)
Beispiel #12
0
    async def lookat(self, ctx, *,
                     what: typing.Union[DigiObject, discord.Member, SV, str]):
        """See what an object looks like to you.

        Used to see how an object would look at your scale.
        Examples:
        `&lookat man`
        `&look book`
        `&examine building`"""

        userdata = getUserdata(ctx.author)
        userstats = proportions.PersonStats(userdata)

        if isinstance(what, DigiObject):
            la = what.relativestatssentence(userdata)
            await ctx.send(la)
            logger.info(
                f"{ctx.author.display_name} looked at {what.article} {what.name}."
            )
            return
        elif isinstance(what, discord.Member) or isinstance(
                what, SV
        ):  # TODO: Make this not literally just a compare. (make a sentence)
            compdata = getUserdata(what, "Raw")
            logger.info(f"{ctx.author.display_name} looked at {what}.")
        elif isinstance(what, str) and what in [
                "person", "man", "average", "average person", "average man",
                "average human", "human"
        ]:
            compheight = userstats.avgheightcomp
            compdata = getUserdata(compheight)
        else:
            await ctx.send(
                f"`{what}` is not a valid object, member, or height.")
            logger.info(
                f"{ctx.author.display_name} tried to look at {what}, but that's invalid."
            )
            return
        stats = proportions.PersonComparison(userdata, compdata)
        embedtosend = stats.toEmbed()
        await ctx.send(embed=embedtosend)
Beispiel #13
0
    async def stat(self,
                   ctx,
                   stat,
                   *,
                   memberOrHeight: typing.Union[discord.Member, SV] = None,
                   customName=None):
        """User stat command.

        Get a single stat about yourself, a user, or a raw height.

        Available stats are: height, weight, foot/feet/shoe, toe, shoeprint/footprint,
        finger/pointer, thumb, nail/fingernail, fingerprint, thread, eye/eyes, hair, tail,
        speed/walk/run, base/baseheight/baseweight, compare/look, scale/multiplier/mult.

        Examples:
        `&stat height` (not specifying a user returns a stat about yourself.)
        `&stat weight @User`
        `&stat foot 10ft`
        """

        statmap = {
            "height": "height",
            "weight": "weight",
            "foot": "foot",
            "feet": "foot",
            "shoe": "foot",
            "shoes": "foot",
            "toe": "toe",
            "shoeprint": "shoeprint",
            "footprint": "shoeprint",
            "finger": "finger",
            "pointer": "finger",
            "thumb": "thumb",
            "nail": "nail",
            "fingernail": "fingernail",
            "fingerprint": "fingerprint",
            "thread": "thread",
            "eye": "eye",
            "eyes": "eye",
            "hair": "hair",
            "tail": "tail",
            "speed": "speed",
            "walk": "speed",
            "run": "speed",
            "base": "base",
            "baseheight": "base",
            "baseweight": "base",
            "compare": "compare",
            "look": "compare",
            "scale": "scale",
            "multiplier": "scale",
            "mult": "scale"
        }

        if memberOrHeight is None:
            memberOrHeight = ctx.author

        userdata = getUserdata(memberOrHeight, customName)

        stats = proportions.PersonStats(userdata)

        logger.info(f"Stat {stat} for {memberOrHeight} sent.")

        if stat not in statmap.keys():
            await ctx.send(f"The `{stat}` stat is not an available option.")
            logger.info(
                f"Tried to send info on stat {stat}, but that's not a valid stat."
            )
            return

        stat = statmap[stat]
        stattosend = stats.getFormattedStat(stat)

        if stattosend is None:
            await ctx.send(f"The `{stat}` stat is unavailable for this user.")
            logger.info(
                f"Tried to send info on stat {stat}, but this user doesn't have it."
            )
            return

        await ctx.send(stattosend)
Beispiel #14
0
    async def royale(self, ctx, subcommand, *, args: str = None):
        """
        Size Royale commands.
        `&royale create [seed]`: create a new game in this guild, optionally with a seed.
        Requires a file upload as per https://github.com/DigiDuncan/SizeRoyale/blob/master/royale-spec.txt
        `&royale next [amount]`: Output the next round of events in the game, default 1.
        `&royale overview`: See a stats screen of all players.
        `&royale stats <player>`: Gets stats about a player in the game.
        `&royale compare <player> <player2>`: Compares two players from the game.
        `&royale delete` or `&royale stop`: Deletes the game in this guild (irreversable)
        """
        global current_games

        if subcommand == "create":
            if is_dm(ctx.author) is False:
                return

            arg1, *arg2 = args.split(" ", 1) if args else (None, (None))
            arg2 = arg2[
                0] if arg2 else None  # This makes split not fail if there's only one element.
            seed = arg1

            if ctx.guild.id in current_games:
                await ctx.send("There is already a game running in this guild!"
                               )
                return
            m = await ctx.send("Creating royale...")

            if not ctx.message.attachments and seed != "test":
                await m.edit(
                    content=
                    "You didn't upload a royale sheet. Please see https://github.com/DigiDuncan/SizeRoyale/blob/master/royale-spec.txt"
                )
                return

            if not ctx.message.attachments and seed == "test":
                data = pkg_resources.read_text(sizebot.data, "royale-spec.txt")
                filename = "test"
                seed = arg2 if arg2 else "radically-key-gazelle"
            else:
                sheet = ctx.message.attachments[0]
                filename = sheet.filename
                if not sheet.filename.endswith(".txt"):
                    await m.edit(
                        content=f"{sheet.filename} is not a `txt` file.")
                    return
                data = (await sheet.read()).decode("utf-8")

            game = Game(seed=seed)
            loop = arrow.now()
            try:
                async for progress in await game.load(data):
                    looppoint = arrow.now()
                    if looppoint - loop >= timedelta(seconds=1):
                        loop = looppoint
                        if progress:
                            await m.edit(
                                content=f"`{progress}` {emojis.loading}")
                if game.royale.parser.errors:
                    await ctx.send(
                        f"{emojis.warning} **Errors in parsing:**\n" +
                        ("\n".join(game.royale.parser.errors)))
                    return
                current_games[ctx.guild.id] = game
                await m.edit(
                    content=
                    f"Royale loaded with file `{filename}` and seed `{current_games[ctx.guild.id].seed}`."
                )
            except Exception:
                await m.edit(content="Game failed to load.")
                raise

        else:
            if ctx.guild.id not in current_games:
                await ctx.send("There is no game running in this guild!")
                return

        if subcommand == "next":
            arg1 = args

            if is_dm(ctx.author) is False:
                return

            loops = int(arg1) if arg1 else 1

            for i in range(loops):
                try:
                    round = await current_games[ctx.guild.id].next()
                except GametimeError as e:
                    await ctx.send(f"Error in running event: {e}")
                    await ctx.send(
                        f"Please check your game file and consider contacting <@{ids.digiduncan}>."
                    )
                    return
                for e in round:
                    data = e.to_embed()
                    # PERMISSION: requires attach_file
                    await ctx.send(embed=data[0], file=data[1])
                    await asyncio.sleep(1)

        elif subcommand == "overview":
            stats = await current_games[ctx.guild.id].stats_screen()
            data = stats.to_embed()
            # PERMISSION: requires attach_file
            await ctx.send(embed=data[0], file=data[1])

        elif subcommand == "stop" or subcommand == "delete":
            if is_dm(ctx.author) is False:
                return

            sentMsg = await ctx.send(
                f"{emojis.warning} **WARNING!** Deleting your game will remove *all progress irrecoverably.* Are you sure?"
                f"To delete your game, react with {emojis.check}.")
            await sentMsg.add_reaction(emojis.check)
            await sentMsg.add_reaction(emojis.cancel)

            # Wait for requesting user to react to sent message with emojis.check or emojis.cancel
            def check(reaction, reacter):
                return reaction.message.id == sentMsg.id \
                    and reacter.id == ctx.message.author.id \
                    and (
                        str(reaction.emoji) == emojis.check
                        or str(reaction.emoji) == emojis.cancel
                    )

            try:
                reaction, user = await self.bot.wait_for("reaction_add",
                                                         timeout=60.0,
                                                         check=check)
            except asyncio.TimeoutError:
                # User took too long to respond
                return
            finally:
                # User took too long OR User clicked the emoji
                await sentMsg.delete()

            # if the reaction isn't the right one, stop.
            if reaction.emoji != emojis.check:
                return

            current_games.pop(ctx.guild.id)

        elif subcommand == "stats":
            arg1 = args

            if arg1.startswith("\"") and arg1.endswith("\""):
                player = arg1[1:-1]
            else:
                await ctx.send(
                    f"Player names must be in quotes. e.g.: `{conf.prefix}royale stats \"DigiDuncan\"`."
                )
                return

            userdata = getPlayerData(ctx.guild.id, player)

            stats = proportions.PersonStats(userdata)

            embedtosend = stats.toEmbed(ctx.author.id)
            await ctx.send(embed=embedtosend)

            logger.log(ROYALE, f"Stats for {player} sent.")

        elif subcommand == "compare":
            arg1 = args

            if match := re.match(r"\"(.*)\"\s*\"(.*)\"", arg1):
                player1 = match.group(1)
                player2 = match.group(2)
            else:
                await ctx.send(
                    f"Player names must be in quotes. e.g.: `{conf.prefix}royale compare \"DigiDuncan\" \"Kelly\"`."
                )
                return

            userdata1 = getPlayerData(player1)
            userdata2 = getPlayerData(player2)

            comparison = proportions.PersonComparison(userdata1, userdata2)
            embedtosend = await comparison.toEmbed(ctx.author.id)
            await ctx.send(embed=embedtosend)
Beispiel #15
0
    async def distance(self, ctx, length: typing.Union[SV, TV, str], *, who: typing.Union[discord.Member, SV] = None):
        """How long will it take to walk, run, or climb a distance?

        Example:
        `&distance <length or time>`"""

        if who is None:
            who = ctx.message.author

        userdata = getUserdata(who)
        userstats = proportions.PersonStats(userdata)

        if userdata.height == 0:
            await ctx.send(f"{userdata.tag} doesn't exist...")
            return

        defaultdata = getUserdata(userdb.defaultheight, "an average person")
        defaultstats = proportions.PersonStats(defaultdata)
        defaultclimblength = Decimal(0.3048)
        defaultclimbspeed = Decimal(4828)
        climblength = Decimal(0.3048) / userdata.viewscale
        climbspeed = Decimal(4828) / userdata.viewscale

        if isinstance(length, str):
            raise errors.InvalidSizeValue(length, "size or time")
        elif isinstance(length, TV):
            walkpersecond = SV(userstats.walkperhour / 3600)
            length = SV(walkpersecond * length)

        defaultwalktimehours = length / defaultstats.walkperhour
        defaultwalksteps = length / defaultstats.walksteplength

        defaultruntimehours = length / defaultstats.runperhour
        defaultrunsteps = length / defaultstats.runsteplength

        defaultclimbtimehours = length / defaultclimbspeed
        defaultclimbsteps = length / defaultclimblength

        newlength = SV(length / userstats.scale)
        walktimehours = length / userstats.walkperhour
        walksteps = length / userstats.walksteplength
        runtimehours = length / userstats.runperhour
        runsteps = length / userstats.runsteplength
        climbtimehours = length / climbspeed
        climbsteps = length / climblength

        walktime = prettyTimeDelta(walktimehours * 60 * 60, roundeventually = True)
        runtime = prettyTimeDelta(runtimehours * 60 * 60, roundeventually = True)
        climbtime = prettyTimeDelta(climbtimehours * 60 * 60, roundeventually = True)

        defaultwalktime = prettyTimeDelta(defaultwalktimehours * 60 * 60, roundeventually = True)
        defaultruntime = prettyTimeDelta(defaultruntimehours * 60 * 60, roundeventually = True)
        defaultclimbtime = prettyTimeDelta(defaultclimbtimehours * 60 * 60, roundeventually = True)

        e = discord.Embed(
            title = f"{length:,.3mu} to {userstats.nickname}",
            description = (
                f"To {userstats.nickname}, {length:,.3mu} would look to be **{newlength:,.3mu}.** "
                f"They could walk that distance in **{walktime}** *({walksteps:,.0f} steps)*, "
                f"run that distance in **{runtime}** *({runsteps:,.0f} strides)*, "
                f"or climb that distance in **{climbtime}** *({climbsteps:,.0f} pulls)*"
            )
        )
        e.set_footer(text = f"An average person could walk {length:,.3mu} in {defaultwalktime} ({defaultwalksteps:,.0f} steps), "
                            f"run that distance in {defaultruntime} ({defaultrunsteps:,.0f} strides), "
                            f"or climb that distance in {defaultclimbtime} ({defaultclimbsteps:,.0f} pulls).")

        await ctx.send(embed = e)