Imported from CloudieSMP/CSystem (
AGENTS.md). Install upstream withnpx skills add CloudieSMP/CSystem. Copyright stays with the author.
AGENTS.md — Cloudie SMP System Plugin
A Paper 1.21.11 plugin for Cloudie SMP Season 10, written in Kotlin. It acts as the all-in-one server management plugin: commands, chat, crates, homes, mail, resource packs, and Discord integration.
Build & Run
./gradlew shadowJar # Build fat JAR → build/libs/csystem-INDEV-<hash>-all.jar
./gradlew runServer # Spin up a local Paper test server under run/
- Version is auto-derived from the short git commit hash (
INDEV-<hash>). - The shadow JAR must be used (not the plain JAR) — Cloud, Configurate, and FastBoard are relocated into
moe.oof.csystem.shade.*. - Relocated packages:
org.incendo,org.spongepowered,fr.mrmicky. - Java toolchain: JVM 25.
Architecture
CSystem.kt — JavaPlugin entry point; wires events, Cloud command manager, command confirmation, config
Config.kt — Spongepowered Configurate data class (mapped from src/main/resources/config.yml)
command/ — One class per command, all discovered via Cloud's annotationParser.parseContainers()
event/ — Bukkit event listeners (player/, block/, entity/)
library/ — Stateful singletons: MailStorage, CrateRollStatsStorage, CardPullCounterStorage, AfkHelper, LiveHelper, NoSleepHelper, HelpHelper, PlayerListNameHelper, GhostMode, TagHelper
item/ — Enums for rarities/types; crate/, booster/, binder/, treasurebag/ sub-packages
util/ — Extensions, Keys registry, resource pack/webhook helpers, UI windows, Sounds
chat/ — MiniMessage formatting, notifications, ChatUtility broadcasts
Adding a Command
- Create a class in
command/annotated with@CommandContainer. - Annotate methods with
@Command,@Permission,@CommandDescription. - Use
css.requirePlayer()(extension inutil/CommandSourceStackExtensions.kt) to guard player-only commands. - No registration needed —
annotationParser.parseContainers()inCSystem.ktauto-discovers all@CommandContainerclasses via kapt. - Declare the permission node in
src/main/resources/paper-plugin.ymland add it to the appropriate group. - Add the command's help entry to
library/HelpHelper.kt(eithercommandsmap for player commands orstaffCommandsmap for staff commands).
Example skeleton:
@Suppress("unused")
@CommandContainer
class MyCommand {
@Command("mycommand")
@CommandDescription("Does something cool.")
@Permission("cloudie.cmd.mycommand")
fun run(css: CommandSourceStack) {
val player = css.requirePlayer() ?: return
player.sendMessage(Formatting.allTags.deserialize("<cloudiecolor>Hello!"))
}
}
Text Formatting
Always use Formatting.allTags for trusted/system messages and Formatting.restrictedTags for player-input messages.
Custom MiniMessage tags available:
| Tag | Meaning |
|---|---|
<cloudiecolor> |
Pink brand colour (#C45889) |
<notifcolor> |
Notification red (#DB0060) |
<prefix:NAME> |
Unicode glyph prefix (e.g. admin, dev, live, warning, nosleep) |
<skull:PLAYERNAME> |
Player skull glyph |
Hardcoded join/quit message templates live in library/Translation.kt.
Storage Pattern
MailStorage and CrateRollStatsStorage follow the same async-read/sync-flush pattern:
- In-memory
ConcurrentHashMapcache per player UUID. - Async reads via callback — disk I/O is async, callback is rescheduled onto the Bukkit main thread.
- Sync flush on plugin disable:
flushAllSync(). - Data files live in
plugins/System/mail/<uuid>.yml,plugins/System/crate-roll-stats/<uuid>.yml, etc. - Call
preload(uuid)on player join to warm the cache early (done inevent/player/PlayerJoin.kt). CardPullCounterStorageis separate: global sync load/save inplugins/System/card-pulls.yml.TagHelperuses a sync variant:loadSync()on startup,flushAllSync()on disable, withscheduleSave()(async) after in-game writes. Data inplugins/System/tags.yml; tag events also appended toplugins/System/tag-log.txt.
Item System
- All
NamespacedKeyvalues are centralized inutil/Keys.kt. - Custom item models use
DataComponentTypes.ITEM_MODELwith keys likeNamespacedKey("cloudie", "crates/blue")— the path maps to the server resource pack. ItemRarityholds display color + Unicode glyph;CardRarityextends this with weighted drop probabilities and broadcast behavior.- Crate items are
Material.PAPERwith food/consumable data components to make them right-click-activatable without placing. - Sub-rarities (
item/SubRarity.kt): Cards can rollSHINY,SHADOW, orOBFUSCATEDvariants (each with a Unicode glyph and amodelDataOffsetapplied on top of the base model). UseSubRarity.getRandomSubRarity(). Debug weights can be overridden viaSubRarity.setDebugWeights(...). - Card Registry (
item/booster/CardRegistry.kt): The single source of truth for all trading cards. Add or modify cards here —CardEntry(type, rarity, canHaveSubRarity, allowedBoosters). MOB card IDs must match BukkitEntityTypekeys. After editing the registry, run/pack export cardmodelsto regenerate resource pack item definitions. - Treasure bags (
item/treasurebag/): Bundle-based items (BundleMeta) created byTreasureBag.create(type). Loot is defined inBagLootPoolwith per-item percentage roll chances and amount ranges. - Vending machines: Entities tagged
vending_machine(scoreboard tag) are handled byevent/entity/VendingMachineInteract.kt. Interacting while holding a specific material consumes it and spawns a booster pack or crate as a dropped item. - Plushie Box (
item/plushiebox/): PDC-backed storage item holding crate collectibles (max 256). Created viaPlushieBox.create(); contents stored asBYTE_ARRAYlists underKeys.PLUSHIE_BOX_ITEMS. Opened by right-click →PlushieBoxWindow. Crafted from any bundle + wool. - Cosmetic system: A CrateItem can be overlaid onto any helmet via an anvil (slot 0 = helmet, slot 1 = CrateItem). The result inherits the CrateItem's
ITEM_MODELand has its PDC keys copied flat. Reversed with/stripcosmetic. The sameAnvilListeneralso enables Sweeping Edge to be applied to hoes. - Material helpers are centralized in
util/Materials.kt(HOE_MATERIALS,HELMET_MATERIALS,STORAGE_INVENTORY_TYPES).
Sounds
All gameplay sounds are centralized in util/Sounds.kt as Sound constants (Adventure API). Use these instead of inline sound(...) calls:
player.playSound(Sounds.PLING)
player.playSound(Sounds.SHINY_CATCH)
Notable entries: EPIC_CATCH, LEGENDARY_CATCH, LEGENDARY_CATCH_EXPLODE, MYTHIC_CATCH, UNREAL_CATCH, TRANSCENDENT_CATCH, CELESTIAL_CATCH, SHINY_CATCH, SHADOW_CATCH, OBFUSCATED_CATCH, VENDING_MACHINE, ERROR_DIDGERIDOO, GAMBLING_WHEEL_TICK/STOP, INTERFACE_INTERACT, INTERFACE_ENTER_SUB_MENU, INTERFACE_BACK, INTERFACE_ERROR.
UI Windows
Inventory GUIs use the Noxcrew Interfaces library (util/ui/). There are two shared GUI engines:
StorageWindow— generic interactive insert/remove storage with pagination, filterable views, and an optional show-missing toggle. Used byBinderWindow(card binder) andPlushieBoxWindow(plushie box). PasscanInsert,onSave,filters, and optionaluniqueKey/onRemovecallbacks.CollectionBrowserWindow— read-only selector/preview browser, used byCrateBrowserWindowandBoosterPackBrowserWindow.- Listener-backed windows are
GamblingWindowandTrashWindow(object : Listener, registered at startup). - Interface windows keep reactive state in closures/triggers (see
DelegateTriggerusage inStorageWindow/CollectionBrowserWindow), whileGamblingWindowtracks sessions in-memory by player UUID.
AFK & Tab List
library/AfkHelper.kt: Tracks AFK state per UUID. CallAfkHelper.recordActivity(player)on any meaningful input. Idle timeout is configured inconfig.afk.idleTimeoutSeconds; the checker runs every 30 seconds viastartIdleChecker()(called at startup).library/LiveHelper.kt: Tracks streamer/live state per UUID. CallLiveHelper.startLive(player)/LiveHelper.stopLive(player). Automatically displays a live glyph next to the player's display name and updates the tab list. Players retain their live status for 10 minutes after disconnecting.library/NoSleepHelper.kt: Tracks which players have the NoSleep tag enabled. CallNoSleepHelper.setNoSleep(player, bool). While any player has NoSleep active, bed interactions are blocked for others.library/PlayerListNameHelper.kt: Updates the player's tab-list name whenever AFK/Live/NoSleep state changes. AFK → gray name, Live → pink name +<prefix:live>, NoSleep →<prefix:nosleep>prefix. CallPlayerListNameHelper.apply(player)after any state change.library/GhostMode.kt: Toggles ghost-mode per player (in-memorymutableSetOf<Player>). Ghost players are hidden from all others except viewers who are looking at them from a peripheral angle (52.5°–62.5° off centre). Updated every 2 ticks via aBukkitRunnable. Ghost state clears automatically on disconnect. CallGhostMode.toggleGhostMode(player).library/TagHelper.kt: Tag-your-it mini-game state. StoresPlayerTagStats(tagged/tagger/timestamps/counts) per UUID. CallTagHelper.ensurePlayer(player)on join,TagHelper.startTagging(tagger, taggee)to execute a tag. Cooldowns are configured underconfig.tagYourIt(cooldownSeconds,cooldownBackTaggingSeconds).
Config
Config is loaded via Spongepowered Configurate from src/main/resources/config.yml into the Config data class. Access it via plugin.config (the field is named config on the CSystem class, shadowing JavaPlugin.getConfig()). Reload at runtime with /cloudie reload (permission cloudie.cmd.reload).
Notable config keys beyond the top-level defaults:
| Key | Description |
|---|---|
motd |
Server MOTD string (MiniMessage). Applied via Bukkit.serverLinks on load/reload. |
links |
List of Link(component, uri, order) entries added to the server links panel. |
resourcePacks |
List of ResourcePack(githubUrl, branch, zipName, priority) entries. |
afk.idleTimeoutSeconds |
Seconds before a player is marked AFK (default 300). |
rainCropGrowth.boostChance |
Probability (0.0–1.0) a sky-exposed crop gets a bonus growth tick during rain (default 0.5). |
showStat.secondsPerPage |
How long each scoreboard page is displayed in seconds (default 7). |
tagYourIt.cooldownSeconds |
Minimum seconds "it" must be held before tagging again (default 30). |
tagYourIt.cooldownBackTaggingSeconds |
Back-tag lockout in seconds (default 86400 = 24 h). |
External Integrations
| Integration | Where |
|---|---|
| Discord reports webhook | util/DiscordWebhook.kt — Ktor CIO, URL in config.discord.reportWebhookUrl |
| Resource pack CDN | util/ResourcePacker.kt — downloads & SHA-1 hashes packs on startup; reapplied on join |
| FastBoard (scoreboard) | fr.mrmicky:fastboard — relocated |
| Resource pack card model export | util/MobCardModelExporter.kt — generates assets/minecraft/items/paper.json dispatch entries and texture placeholder PNGs; triggered via /pack export cardmodels |
Top-level Convenience Accessors
plugin and logger are top-level vals (defined in CSystem.kt) that delegate to the plugin instance — use them freely anywhere instead of passing the plugin reference around.