Skip to content

Repository files navigation

RSFramework

Modular Bukkit/Paper plugin development framework.

VersionJavaBukkitVelocityLicense


Download



🇰🇷 한국어 문서 | 🇬🇧 English Docs


Project Structure

RSFramework/
├── Bridge/ Inter-server communication broker
│ ├── Common/ Bridge interface, BridgeChannel
│ ├── Proxium/ Netty-based direct proxy communication
│ │ ├── Common/ Public API & Internal implementation
│ │ ├── Bukkit/ Bukkit-side implementation
│ │ └── Velocity/ Velocity-side implementation
│ └── Redisson/ Redis implementation
├── Configurate/ YAML object mapping wrapper
├── Framework/ Framework core
│ ├── API/ RSPlugin, RSCommand, RSListener
│ ├── Core/ Internal implementation
│ └── NMS/ Version-specific NMS adapters (1.20 R1 ~ 1.21 R7)
├── LightDI/ Lightweight DI container
├── Platform/ Platform adapters
│ ├── Folia/
│ ├── Paper/
│ ├── Spigot/
│ └── Velocity/
├── Storage/ Unified storage system
│ ├── Common/ Common API
│ ├── Json/
│ ├── MariaDB/
│ ├── MongoDB/
│ ├── MySQL/
│ ├── PostgreSQL/
│ └── SQLite/
└── docs/ Technical documentation
├── en/ English documentation
└── kr/ Korean documentation

Build output: ./gradlew shadowJarbuilds/plugin/RSFramework-{version}.jar


Common Fields

RSCommand, RSListener, RSInventory share identical protected final fields.

FieldTypeDescription
pluginTOwning plugin instance
frameworkFrameworkFramework core
messageMessageTranslationi18n message translation
commandCommandTranslationi18n command translation
notifierNotifierMessage sending utility

RSCommand additionally provides sender, player, audience fields.

// ✅ Direct field accessplugin.reloadConfiguration(MyConfig.class);
notifier.announce(player, "Done!");

Dependency

repositories {
maven {
name ="RTUStudio"
url = uri("https://repo.codemc.io/repository/rtustudio/")
}
}
dependencies {
compileOnly("kr.rtustudio:framework-api:4.7.17")
}

Getting Started

importkr.rtustudio.framework.bukkit.api.RSPlugin;
publicclassMyPluginextendsRSPlugin {
@Overrideprotectedvoidenable() {
registerConfiguration(PerkConfig.class, ConfigPath.of("Perk"));
registerCommand(newMainCommand(this), true);
registerEvent(newPlayerAttack(this));
}
@Overrideprotectedvoiddisable() { }
}

Lifecycle: onLoadinitialize()load()onEnableenable()onDisabledisable()

Auto-logging: Console messages are automatically printed on enable/disable/reload.


Event Listeners

publicclassJoinListenerextendsRSListener<MyPlugin> {
publicJoinListener(MyPluginplugin) {
super(plugin);
}
@EventHandlerpublicvoidonJoin(PlayerJoinEventevent) {
notifier.announce(event.getPlayer(), "<green>Welcome to the server!");
}
}

Command System

Supports hierarchical structure, auto-permission registration, cooldowns, and tab completion.

publicclassMainCommandextendsRSCommand<MyPlugin> {
publicMainCommand(MyPluginplugin) {
super(plugin, "test", PermissionDefault.OP);
registerCommand(newSubCommand(plugin));
}
@OverrideprotectedResultexecute(CommandArgsdata) {
notifier.announce("Main command executed!");
returnResult.SUCCESS;
}
@Overrideprotectedvoidreload(CommandArgsdata) {
plugin.reloadConfiguration(TestConfig.class);
}
}

registerCommand(cmd, true) — automatically adds /{command} reload.

Execution Results

ResultFramework Behavior
SUCCESS / FAILURENone
ONLY_PLAYER / ONLY_CONSOLEAuto info message
NO_PERMISSIONAuto info message
NOT_FOUND_ONLINE_PLAYER / NOT_FOUND_OFFLINE_PLAYERAuto info message
WRONG_USAGEAuto-show subcommands

i18n Translation

Translation/Command/{language}.yml:

test:
name: "test"description: "A test command"usage: "/test"commands:
sub:
name: "sub"

Configuration Management

Configurate-based YAML object mapping.

@Getter@SuppressWarnings({
"unused",
"CanBeFinal",
"FieldCanBeLocal",
"FieldMayBeFinal",
"InnerClassMayBeStatic"
})
publicclassMyConfigextendsConfigurationPart {
@Comment("Welcome message")
privateStringwelcomeMessage = "<green>Welcome!";
@Min(1)
@Comment("Maximum players")
privateintmaxPlayers = 100;
publicConnectionconnection;
@GetterpublicclassConnectionextendsConfigurationPart {
privateStringhost = "127.0.0.1";
privateintport = 25565;
}
}

Reload-safe Collection Helpers

ConfigurationPart provides mutable collection factories that are safe during configuration reload.

Note: Using immutable collections like List.of(), Map.of() as config defaults will cause UnsupportedOperationException on reload. Always use listOf(), mapOf() helpers.

publicclassWhitelistConfigextendsConfigurationPart {
// varargs styleprivateList<String> commands = listOf("help", "spawn");
// Consumer style (complex initialization)privateMap<String, List<String>> groups = mapOf(map -> {
map.put("default", listOf("help"));
map.put("worldedit", listOf("/wand", "/copy"));
});
// key-value styleprivateMap<String, String> aliases = mapOf("h", "help", "s", "spawn");
}

