(stats): Basic achievements command.

Wrap `config.emojis` in a new `MapDotProxy` for easier access.
This commit is contained in:
2022-01-27 07:57:33 +02:00
parent 3261781775
commit 08935c08e7
3 changed files with 142 additions and 15 deletions

View File

@@ -33,6 +33,33 @@ class configEmoji(PartialEmoji):
) )
class MapDotProxy:
"""
Allows dot access to an underlying Mappable object.
"""
__slots__ = ("_map", "_converter")
def __init__(self, mappable, converter=None):
self._map = mappable
self._converter = converter
def __getattribute__(self, key):
_map = object.__getattribute__(self, '_map')
if key == '_map':
return _map
if key in _map:
_converter = object.__getattribute__(self, '_converter')
if _converter:
return _converter(_map[key])
else:
return _map[key]
else:
return object.__getattribute__(_map, key)
def __getitem__(self, key):
return self._map.__getitem__(key)
class Conf: class Conf:
def __init__(self, configfile, section_name="DEFAULT"): def __init__(self, configfile, section_name="DEFAULT"):
self.configfile = configfile self.configfile = configfile
@@ -49,9 +76,12 @@ class Conf:
self.section_name = section_name if section_name in self.config else 'DEFAULT' self.section_name = section_name if section_name in self.config else 'DEFAULT'
self.default = self.config["DEFAULT"] self.default = self.config["DEFAULT"]
self.section = self.config[self.section_name] self.section = MapDotProxy(self.config[self.section_name])
self.bot = self.section self.bot = self.section
self.emojis = self.config['EMOJIS'] if 'EMOJIS' in self.config else self.section self.emojis = MapDotProxy(
self.config['EMOJIS'] if 'EMOJIS' in self.config else self.section,
converter=configEmoji.from_str
)
# Config file recursion, read in configuration files specified in every "ALSO_READ" key. # Config file recursion, read in configuration files specified in every "ALSO_READ" key.
more_to_read = self.section.getlist("ALSO_READ", []) more_to_read = self.section.getlist("ALSO_READ", [])

View File

