(stats): Basic achievements command.
Wrap `config.emojis` in a new `MapDotProxy` for easier access.
This commit is contained in:
@@ -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", [])
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
Reference in New Issue
Block a user