Registration

@Overrideprotectedvoidinitialize() {
registerConfiguration(MyConfig.class, ConfigPath.of("Setting"));
registerConfigurations(RegionConfig.class, ConfigPath.of("Regions"));
}

Details → docs/en/configuration.md


i18n Support

Stringmsg = message.get(player, "error.no-money");
notifier.announce(player, msg);

Messaging (Notifier)

MiniMessage format. Supports chat, action bar, title, boss bar, and cross-server broadcast.

notifier.announce(player, "<aqua>Item received!"); // with prefixnotifier.send(player, "<yellow>Warning message"); // without prefixnotifier.title(player, "<bold><gold>Level Up!", "<gray>New skill unlocked");
Notifier.broadcastAll("<green>A new event has started!");

Bridge Communication

Supports inter-server Pub/Sub broadcast and RPC request-response.

Bridge (isConnected + close)
├── Broadcast (register · subscribe · publish · unsubscribe)
├── Transaction (request · respond · getRequestTimeout)
│
├── Redis extends Broadcast
└── Proxium extends Broadcast, Transaction

Architecture

LayerRole
Proxium (Bridge)Pure communication infrastructure — packet serialization, Netty transport, TLS
RSFramework BukkitApplication logic — teleport execution, message handling
RSFramework VelocityTeleport routing — server transfer coordination

Pub/Sub

Proxiumproxium = getBridge(Proxium.class);
BridgeChannelchannel = BridgeChannel.of("myplugin", "shop");
// Type-specific subscription (multiple types can be registered individually)proxium.subscribe(channel, BuyRequest.class, buy -> {
getLogger().info(buy.playerName() + " requested a purchase.");
});
// Publishproxium.publish(channel, newBuyRequest("ipecter", "DIAMOND", 64));

RPC

// Response server (data holder)proxium.respond(channel)
.on(BalanceRequest.class, (sender, req) -> {
returnnewBalanceResponse(req.uuid(), getBalance(req.uuid()));
})
.error(e -> log.error("RPC failed: " + e.getMessage()));
// Request server (needs data)proxium.request("Survival-1", channel, newBalanceRequest(uuid))
.on(BalanceResponse.class, (sender, res) -> {
player.sendMessage("Balance: " + res.balance());
})
.error(e -> player.sendMessage("Request failed: " + e.type()));

Network Player Query

for (ProxyPlayerp : proxium.getPlayers().values()) {
System.out.println(p.getName() + " → " + p.getServer());
}

Redis — Distributed Locking

Redisredis = getBridge(Redis.class);
redis.withLock("player-data-save", () -> { /* safe save */ });

Details → docs/en/bridge.md


Storage

Unified JSON document-based API managing all databases with an identical interface.

Supported: JSON, SQLite, MySQL, MariaDB, PostgreSQL, MongoDB

registerStorage("PlayerData", StorageType.MARIADB);
Storagestorage = getStorage("PlayerData");
// Insertstorage.add(JSON.of("uuid", uuid.toString()).append("name", "IPECTER").append("coins", 1000));
// Querystorage.get(JSON.of("uuid", uuid.toString())).thenAccept(results -> {
if (!results.isEmpty()) {
intcoins = results.get(0).get("coins").getAsInt();
}
});
// Updatestorage.set(JSON.of("uuid", uuid.toString()), JSON.of("uuid", uuid.toString()).append("coins", 2000));

Details → docs/en/storage.md


Scheduler

CraftScheduler (Bukkit/Paper/Folia)

// Entity-based sync execution (Folia Region compatible)CraftScheduler.sync(player, () -> {
player.teleport(location);
});
// Async/sync chainingCraftScheduler.sync(plugin, task -> {
player.setHealth(20);
}).delay(task -> {
player.setHealth(1);
}, 20L);
// Safe sync result returnCraftScheduler.callSync(location, () -> {
returnlocation.getBlock().getType();
}).thenAccept(material -> {
notifier.announce("Block at location: " + material);
});

Details → docs/en/scheduler.md

QuartzScheduler (Cron)

QuartzScheduler.run("DailyReset", "0 0 0 * * ?", MyJob.class);

Inventory UI

publicclassMyGUIextendsRSInventory<MyPlugin> {
publicMyGUI(MyPluginplugin) {
super(plugin);
}
publicvoidopen(Playerplayer) {
Inventoryinv = createInventory(27, ComponentFormatter.mini("My Inventory"));
player.openInventory(inv);
}
@OverridepublicbooleanonClick(Event<InventoryClickEvent> event, Clickclick) {
notifier.announce(event.player(), "Slot " + click.slot() + " clicked!");
returntrue;
}
}

Custom Block/Item Integration

Unifies Nexo, Oraxen, ItemsAdder, MMOItems, EcoItems under a single API.

ItemStacksword = CustomItems.from("mmoitems:SWORD:FIRE_SWORD");
Stringid = CustomItems.to(player.getInventory().getItemInMainHand());
CustomBlocks.place(location, "oraxen:custom_ore");

Build

./gradlew shadowJar # Plugin JAR → builds/plugin/
./gradlew spotlessApply # Code formatting

Requirements: JDK 21+, Gradle 9.3+

About

Framework for RTUStudio plugins

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages