aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/seasons/evergreen/__init__.py2
-rw-r--r--bot/seasons/evergreen/error_handler.py6
-rw-r--r--bot/seasons/evergreen/fun.py12
-rw-r--r--bot/seasons/evergreen/snakes/__init__.py2
-rw-r--r--bot/seasons/evergreen/snakes/converter.py15
-rw-r--r--bot/seasons/evergreen/snakes/snakes_cog.py99
-rw-r--r--bot/seasons/evergreen/snakes/utils.py119
-rw-r--r--bot/seasons/evergreen/uptime.py12
8 files changed, 167 insertions, 100 deletions
diff --git a/bot/seasons/evergreen/__init__.py b/bot/seasons/evergreen/__init__.py
index db610e7c..ac32c199 100644
--- a/bot/seasons/evergreen/__init__.py
+++ b/bot/seasons/evergreen/__init__.py
@@ -2,4 +2,6 @@ from bot.seasons import SeasonBase
class Evergreen(SeasonBase):
+ """Evergreen Seasonal event attributes."""
+
bot_icon = "/logos/logo_seasonal/evergreen/logo_evergreen.png"
diff --git a/bot/seasons/evergreen/error_handler.py b/bot/seasons/evergreen/error_handler.py
index 47e18a31..dcdbe4e9 100644
--- a/bot/seasons/evergreen/error_handler.py
+++ b/bot/seasons/evergreen/error_handler.py
@@ -9,13 +9,13 @@ log = logging.getLogger(__name__)
class CommandErrorHandler:
- """A error handler for the PythonDiscord server!"""
+ """A error handler for the PythonDiscord server."""
def __init__(self, bot):
self.bot = bot
async def on_command_error(self, ctx, error):
- """Activates when a command opens an error"""
+ """Activates when a command opens an error."""
if hasattr(ctx.command, 'on_error'):
return logging.debug(
@@ -108,5 +108,7 @@ class CommandErrorHandler:
def setup(bot):
+ """Error handler Cog load."""
+
bot.add_cog(CommandErrorHandler(bot))
log.debug("CommandErrorHandler cog loaded")
diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py
index 4da01dd1..f5814a80 100644
--- a/bot/seasons/evergreen/fun.py
+++ b/bot/seasons/evergreen/fun.py
@@ -9,18 +9,15 @@ log = logging.getLogger(__name__)
class Fun:
- """
- A collection of general commands for fun.
- """
+ """A collection of general commands for fun."""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def roll(self, ctx, num_rolls: int = 1):
- """
- Outputs a number of random dice emotes (up to 6)
- """
+ """Outputs a number of random dice emotes (up to 6)."""
+
output = ""
if num_rolls > 6:
num_rolls = 6
@@ -32,7 +29,8 @@ class Fun:
await ctx.send(output)
-# Required in order to load the cog, use the class name in the add_cog function.
def setup(bot):
+ """Fun Cog load."""
+
bot.add_cog(Fun(bot))
log.debug("Fun cog loaded")
diff --git a/bot/seasons/evergreen/snakes/__init__.py b/bot/seasons/evergreen/snakes/__init__.py
index 367aea4d..88793308 100644
--- a/bot/seasons/evergreen/snakes/__init__.py
+++ b/bot/seasons/evergreen/snakes/__init__.py
@@ -6,5 +6,7 @@ log = logging.getLogger(__name__)
def setup(bot):
+ """Snakes Cog load."""
+
bot.add_cog(Snakes(bot))
log.info("Cog loaded: Snakes")
diff --git a/bot/seasons/evergreen/snakes/converter.py b/bot/seasons/evergreen/snakes/converter.py
index c091d9c1..ec9c9870 100644
--- a/bot/seasons/evergreen/snakes/converter.py
+++ b/bot/seasons/evergreen/snakes/converter.py
@@ -13,10 +13,14 @@ log = logging.getLogger(__name__)
class Snake(Converter):
+ """Snake converter for the Snakes Cog."""
+
snakes = None
special_cases = None
async def convert(self, ctx, name):
+ """Convert the input snake name to the closest matching Snake object."""
+
await self.build_list()
name = name.lower()
@@ -56,6 +60,8 @@ class Snake(Converter):
@classmethod
async def build_list(cls):
+ """Build list of snakes from the static snake resources."""
+
# Get all the snakes
if cls.snakes is None:
with (SNAKE_RESOURCES / "snake_names.json").open() as snakefile:
@@ -70,11 +76,14 @@ class Snake(Converter):
@classmethod
async def random(cls):
"""
- This is stupid. We should find a way to
- somehow get the global session into a
- global context, so I can get it from here.
+ Get a random Snake from the loaded resources.
+
+ This is stupid. We should find a way to somehow get the global session into a global context,
+ so I can get it from here.
+
:return:
"""
+
await cls.build_list()
names = [snake['scientific'] for snake in cls.snakes]
return random.choice(names)
diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py
index 57eb7a52..92b28c55 100644
--- a/bot/seasons/evergreen/snakes/snakes_cog.py
+++ b/bot/seasons/evergreen/snakes/snakes_cog.py
@@ -134,12 +134,11 @@ CARD = {
class Snakes:
"""
- Commands related to snakes. These were created by our
- community during the first code jam.
+ Commands related to snakes, created by our community during the first code jam.
More information can be found in the code-jam-1 repo.
- https://gitlab_bot_repo.com/discord-python/code-jams/code-jam-1
+ https://github.com/python-discord/code-jam-1
"""
wiki_brief = re.compile(r'(.*?)(=+ (.*?) =+)', flags=re.DOTALL)
@@ -156,9 +155,8 @@ class Snakes:
# region: Helper methods
@staticmethod
def _beautiful_pastel(hue):
- """
- Returns random bright pastels.
- """
+ """Returns random bright pastels."""
+
light = random.uniform(0.7, 0.85)
saturation = 1
@@ -178,6 +176,7 @@ class Snakes:
Written by juan and Someone during the first code jam.
"""
+
snake = Image.open(buffer)
# Get the size of the snake icon, configure the height of the image box (yes, it changes)
@@ -254,9 +253,8 @@ class Snakes:
@staticmethod
def _snakify(message):
- """
- Sssnakifffiesss a sstring.
- """
+ """Sssnakifffiesss a sstring."""
+
# Replace fricatives with exaggerated snake fricatives.
simple_fricatives = [
"f", "s", "z", "h",
@@ -278,9 +276,8 @@ class Snakes:
return message
async def _fetch(self, session, url, params=None):
- """
- Asyncronous web request helper method.
- """
+ """Asyncronous web request helper method."""
+
if params is None:
params = {}
@@ -290,11 +287,11 @@ class Snakes:
def _get_random_long_message(self, messages, retries=10):
"""
- Fetch a message that's at least 3 words long,
- but only if it is possible to do so in retries
- attempts. Else, just return whatever the last
- message is.
+ Fetch a message that's at least 3 words long, if possible to do so in retries attempts.
+
+ Else, just return whatever the last message is.
"""
+
long_message = random.choice(messages)
if len(long_message.split()) < 3 and retries > 0:
return self._get_random_long_message(
@@ -306,14 +303,16 @@ class Snakes:
async def _get_snek(self, name: str) -> Dict[str, Any]:
"""
- Goes online and fetches all the data from a wikipedia article
- about a snake. Builds a dict that the .get() method can use.
+ Fetches all the data from a wikipedia article about a snake.
+
+ Builds a dict that the .get() method can use.
Created by Ava and eivl.
:param name: The name of the snake to get information for - omit for a random snake
:return: A dict containing information on a snake
"""
+
snake_info = {}
async with aiohttp.ClientSession() as session:
@@ -412,20 +411,21 @@ class Snakes:
async def _get_snake_name(self) -> Dict[str, str]:
"""
Gets a random snake name.
+
:return: A random snake name, as a string.
"""
return random.choice(self.snake_names)
async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list):
"""
- Validate the answer using a reaction event loop
+ Validate the answer using a reaction event loop.
+
:return:
"""
def predicate(reaction, user):
- """
- Test if the the answer is valid and can be evaluated.
- """
+ """Test if the the answer is valid and can be evaluated."""
+
return (
reaction.message.id == message.id # The reaction is attached to the question we asked.
and user == ctx.author # It's the user who triggered the quiz.
@@ -465,7 +465,7 @@ class Snakes:
@locked()
async def antidote_command(self, ctx: Context):
"""
- Antidote - Can you create the antivenom before the patient dies?
+ Antidote - Can you create the antivenom before the patient dies.
Rules: You have 4 ingredients for each antidote, you only have 10 attempts
Once you synthesize the antidote, you will be presented with 4 markers
@@ -480,9 +480,7 @@ class Snakes:
"""
def predicate(reaction_: Reaction, user_: Member):
- """
- Make sure that this reaction is what we want to operate on
- """
+ """Make sure that this reaction is what we want to operate on."""
return (
all((
@@ -610,7 +608,7 @@ class Snakes:
@snakes_group.command(name='draw')
async def draw_command(self, ctx: Context):
"""
- Draws a random snek using Perlin noise
+ Draws a random snek using Perlin noise.
Written by Momo and kel.
Modified by juan and lemon.
@@ -652,12 +650,14 @@ class Snakes:
async def get_command(self, ctx: Context, *, name: Snake = None):
"""
Fetches information about a snake from Wikipedia.
+
:param ctx: Context object passed from discord.py
:param name: Optional, the name of the snake to get information
for - omit for a random snake
Created by Ava and eivl.
"""
+
with ctx.typing():
if name is None:
name = await Snake.random()
@@ -702,11 +702,12 @@ class Snakes:
@locked()
async def guess_command(self, ctx):
"""
- Snake identifying game!
+ Snake identifying game.
Made by Ava and eivl.
Modified by lemon.
"""
+
with ctx.typing():
image = None
@@ -736,10 +737,11 @@ class Snakes:
@snakes_group.command(name='hatch')
async def hatch_command(self, ctx: Context):
"""
- Hatches your personal snake
+ Hatches your personal snake.
Written by Momo and kel.
"""
+
# Pick a random snake to hatch.
snake_name = random.choice(list(utils.snakes.keys()))
snake_image = utils.snakes[snake_name]
@@ -772,6 +774,7 @@ class Snakes:
Written by Samuel.
Modified by gdude.
"""
+
url = "http://www.omdbapi.com/"
page = random.randint(1, 27)
@@ -842,6 +845,7 @@ class Snakes:
This was created by Mushy and Cardium,
and modified by Urthas and lemon.
"""
+
# Prepare a question.
question = random.choice(self.snake_quizzes)
answer = question["answerkey"]
@@ -862,6 +866,8 @@ class Snakes:
@snakes_group.command(name='name', aliases=('name_gen',))
async def name_command(self, ctx: Context, *, name: str = None):
"""
+ Snakifies a username.
+
Slices the users name at the last vowel (or second last if the name
ends with a vowel), and then combines it with a random snake name,
which is sliced at the first vowel (or second if the name starts with
@@ -880,6 +886,7 @@ class Snakes:
This was written by Iceman, and modified for inclusion into the bot by lemon.
"""
+
snake_name = await self._get_snake_name()
snake_name = snake_name['name']
snake_prefix = ""
@@ -932,11 +939,12 @@ class Snakes:
@locked()
async def sal_command(self, ctx: Context):
"""
- Play a game of Snakes and Ladders!
+ Play a game of Snakes and Ladders.
Written by Momo and kel.
Modified by lemon.
"""
+
# check if there is already a game in this channel
if ctx.channel in self.active_sal:
await ctx.send(f"{ctx.author.mention} A game is already in progress in this channel.")
@@ -949,10 +957,8 @@ class Snakes:
@snakes_group.command(name='about')
async def about_command(self, ctx: Context):
- """
- A command that shows an embed with information about the event,
- it's participants, and its winners.
- """
+ """Show an embed with information about the event, its participants, and its winners."""
+
contributors = [
"<@!245270749919576066>",
"<@!396290259907903491>",
@@ -996,10 +1002,11 @@ class Snakes:
@snakes_group.command(name='card')
async def card_command(self, ctx: Context, *, name: Snake = None):
"""
- Create an interesting little card from a snake!
+ Create an interesting little card from a snake.
Created by juan and Someone during the first code jam.
"""
+
# Get the snake data we need
if not name:
name_obj = await self._get_snake_name()
@@ -1034,11 +1041,12 @@ class Snakes:
@snakes_group.command(name='fact')
async def fact_command(self, ctx: Context):
"""
- Gets a snake-related fact
+ Gets a snake-related fact.
Written by Andrew and Prithaj.
Modified by lemon.
"""
+
question = random.choice(self.snake_facts)["fact"]
embed = Embed(
title="Snake fact",
@@ -1049,16 +1057,16 @@ class Snakes:
@snakes_group.command(name='help')
async def help_command(self, ctx: Context):
- """
- This just invokes the help command on this cog.
- """
+ """Invokes the help command for the Snakes Cog."""
+
log.debug(f"{ctx.author} requested info about the snakes cog")
return await ctx.invoke(self.bot.get_command("help"), "Snakes")
@snakes_group.command(name='snakify')
async def snakify_command(self, ctx: Context, *, message: str = None):
"""
- How would I talk if I were a snake?
+ How would I talk if I were a snake.
+
:param ctx: context
:param message: If this is passed, it will snakify the message.
If not, it will snakify a random message from
@@ -1067,6 +1075,7 @@ class Snakes:
Written by Momo and kel.
Modified by lemon.
"""
+
with ctx.typing():
embed = Embed()
user = ctx.message.author
@@ -1100,13 +1109,14 @@ class Snakes:
@snakes_group.command(name='video', aliases=('get_video',))
async def video_command(self, ctx: Context, *, search: str = None):
"""
- Gets a YouTube video about snakes
+ Gets a YouTube video about snakes.
:param ctx: Context object passed from discord.py
:param search: Optional, a name of a snake. Used to search for videos with that name
Written by Andrew and Prithaj.
"""
+
# Are we searching for anything specific?
if search:
query = search + ' snake'
@@ -1141,12 +1151,12 @@ class Snakes:
@snakes_group.command(name='zen')
async def zen_command(self, ctx: Context):
"""
- Gets a random quote from the Zen of Python,
- except as if spoken by a snake.
+ Gets a random quote from the Zen of Python, except as if spoken by a snake.
Written by Prithaj and Andrew.
Modified by lemon.
"""
+
embed = Embed(
title="Zzzen of Pythhon",
color=SNAKE_COLOR
@@ -1168,6 +1178,7 @@ class Snakes:
@card_command.error
@video_command.error
async def command_error(self, ctx, error):
+ """Local error handler for the Snake Cog."""
embed = Embed()
embed.colour = Colour.red()
@@ -1190,5 +1201,7 @@ class Snakes:
def setup(bot):
+ """Snake Cog load."""
+
bot.add_cog(Snakes(bot))
log.info("Cog loaded: Snakes")
diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py
index 605c7ef3..ba2068d5 100644
--- a/bot/seasons/evergreen/snakes/utils.py
+++ b/bot/seasons/evergreen/snakes/utils.py
@@ -1,8 +1,3 @@
-"""
-Perlin noise implementation.
-Taken from: https://gist.github.com/eevee/26f547457522755cb1fb8739d0ea89a1
-Licensed under ISC
-"""
import asyncio
import io
import json
@@ -117,43 +112,54 @@ ANGLE_RANGE = math.pi * 2
def get_resource(file: str) -> List[dict]:
+ """Load Snake resources JSON."""
+
with (SNAKE_RESOURCES / f"{file}.json").open() as snakefile:
return json.load(snakefile)
def smoothstep(t):
- """Smooth curve with a zero derivative at 0 and 1, making it useful for
- interpolating.
- """
+ """Smooth curve with a zero derivative at 0 and 1, making it useful for interpolating."""
+
return t * t * (3. - 2. * t)
def lerp(t, a, b):
"""Linear interpolation between a and b, given a fraction t."""
+
return a + t * (b - a)
class PerlinNoiseFactory(object):
- """Callable that produces Perlin noise for an arbitrary point in an
- arbitrary number of dimensions. The underlying grid is aligned with the
- integers.
- There is no limit to the coordinates used; new gradients are generated on
- the fly as necessary.
+ """
+ Callable that produces Perlin noise for an arbitrary point in an arbitrary number of dimensions.
+
+ The underlying grid is aligned with the integers.
+
+ There is no limit to the coordinates used; new gradients are generated on the fly as necessary.
+
+ Taken from: https://gist.github.com/eevee/26f547457522755cb1fb8739d0ea89a1
+ Licensed under ISC
"""
def __init__(self, dimension, octaves=1, tile=(), unbias=False):
- """Create a new Perlin noise factory in the given number of dimensions,
- which should be an integer and at least 1.
- More octaves create a foggier and more-detailed noise pattern. More
- than 4 octaves is rather excessive.
- ``tile`` can be used to make a seamlessly tiling pattern. For example:
+ """
+ Create a new Perlin noise factory in the given number of dimensions.
+
+ dimension should be an integer and at least 1.
+
+ More octaves create a foggier and more-detailed noise pattern. More than 4 octaves is rather excessive.
+
+ ``tile`` can be used to make a seamlessly tiling pattern.
+ For example:
pnf = PerlinNoiseFactory(2, tile=(0, 3))
- This will produce noise that tiles every 3 units vertically, but never
- tiles horizontally.
- If ``unbias`` is true, the smoothstep function will be applied to the
- output before returning it, to counteract some of Perlin noise's
- significant bias towards the center of its output range.
+
+ This will produce noise that tiles every 3 units vertically, but never tiles horizontally.
+
+ If ``unbias`` is true, the smoothstep function will be applied to the output before returning
+ it, to counteract some of Perlin noise's significant bias towards the center of its output range.
"""
+
self.dimension = dimension
self.octaves = octaves
self.tile = tile + (0,) * dimension
@@ -166,8 +172,11 @@ class PerlinNoiseFactory(object):
self.gradient = {}
def _generate_gradient(self):
- # Generate a random unit vector at each grid point -- this is the
- # "gradient" vector, in that the grid tile slopes towards it
+ """
+ Generate a random unit vector at each grid point.
+
+ This is the "gradient" vector, in that the grid tile slopes towards it
+ """
# 1 dimension is special, since the only unit vector is trivial;
# instead, use a slope between -1 and 1
@@ -184,9 +193,8 @@ class PerlinNoiseFactory(object):
return tuple(coord * scale for coord in random_point)
def get_plain_noise(self, *point):
- """Get plain noise for a single point, without taking into account
- either octaves or tiling.
- """
+ """Get plain noise for a single point, without taking into account either octaves or tiling."""
+
if len(point) != self.dimension:
raise ValueError("Expected {0} values, got {1}".format(
self.dimension, len(point)))
@@ -234,9 +242,12 @@ class PerlinNoiseFactory(object):
return dots[0] * self.scale_factor
def __call__(self, *point):
- """Get the value of this Perlin noise function at the given point. The
- number of values given should match the number of dimensions.
"""
+ Get the value of this Perlin noise function at the given point.
+
+ The number of values given should match the number of dimensions.
+ """
+
ret = 0
for o in range(self.octaves):
o2 = 1 << o
@@ -281,6 +292,7 @@ def create_snek_frame(
) -> Image:
"""
Creates a single random snek frame using Perlin noise.
+
:param perlin_factory: the perlin noise factory used. Required.
:param perlin_lookup_vertical_shift: the Perlin noise shift in the Y-dimension for this frame
:param image_dimensions: the size of the output image.
@@ -288,14 +300,15 @@ def create_snek_frame(
:param snake_length: the length of the snake, in segments.
:param snake_color: the color of the snake.
:param bg_color: the background color.
- :param segment_length_range: the range of the segment length. Values will be generated inside this range, including
- the bounds.
+ :param segment_length_range: the range of the segment length. Values will be generated inside
+ this range, including the bounds.
:param snake_width: the width of the snek, in pixels.
:param text: the text to display with the snek. Set to None for no text.
:param text_position: the position of the text.
:param text_color: the color of the text.
:return: a PIL image, representing a single frame.
"""
+
start_x = random.randint(image_margins[X], image_dimensions[X] - image_margins[X])
start_y = random.randint(image_margins[Y], image_dimensions[Y] - image_margins[Y])
points = [(start_x, start_y)]
@@ -349,6 +362,8 @@ def create_snek_frame(
def frame_to_png_bytes(image: Image):
+ """Convert image to byte stream."""
+
stream = io.BytesIO()
image.save(stream, format='PNG')
return stream.getvalue()
@@ -371,6 +386,8 @@ GAME_SCREEN_EMOJI = [
class SnakeAndLaddersGame:
+ """Snakes and Ladders game Cog."""
+
def __init__(self, snakes, context: Context):
self.snakes = snakes
self.ctx = context
@@ -393,10 +410,10 @@ class SnakeAndLaddersGame:
Listen for reactions until players have joined,
and the game has been started.
"""
+
def startup_event_check(reaction_: Reaction, user_: Member):
- """
- Make sure that this reaction is what we want to operate on
- """
+ """Make sure that this reaction is what we want to operate on."""
+
return (
all((
reaction_.message.id == startup.id, # Reaction is on startup message
@@ -471,6 +488,13 @@ class SnakeAndLaddersGame:
self.avatar_images[user.id] = im
async def player_join(self, user: Member):
+ """
+ Handle players joining the game.
+
+ Prevent player joining if they have already joined, if the game is full, or if the game is
+ in a waiting state.
+ """
+
for p in self.players:
if user == p:
await self.channel.send(user.mention + " You are already in the game.", delete_after=10)
@@ -491,6 +515,13 @@ class SnakeAndLaddersGame:
)
async def player_leave(self, user: Member):
+ """
+ Handle players leaving the game.
+
+ Leaving is prevented if the user initiated the game or if they weren't part of it in the
+ first place.
+ """
+
if user == self.author:
await self.channel.send(
user.mention + " You are the author, and cannot leave the game. Execute "
@@ -515,6 +546,8 @@ class SnakeAndLaddersGame:
await self.channel.send(user.mention + " You are not in the match.", delete_after=10)
async def cancel_game(self, user: Member):
+ """Allow the game author to cancel the running game."""
+
if not user == self.author:
await self.channel.send(user.mention + " Only the author of the game can cancel it.", delete_after=10)
return
@@ -522,6 +555,13 @@ class SnakeAndLaddersGame:
self._destruct()
async def start_game(self, user: Member):
+ """
+ Allow the game author to begin the game.
+
+ The game cannot be started if there aren't enough players joined or if the game is in a
+ waiting state.
+ """
+
if not user == self.author:
await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10)
return
@@ -540,10 +580,11 @@ class SnakeAndLaddersGame:
await self.start_round()
async def start_round(self):
+ """Begin the round."""
+
def game_event_check(reaction_: Reaction, user_: Member):
- """
- Make sure that this reaction is what we want to operate on
- """
+ """Make sure that this reaction is what we want to operate on."""
+
return (
all((
reaction_.message.id == self.positions.id, # Reaction is on positions message
@@ -634,6 +675,8 @@ class SnakeAndLaddersGame:
await self._complete_round()
async def player_roll(self, user: Member):
+ """Handle the player's roll."""
+
if user.id not in self.player_tiles:
await self.channel.send(user.mention + " You are not in the match.", delete_after=10)
return
diff --git a/bot/seasons/evergreen/uptime.py b/bot/seasons/evergreen/uptime.py
index 1321da19..d6b59a69 100644
--- a/bot/seasons/evergreen/uptime.py
+++ b/bot/seasons/evergreen/uptime.py
@@ -10,18 +10,15 @@ log = logging.getLogger(__name__)
class Uptime:
- """
- A cog for posting the bots uptime.
- """
+ """A cog for posting the bot's uptime."""
def __init__(self, bot):
self.bot = bot
@commands.command(name="uptime")
async def uptime(self, ctx):
- """
- Returns the uptime of the bot.
- """
+ """Responds with the uptime of the bot."""
+
difference = relativedelta(start_time - arrow.utcnow())
uptime_string = start_time.shift(
seconds=-difference.seconds,
@@ -32,7 +29,8 @@ class Uptime:
await ctx.send(f"I started up {uptime_string}.")
-# Required in order to load the cog, use the class name in the add_cog function.
def setup(bot):
+ """Uptime Cog load."""
+
bot.add_cog(Uptime(bot))
log.debug("Uptime cog loaded")