Repository navigation
Stack and architecture
Pavel Erokhin edited this page Oct 19, 2026
·
1 revision
- Kotlin and Gradle Kotlin DSL
- Paper API 26.3 and Adventure MiniMessage
- kotlinx-serialization with kaml for the config and the language files
- MaxMind DB reader for the country database
- Optional, compile-only: PlaceholderAPI, MiniPlaceholders, Plan API
| File | What it does |
|---|---|
MoJoinsPlugin.kt |
Startup and reload(): reads config.yml and lang/, builds the resolver and the effects, schedules GeoIP refreshes |
Settings.kt |
config.yml through kaml, not strict: every field has a default |
Languages.kt |
Message keys, the texts, their native names, and LocaleResolver, which picks a language |
LanguageChoice.kt |
The language a player chose, kept in their persistent data |
GeoIp.kt |
Downloads the country database and looks up countries; lookups never touch the network |
JoinListener.kt |
Country lookup at login, then the join, first-join, and quit lines, rendered per reader |
JoinEffects.kt |
Sounds, the particle ring, the firework, potion effects |
MoJoinsCommand.kt |
/mojoins and its subcommands |
Vanish.kt, Silent.kt
|
Who joins without a line |
Placeholders.kt |
The <papi:name> tag |
MiniPlaceholderTags.kt |
MiniPlaceholders both ways |
PlanData.kt |
The Plan extension |
- Game ids stay strings in
Settings;JoinEffectsresolves them once per load, and a bad one logs a warning instead of failing the config. - A new message gets a
Messageentry and a line in every bundled language. - A new config key gets a default in
Settingsand the same one insrc/main/resources/config.yml. - Player-provided text renders with
Placeholder.unparsed, so a name can’t inject tags. - PlaceholderAPI goes through the
<papi:…>tag only. Running%…%replacement over whole texts would rewrite any percent sign, and its legacy color codes would break MiniMessage. - Join lines go out at
MONITOR, after vanish plugins mark the player (SuperVanish atHIGH, Essentials atHIGHEST). On quit, the vanish mark is read atLOWEST, before Essentials clears it. - The GeoIP download and the lookup run off the server thread.
- MiniPlaceholders and Plan may ask from any thread, so they read only
JoinListener.seen, which the server thread fills.
Next: AI involvement
Mo’Joins on GitHub · Related projects · IP Geolocation by DB-IP, CC BY 4.0