Skip to content

Stack and architecture

Pavel Erokhin edited this page Oct 19, 2026 · 1 revision

Stack

  • 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

Files

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

Rules that keep it working

  • Game ids stay strings in Settings; JoinEffects resolves them once per load, and a bad one logs a warning instead of failing the config.
  • A new message gets a Message entry and a line in every bundled language.
  • A new config key gets a default in Settings and the same one in src/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 at HIGH, Essentials at HIGHEST). On quit, the vanish mark is read at LOWEST, 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

Clone this wiki locally