22 Home
Henri Schubin edited this page 2024-10-20 14:52:31 +03:00

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 example org.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/ and command/discord/
    • Exceptions:
      • Configurations in config/
      • Events in events/
      • Exceptions in exception/
      • Util classes in util/
      • Helper classes in helper/
  • 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/ Abstractions
    • command/discord/, command/game/, command/combined/ Discord, game and combined (Discord+game) commands
    • config/ Configs and Configurate classes
    • core/ Core DiscordSRV functionality (components, event bus, dependency loading, module system, logging, placeholder service, storage)
    • feature/ DiscordSRV feature implementations
    • helper/ 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 and Module#reload
  • 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 usually main, connection or messages
  • 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 type ChannelConfig 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:

  1. DiscordSRV#channelConfig instead of DiscordSRV#config
  2. ChannelConfigHelper#get(GameChannel) or ChannelConfigHelper#resolve(DiscordMessageChannel) or ChannelConfigHelper#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 Modules 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)

Illustration of the above

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)
  • 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
  • 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 and DiscordSRVApi methods which don't rely on platform code
    • The 'true' main class of DiscordSRV
  • 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

Illustration of the above:

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 jars) 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.

Illustration of the above