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.")
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)
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.")
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)
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)
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}.")
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)
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)
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)
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)
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)
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)
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)
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)
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)