@@ -7,7 +7,7 @@ import discord
from cmdClient.checks import in_guild from cmdClient.checks import in_guild
from LionContext import LionContext from LionContext import LionContext
from meta import client from meta import client, conf
from core import Lion from core import Lion
from data.conditions import NOTNULL, LEQ from data.conditions import NOTNULL, LEQ
from utils.lib import utc_now from utils.lib import utc_now
@@ -41,6 +41,61 @@ class Achievement:
# Current level index in levels. None until calculated by `update`. # Current level index in levels. None until calculated by `update`.
self.level_id: int = None self.level_id: int = None
@staticmethod
def progress_bar(value, minimum, maximum, width=15) -> str:
"""
Build a text progress bar representing `value` between `minimum` and `maximum`.
"""
emojis = conf.emojis
proportion = (value - minimum) / (maximum - minimum)
sections = max(int(proportion * width), 0)
bar = []
# Starting segment
bar.append(str(emojis.progress_left_empty) if sections == 0 else str(emojis.progress_left_full))
# Full segments up to transition or end
if sections >= 2:
bar.append(str(emojis.progress_middle_full) * (sections - 2))
# Transition, if required
if 1 < sections < width:
bar.append(str(emojis.progress_middle_transition))
# Empty sections up to end
bar.append(str(emojis.progress_middle_empty) * (width - max(sections, 1)))
# End section
bar.append(str(emojis.progress_right_empty) if sections < width else str(emojis.progress_right_full))
# Join all the sections together and return
return ''.join(bar)
@property
def progress_text(self) -> str:
"""
A brief textual description of the current progress.
Intended to be overridden by achievement implementations.
"""
if self.next_level:
return f"{int(self.value)}/{self.next_level.threshold}"
else:
return f"{int(self.value)}, at the maximum level!"
def progress_field(self) -> tuple[str, str]:
"""
Builds the progress field for the achievement display.
"""
# TODO: Not adjusted for levels
# TODO: Add hint if progress is empty?
name = f"{self.levels[1].emoji} {self.name} ({self.progress_text})"
value = "**0** {progress_bar} **{threshold}**".format(
progress_bar=self.progress_bar(self.value, self.levels[0].threshold, self.levels[1].threshold),
threshold=self.levels[1].threshold
)
return (name, value)
@classmethod @classmethod
async def fetch(cls, guildid: int, userid: int) -> 'Achievement': async def fetch(cls, guildid: int, userid: int) -> 'Achievement':
""" """
@@ -102,7 +157,7 @@ class Workout(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 50, None), AchievementLevel("Level 1", 50, conf.emojis.active_achievement_1),
] ]
async def _calculate_value(self) -> int: async def _calculate_value(self) -> int:
@@ -120,7 +175,7 @@ class StudyHours(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 1000, None), AchievementLevel("Level 1", 1000, conf.emojis.active_achievement_2),
] ]
async def _calculate_value(self) -> float: async def _calculate_value(self) -> float:
@@ -146,7 +201,7 @@ class StudyStreak(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 100, None) AchievementLevel("Level 1", 100, conf.emojis.active_achievement_3)
] ]
async def _calculate_value(self) -> int: async def _calculate_value(self) -> int:
@@ -225,7 +280,7 @@ class Voting(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 100, None) AchievementLevel("Level 1", 100, conf.emojis.active_achievement_4)
] ]
async def _calculate_value(self) -> int: async def _calculate_value(self) -> int:
@@ -243,7 +298,7 @@ class DaysStudying(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 90, None) AchievementLevel("Level 1", 90, conf.emojis.active_achievement_5)
] ]
async def _calculate_value(self) -> int: async def _calculate_value(self) -> int:
@@ -276,7 +331,7 @@ class TasksComplete(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 1000, None) AchievementLevel("Level 1", 1000, conf.emojis.active_achievement_6)
] ]
async def _calculate_value(self) -> int: async def _calculate_value(self) -> int:
@@ -294,7 +349,7 @@ class ScheduledSessions(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 500, None) AchievementLevel("Level 1", 500, conf.emojis.active_achievement_7)
] ]
async def _calculate_value(self) -> int: async def _calculate_value(self) -> int:
@@ -314,7 +369,7 @@ class MonthlyHours(Achievement):
levels = [ levels = [
AchievementLevel("Level 0", 0, None), AchievementLevel("Level 0", 0, None),
AchievementLevel("Level 1", 100, None) AchievementLevel("Level 1", 100, conf.emojis.active_achievement_8)
] ]
async def _calculate_value(self) -> float: async def _calculate_value(self) -> float:
@@ -347,7 +402,6 @@ class MonthlyHours(Achievement):
self.guildid, self.userid, *months self.guildid, self.userid, *months
) )
cumulative_times = [row[0] for row in data] cumulative_times = [row[0] for row in data]
print(cumulative_times)
times = [nxt - crt for nxt, crt in zip(cumulative_times[1:], cumulative_times[0:])] times = [nxt - crt for nxt, crt in zip(cumulative_times[1:], cumulative_times[0:])]
max_time = max(cumulative_times[0], *times) if len(months) > 1 else cumulative_times[0] max_time = max(cumulative_times[0], *times) if len(months) > 1 else cumulative_times[0]
@@ -377,10 +431,26 @@ async def get_achievements_for(member):
@module.cmd( @module.cmd(
name="achievements", name="achievements",
desc="View your achievement progress!", desc="View your progress towards the achievements!",
group="Statistics", group="Statistics",
) )
@in_guild() @in_guild()
async def cmd_achievements(ctx: LionContext): async def cmd_achievements(ctx: LionContext):
achs = await get_achievements_for(ctx.author) """
await ctx.reply('\n'.join(f"{ach.name}: {ach.level_id}, {ach.value}" for ach in achs)) Usage``:
{prefix}achievements
Description:
View your progress towards attaining the achievement badges shown on your `profile`.
"""
status = await get_achievements_for(ctx.author)
embed = discord.Embed(
title="Achievements",
colour=discord.Colour.orange()
)
for achievement in status:
name, value = achievement.progress_field()
embed.add_field(
name=name, value=value, inline=False
)
await ctx.reply(embed=embed)

View File

@@ -25,3 +25,30 @@ support_link = https://discord.gg/StudyLions
[EMOJIS] [EMOJIS]
lionyay = lionyay =
lionlove = lionlove =
progress_left_empty =
progress_left_full =
progress_middle_empty =
progress_middle_full =
progress_middle_transition =
progress_right_empty =
progress_right_full =
inactive_achievement_1 =
inactive_achievement_2 =
inactive_achievement_3 =
inactive_achievement_4 =
inactive_achievement_5 =
inactive_achievement_6 =
inactive_achievement_7 =
inactive_achievement_8 =
active_achievement_1 =
active_achievement_2 =
active_achievement_3 =
active_achievement_4 =
active_achievement_5 =
active_achievement_6 =
active_achievement_7 =
active_achievement_8 =