Ascension Developer Guide
Code organization & structure
Common Modules (most important)
:api
- API code (mostly interfaces, some implementations)
:common
- Contains the bulk of all DiscordSRV code
Platform modules (eg. :bukkit
, :bungee
etc.)
:<platform>
- Platform specific code
:<platform>:loader
(rarely need to be touched)- For jarinjar loading, explained later in this guide
:bukkit:bukkit1_12
,:bukkit:folia
,:bukkit:paper
,:bukkit:spigot
- Implementations for specific server distributions
Misc. modules (rarely need to be touched)
:common:common-api
- This contains the Slf4j implementation which needs to be included in loaders but doesn't need to be accessed by API users
:common:common-unrelocate
compileOnly
classes (not included in the jar) that are relocated to their original package, allowing DiscordSRV code to use unrelocated versions of classes while also relocating that class (for exampleorg.slf4j.Logger
is relocated in DiscordSRV, but Velocity's logger is also that same class unrelocated, so the "unrelocate" class can be used to use original)
:i18n
- Standalone java app to generate a file for Crowdin from the platform's Configuration's
Packages
- Sorted based on their 'concept' with a few exceptions
- Examples:
- Everything related to bans go in
ban/
- Everything related to Discord and game commands go in
command/game/
andcommand/discord/
- Everything related to bans go in
- Exceptions:
- Configurations in
config/
- Events in
events/
- Exceptions in
exception/
- Util classes in
util/
- Helper classes in
helper/
- Configurations in
- Examples:
- Common module top-level packages:
- Due to the amount of packages in common they are split into some top level packages to keep things organized
abstraction/
Abstractionscommand/discord/
,command/game/
,command/combined/
Discord, game and combined (Discord+game) commandsconfig/
Configs and Configurate classescore/
Core DiscordSRV functionality (components, event bus, dependency loading, module system, logging, placeholder service, storage)feature/
DiscordSRV feature implementationshelper/
Classes to reduce amount of duplicate code, and to keep long common code paths in separate classes
Buildscript (Gradle)
Dependencies are using a version catalogue which is defined in the settings.gradle
(Gradle docs)
Some common parts Gradle script stuff is in the buildscript/
folder (including most relocations)
Event bus
To simplify development for api users and DiscordSRV itself, DiscordSRV's event bus (DiscordSRV#eventBus
) is used for listening to DiscordSRV's own events as well as JDA's events.
- The JDA event listener system is blocked in favor of DiscordSRV's own event bus
Additionally the :api
module includes a annotation processor which will cause compile time errors if the @Subscribe
annotation used by the event bus is used incorrectly
Modules
Module
is a type for easily building features and plugin integrations.
Modules...
- can request gateway intents, cache flags and member caching policies (via methods)
- intents are automatically requested based on subscribed events, with a few exceptions
- are enabled and disabled when DiscordSRV reloads based on the module's
isEnabled
method's return value- triggers
Module#enable
,Module#disable
andModule#reload
- triggers
- are automatically subscribed to the event bus
Modules can be used for building plugin integrations such as permissions providers,
- The DiscordSRV class has methods to lookup modules by their type, for example:
PermissionProvider permProvider = discordSRV.getModule(PermissionProvider.class);
- Modules can specify their priorities for lookup via
Module#priority(Class)
Config
DiscordSRV uses Configurate object serialization. Config translation is handled by taking the default config written in English and dumping the useful parts into a file that can be imported into Crowdin (:i18n
module) and then loaded back into DiscordSRV (TranslatedConfigManager
).
DiscordSRV has some useful extra's such as serialization for SendableDiscordMessage.Builder
and the following annotations,
@DefaultOnly
controls parts of the config that will not be added back into the config if they are removed@Order
for precise control over options when inheritance prevents ordering the fields as desired@Untranslated
to specify options and/or comments which shouldn't be translated
Configs are stored under each module in the config.<config name>
package
- Where
<config name>
is usuallymain
,connection
ormessages
- The other sub-packages are used to configure Configurate
Channels config & Channel name priority
To avoid conflicting channel names, the GameChannelLookupEvent
will be used to determine which plugin 'owns' a given channel name, when a plugin isn't provided at the time of looking up a config value.
Unless the plugin is specified in the config with a semicolon between the plugin name and channel name (eg. discordsrv:global
) in which case there can be no conflict.
Example configuration
channels:
global: # <-- This will be used for the first integration to respond to the GameChannelLookupEvent with a "global" channel
...
"discordsrv:global": # <-- This will be used for DiscordSRV's global channel exclusively
...
The channels
option in the main config works like this:
- The value is equivalent to Map<String, BaseChannelConfig>
- Keys other than
default
are of typeChannelConfig
which also contains configuration for channel ids and threads - If a given key doesn't have a value for the requested option it will be looked up from the
default
key instead
To access the channels
config option:
DiscordSRV#channelConfig
instead ofDiscordSRV#config
ChannelConfigHelper#get(GameChannel)
orChannelConfigHelper#resolve(DiscordMessageChannel)
orChannelConfigHelper#resolve(String)
If access to channel ids / threads is needed, check if BaseChannelConfig is a instance of IChannelConfig
, cast to that if it is, and use the methods from that interface.
Placeholder service
To make DiscordSRV as configurable as possible, and to avoid the pains of converting between Minecraft and Discord formatting.
Some points of how the placeholder service works,
- works by giving in a input string and 'context' which will be used by placeholders depending on their requirements
- 'context' can be more than just Minecraft Players, mainly allows using Discord users and server members as context
- includes some special party ticks such as OR on placeholders eg.
%player_display_name|player_name%
(%player_display_name% or %player_name% if the display name isn't present) - recursive placeholders out of the box
%awesomelevels_player_level_{linked_player_name}%
- hooks directly into external plugins such as PlaceholderAPI
- allows declaring placeholders directly in types, example:
@PlaceholderPrefix("player_")
public interface Player {
@Placeholder("name") // This will be %player_name% due to the PlaceholderPrefix
String username();
...
Logging
In order to deal with dependencies which use Slf4j, DiscordSRV has it's own implementation of it, that is relocated to com.discordsrv.dependencies.org.slf4j
(.impl
) which redirects log messages to DiscordSRV's own logger as shown in the below illustration.
DiscordSRV's own logging happens through DiscordSRV#logger
and Module
s and other types should use the NamedLogger
proxy to specify the name of the component the log messages are for. This is the replacement for the Debug
categories in DiscordSRV1.
All log messages flow through DiscordSRVLogger
, which...
- Filters & cleans up log messages (from dependencies)
- Stores them as log files for debugging (with logger names)
- Keeps 3 files, rolling over when DiscordSRV is initialized
- And forwards them to the platform's logger (without logger names)
The main class
The main class structure, from top to bottom
-
DiscordSRVApi
(interface)- In the
:api
module - Exposes limited methods for API users (using API types)
- In the
-
DiscordSRV
(interface)- In the
:common
module - "Upgrades" some of the methods in
DiscordSRVApi
to implementation types (eg.IProfileManager
->ProfileManagerImpl
) - Exposes internal methods that aren't platform dependent
- Nearly everything is accessible from here,
logger()
,config()
,jda()
,httpClient()
,placeholderService()
etc. - The primary type that is passed to (almost) everything via dependency injection
- Avoids the nasty generics of
AbstractDiscordSRV
- Avoids the nasty generics of
- In the
-
AbstractDiscordSRV<B, C, CC, MC>
(abstract class)- In the
:common
module - B is the bootstrap type
- C, CC and MC are the configuration types which will be specified by the PlatformDiscordSRV class
- Implements
DiscordSRV
andDiscordSRVApi
methods which don't rely on platform code - The 'true' main class of DiscordSRV
- In the
-
PlatformDiscordSRV (
BukkitDiscordSRV
,BungeeDiscordSRV
etc.) (regular classes)- In the platform's module (
:bukkit
,:bungee
etc.) - The platform implementation
- Dependency injected into plugin hooks and platform specific implementations
- In the platform's module (
JarInJar loading
Very similar to LuckPerms. Required to safely load dependencies at runtime on platforms that don't expose their classloader to adding new urls (Velocity & Fabric do, and this section does not apply to them)
The :<platform>:loader
module's output file is installed on the server and it contains the .jarinjar
(avoids shadow behavior of flattening jar
s) from the :<platform>
module's output.
The :<platform>:loader
output jar contains,
- the
:api
module's code and it's dependencies (as code from the:<platform>
module and dependencies loaded at runtime cannot be accessed by other plugins), - the bare minimum of code to launch the
:<platform>
module's.jarinjar
.