Skip to content

Architecture

Pavel Erokhin edited this page Nov 9, 2026 · 8 revisions

Mo’Bosses is a Paper plugin in Kotlin, and Fabric and NeoForge mods are on their way. The code lives in five Gradle modules, one package per concern under ru.mairwunnx.mobosses. This page maps the modules and the packages and lists the rules that keep the plugin working.

Modules

Module What it holds
core/ What doesn’t touch a game platform: Log, algebra/, config/ but Ids, lang/ but PlayerLanguages and Texts, progress/Store and Tables, combat/Shares, boss/Codec, and every file the plugin copies (the configs, lang/, bundled.sha256). No Bukkit and no Minecraft
paper/ The plugin: everything that calls Bukkit or Paper, and paper-plugin.yml. Its jar is mobosses-paper-X.Y.Z.jar
modcore/ What both mods share, the game without a loader, compiled against NeoForm: one package per Paper package under ru.mairwunnx.mobosses.modcore, each file the twin of Paper’s, the mixins and Hooks where they land, MoBosses for one server, the messages between a player’s game and the server (net/), and the settings screen and loot pages (client/). Its game tests run on both loaders
fabric/, neoforge/ The mods, thin adapters around modcore, for dedicated servers and singleplayer and LAN worlds: the entries, the loader’s events, permissions, and channel, the client entries, and the test mods

The packages stay the same in every module, so a class moves between core and paper without a change to its imports. A class goes to core once nothing in it, and nothing it uses, needs Bukkit.

Packages

Where What it does
MoBossesPlugin.kt Startup, reload(), and shutdown. Reload reads every file again and keeps the plugin, its command, and its bosses running
MoBossesCommand.kt /mobosses and its subcommands, with suggestions
config/ One model per file (Settings, AbilitySettings, Classes, BossSpec, MinionSettings, RewardSettings, LootTable), read by Configs through kaml, not strict. Yamls.decode drops a value that doesn’t fit, with a warning, so it takes its default. Ids resolves game ids against the registries, 1.x enum names included
algebra/ The math, pure: Curves (the shared growth curve), Stats (health, damage, armor, vanilla experience), Leveling (player levels, boss levels, relevance), Rolls (minions, cooldowns, weighted picks)
boss/ Catalog (configs with ids resolved), Bosses (live bosses: make, adopt, forget), BossFactory and Equipment (stats and gear), Boss (the fight state, kept on the entity through Keys and Codec), Minions, NameTags, BossBars, Visuals, Decoys, Despawns
spawn/ SpawnListener (events), NaturalSpawns (the roll and the level), SpawnLimiter (cooldowns per player, the cap on bosses nearby), FullMoon, SurvivalBonus
combat/ DamageListener (shares, bars, abilities on hit), DeathListener (experience, kills, loot, rewards, the kill message), Shares, Loot, Rewards, GroupHealth, SpawnerFatigue
ability/ Ability and its context Cast. Abilities picks one on a hit. kinds/ holds one object per shared ability and signature/ one per boss type’s own. Targets decides who may be hit and where the ground is. Arrows shoots for the arrow abilities
achievement/ The advancements tab: Achievement lists them, Achievements loads and grants them
menu/ Stats (cards, the board, boss counts, from the database off the server thread), Menu, Bestiary, BossList, LanguagePage, MenuListener
progress/ Players’ levels and boss statistics: Progression (a cache filled at login), Store (Exposed over SQLite, MySQL/MariaDB, or PostgreSQL) on a thread of its own, Tables, DeathPenalty
lang/ Message, Languages, LanguageFiles, LocaleResolver, GeoIp, PlayerLanguages, LanguageChoices, Texts (rendering, boss names, classes, minions)
protect/ Guard and Guards, the GriefPrevention and WorldGuard adapters and flags, BlockLog and CoreProtectLog, and BlockChanges with TemporaryBlocks, the only way abilities touch blocks
hooks/ PlaceholderAPI (Expansion, Placeholders), MiniPlaceholders (MiniPlaceholderTags), Plan (PlanData), WorldAPI (BossesExpansion, through WorldAPI’s expansion API 2, com.mairwunnx:worldapi-expansion, and WorldApiLink, which the rest of the plugin calls and which loads without WorldAPI), and Allay (AllayBosses, through Allay’s expansion API 2, chat.allay:allay-expansion, and AllayLink, the same way)

Rules that keep it working

  • Threads. Bukkit calls stay on the server thread. The database and GeoIP downloads run off it. PlaceholderAPI, Plan, and MiniPlaceholders may ask from any thread, so they read only the progress cache and counts the server thread keeps.
  • What a boss is. Any entity that carries mobosses:type, mobosses:class, and mobosses:level. Fight state that must outlive a restart (damage shares, cooldowns, enrage) lives on the boss. Cooldowns that must outlive a relog (rewards, spawns) live on the player. Never keep it only in memory, and never force-load chunks for a boss.
  • Names. Names over mobs are TextDisplay passengers, not saved, invisible by default, and shown per player by NameTags.
  • Ability entities, like meteors and arrows, are isPersistent = false, so a restart never brings one back.
  • Blocks. Abilities change blocks only through BlockChanges.set or setFor. That asks the guards, logs to CoreProtect before the change, in the same tick, and restores temporary blocks on shutdown.
  • Targets. Abilities hit players through Targets, which checks the game mode and Guard.canHurt, and the pets and golems that hit the boss in the last 15 seconds. Nothing else.
  • Never weaker than vanilla. BossFactory keeps every stat at least the mob’s own, and Equipment puts on only stronger pieces of the same kind.
  • Core stays platform-free. Nothing in core imports org.bukkit, io.papermc, or net.minecraft, so the mods can share it.
  • Optional plugins are compileOnly and join-classpath in paper-plugin.yml. Their classes are touched only after checking the plugin is enabled. WorldGuard flags register in onLoad, the only time WorldGuard takes them.
  • Game ids stay strings in the config models and resolve in Catalog. A bad one logs a warning and drops out, never failing the file.
  • Player text goes into messages through Placeholder.unparsed. PlaceholderAPI goes through the <papi:…> tag only.
  • The database. The table player_progress keeps 1.x’s name, columns, and index. Progress changes go through Store.add, which reads and writes in one transaction.

Adding things

  • A new ability. An object in ability/kinds/, listed in Abilities.PICKED, with its keys in a section of its own in AbilitySettings and abilities.yml (as obelisk, chain, doubles, and phase_two do), a threshold in AbilityThresholds and classes.yml, and a message in every language.
  • A new message. A Message entry and a line in every file in core/src/main/resources/lang/. A new boss type, minion, class, or loot id needs its name in every language too.
  • A new config key. The same default in its model as in the bundled file. 1.x files must keep loading (core/src/test/resources/v1/).

ResourcesTest fails when any of these is missing.

Next: Development

Clone this wiki locally