VulCode
Blog

Pierwszy plugin Minecraft na Paper 26.3: od projektu do serwera testowego

Zakładasz projekt Gradle z Paper API 26.3 i Javą 25, piszesz komendę i obsługę zdarzenia, a serwer testowy uruchamiasz jednym poleceniem. Według dokumentacji PaperMC, z różnicami, które wprowadziła wersja 26.1.

VulCodeVulCode12 min czytania
Pierwszy plugin Minecraft na Paper 26.3: od projektu do serwera testowego - VulCode
Co zbudujeszJava, 7 kroków
  1. ProjektGradlePaper API 26.3 i Java 25.
  2. Opis pluginuplugin.ymlNazwa, klasa główna, api-version.
  3. Komenda i zdarzenieJava/powitanie i powitanie przy wejściu.
  4. Serwer testowyrun-paperPaper pobrany i uruchomiony jednym poleceniem.

Poradnik w skrócie

Zbudujesz plugin, który wita każdego gracza wiadomością z pliku config.yml i ma komendę /powitanie: gracz widzi swoje powitanie, a administrator przeładowuje konfigurację bez restartu serwera. Wiadomości formatuje MiniMessage, więc w configu piszesz kolory i gradienty jak tagi.

Pierwszy plugin Minecraft na Paper

Grupa
Gry i Discord
Poziom
średni
Czas czytania
około 12 min
Kod
Java

Pliki: build.gradle.kts, settings.gradle.kts, plugin.yml, config.yml, PowitaniePlugin.java, PowitanieCommand.java; do tego polecenia w terminalu.

Kroki
7 kroków

W 7 z nich kod albo polecenia do skopiowania.

Sprawdzenie
Gradle 9.8.0, Java 25

Plugin zbudowany i załadowany, start czeka na EULA

Dokumentacja
4 oficjalne dokumenty

PaperMC: konfiguracja projektu; PaperMC: plugin.yml; PaperMC: BasicCommand; run-task (run-paper) na GitHubie

Z naszego kodu
Flaem (frozcraft)

frozcraft/src/main/java/com/vulcode/frozcraft/store/StoreClient.java

Zanim zaczniesz

Wersja 26.1 zmieniła numerację Paper. Przed nią zależność miała postać {VERSION}-R0.1-SNAPSHOT, teraz to {VERSION}.build.{numer}, a starsze poradniki w sieci nie skompilują się z nowym API. Ten poradnik jest napisany pod Paper 26.3.

Co musisz mieć
Pierwszy plugin Minecraft na Paper
JDK 25
Paper 26.3 wymaga Javy 25. Gradle sam dobierze JDK przez toolchain, ale do uruchomienia Gradle potrzebujesz Javy na komputerze.
Gradle albo IntelliJ IDEA
PaperMC zaleca Gradle z Kotlin DSL. W IntelliJ wtyczka Minecraft Development tworzy ten sam projekt z kreatora.
Nazwa pakietu
Pakiet buduje się z odwróconej domeny i nazwy projektu, na przykład com.example.powitanie. Bez domeny użyj io.github.twojanazwa.
Akceptacja EULA Minecrafta
Serwer testowy nie wystartuje, dopóki właściciel nie zaakceptuje EULA, wpisując eula=true w pliku run/eula.txt. To decyzja prawna osoby, która uruchamia serwer, więc nie automatyzuj jej w skrypcie.

Wersje użyte w poradniku

ElementWersjaSkąd
Paper API26.3.build.157-betarepo.papermc.io
Java25toolchain w build.gradle.kts
Gradle9.8.0obraz gradle:9.8.0-jdk25
run-paper3.1.0plugins.gradle.org

Krok po kroku

7 kroków. Okno obok pokazuje plik albo polecenia kroku, który właśnie czytasz.

Załóż projekt Gradle

Repozytorium PaperMC i zależność compileOnly: API dostarcza serwer, więc nie pakujesz go do pliku jar. Znak + w 26.3.build.+ wybiera najnowszy build wersji 26.3. Gdy potrzebujesz powtarzalnej kompilacji, wpisz konkretny numer, na przykład 26.3.build.157-beta.

Wtyczka run-paper dodaje zadanie runServer, które pobiera Paper w podanej wersji i uruchamia go z Twoim pluginem.

Nazwij projekt

rootProject.name to nazwa pliku jar: build/libs/powitanie-1.0.0.jar.

Opisz plugin w plugin.yml

Wymagane pola to name, version i main, czyli pełna nazwa klasy, która rozszerza JavaPlugin. api-version mówi, pod którą wersję API plugin jest pisany. Uprawnienie powitanie.admin z domyślnym op mają tylko operatorzy serwera.

Dodaj domyślną konfigurację

saveDefaultConfig zapisze ten plik w plugins/Powitanie/config.yml przy pierwszym starcie. Administrator zmienia tam wiadomość, a Twój kod czyta ją przez getConfig.

Napisz klasę główną i zdarzenie

W onEnable rejestrujesz nasłuch zdarzeń i komendę. registerCommand z Paper rejestruje komendę w kodzie, razem z opisem i podpowiedziami, bez wpisu commands w plugin.yml.

Placeholder.unparsed wstawia nick gracza jako zwykły tekst. Gdybyś wkleił nick do szablonu sklejaniem tekstu, gracz z nickiem zawierającym tagi MiniMessage mógłby zmienić wygląd wiadomości.

Napisz komendę

BasicCommand to najprostszy sposób na komendę w Paper: metoda execute dostaje źródło i argumenty, suggest zwraca podpowiedzi. getExecutor to ten, za kogo komenda się wykonuje, także przy /execute as, a getSender to ten, kto ją wpisał.

Zbuduj plugin i uruchom serwer testowy

gradle build tworzy plik jar w build/libs. gradle runServer pobiera Paper 26.3, uruchamia go w katalogu run i ładuje plugin. Przy pierwszym starcie serwer zatrzyma się na EULA: przeczytaj ją, wpisz eula=true w run/eula.txt i uruchom ponownie.

Na prawdziwy serwer kopiujesz sam plik jar do katalogu plugins i restartujesz serwer.

Okno kodukrok 1 z 7
build.gradle.ktsNowy plik, 25 linii
plugins {    java    id("xyz.jpenilla.run-paper") version "3.1.0"} group = "com.example"version = "1.0.0" repositories {    maven(url = "https://repo.papermc.io/repository/maven-public/") {        name = "papermc"    }} dependencies {    compileOnly("io.papermc.paper:paper-api:26.3.build.+")} java {    toolchain.languageVersion.set(JavaLanguageVersion.of(25))} tasks.runServer {    minecraftVersion("26.3")}
settings.gradle.ktsNowy plik, 1 linia
rootProject.name = "powitanie"
src/main/resources/plugin.ymlNowy plik, 9 linii
name: Powitanieversion: 1.0.0main: com.example.powitanie.PowitaniePlugindescription: Wita graczy wiadomością z config.ymlapi-version: '26.3'permissions:  powitanie.admin:    description: Przeładowanie konfiguracji    default: op
src/main/resources/config.ymlNowy plik, 2 linie
# MiniMessage: <green>, <bold>, <gradient:#61cafa:#2112fc> i placeholder <gracz>wiadomosc: "<green>Witaj na serwerze, <gracz>!</green>"
src/main/java/com/example/powitanie/PowitaniePlugin.javaNowy plik, 31 linii
package com.example.powitanie; import net.kyori.adventure.text.Component;import net.kyori.adventure.text.minimessage.MiniMessage;import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;import org.bukkit.entity.Player;import org.bukkit.event.EventHandler;import org.bukkit.event.Listener;import org.bukkit.event.player.PlayerJoinEvent;import org.bukkit.plugin.java.JavaPlugin; public final class PowitaniePlugin extends JavaPlugin implements Listener {     @Override    public void onEnable() {        saveDefaultConfig();        getServer().getPluginManager().registerEvents(this, this);        registerCommand("powitanie", "Pokazuje albo przeładowuje powitanie", new PowitanieCommand(this));        getLogger().info("Powitanie gotowe");    }     Component powitanie(Player player) {        String template = getConfig().getString("wiadomosc", "<green>Witaj, <gracz>!");        return MiniMessage.miniMessage().deserialize(template, Placeholder.unparsed("gracz", player.getName()));    }     @EventHandler    public void onJoin(PlayerJoinEvent event) {        event.getPlayer().sendMessage(powitanie(event.getPlayer()));    }}
src/main/java/com/example/powitanie/PowitanieCommand.javaNowy plik, 41 linii
package com.example.powitanie; import io.papermc.paper.command.brigadier.BasicCommand;import io.papermc.paper.command.brigadier.CommandSourceStack;import java.util.Collection;import java.util.List;import org.bukkit.command.CommandSender;import org.bukkit.entity.Player; public final class PowitanieCommand implements BasicCommand {     private final PowitaniePlugin plugin;     PowitanieCommand(PowitaniePlugin plugin) {        this.plugin = plugin;    }     @Override    public void execute(CommandSourceStack source, String[] args) {        CommandSender sender = source.getSender();        if (args.length == 1 && args[0].equalsIgnoreCase("przeladuj")) {            if (!sender.hasPermission("powitanie.admin")) {                sender.sendRichMessage("<red>Brak uprawnienia powitanie.admin");                return;            }            plugin.reloadConfig();            sender.sendRichMessage("<green>Przeładowano config.yml");            return;        }        if (source.getExecutor() instanceof Player player) {            player.sendMessage(plugin.powitanie(player));        } else {            sender.sendRichMessage("<gray>Użycie: /powitanie albo /powitanie przeladuj");        }    }     @Override    public Collection<String> suggest(CommandSourceStack source, String[] args) {        return args.length <= 1 ? List.of("przeladuj") : List.of();    }}
gradle2 polecenia
gradle buildbuild/libs/powitanie-1.0.0.jargradle runServerLatest build for 26.3 is 157.[PluginInitializerManager] Bukkit plugins (1): - Powitanie (1.0.0)

Sprawdź, czy działa

Projekt z poradnika zbudowaliśmy 6 października 2026 r. w Gradle 9.8.0 z Javą 25. Zależność 26.3.build.+ rozwiązała się do 26.3.build.157-beta, a klasy mają wersję pliku 69, czyli Javę 25. runServer pobrał Paper 26.3 build 157, sprawdził skrót SHA-256 pliku i wypisał nasz plugin na liście pluginów Bukkit. Start zatrzymał się na EULA, której za Ciebie nie akceptujemy.

terminal6 października 2026Gradle 9.8.0, Java 25
$ gradle runServer
Fetching latest Paper build for version 26.3...
Latest build for 26.3 is 157.
Verified SHA256 hash of downloaded jar.
[bootstrap] Running Java 25 (Eclipse Adoptium Temurin-25.0.4.1+1)
[bootstrap] Loading Paper 26.3-157-main@4728a90 for Minecraft 26.3
[PluginInitializerManager] Bukkit plugins (1):
- Powitanie (1.0.0)
You need to agree to the EULA in order to run the server.
Plugin zbudowany i załadowany, start czeka na EULA
Co znaczą linie wyniku
  1. linie 2-4Paper pobrany i sprawdzony

    runServer pobrał Paper 26.3 build 157 i sprawdził skrót SHA-256 pliku.

  2. linie 5-6Java 25

    Serwer działa na Javie 25, tej, którą ustawia toolchain w build.gradle.kts.

  3. linie 7-8Plugin na liście

    Powitanie 1.0.0 jest na liście pluginów Bukkit.

  4. linie 9Zatrzymanie na EULA

    Start zatrzymał się na EULA, której nie akceptujemy za Ciebie. Wpisz eula=true w run/eula.txt, gdy ją przeczytasz.

Po czym poznasz, że działa u Ciebie

  • gradle build kończy się plikiem build/libs/powitanie-1.0.0.jar.
  • Po akceptacji EULA w logu serwera jest linia "Powitanie gotowe".
  • Gracz po wejściu widzi zieloną wiadomość ze swoim nickiem.
  • /powitanie przeladuj działa tylko dla operatora.

Najczęstsze błędy

Ta sama droga co w "Co zbudujesz" na górze strony, z miejscami, w których praca staje najczęściej. Przy każdym błędzie: objaw, przyczyna i co zrobić.

Gdzie staje i dlaczego

Projekt

Gradle

Paper API 26.3 i Java 25.

  • Zatrzymuje się: Could not find io.papermc.paper:paper-api:1.21...-R0.1-SNAPSHOT dla 26.x

    Stary format wersji z poradników sprzed 26.1. Co zrobić: Użyj 26.3.build.+ albo konkretnego numeru buildu.

  • Zatrzymuje się: Unsupported class file major version 69

    Kompilacja albo serwer na Javie starszej niż 25. Co zrobić: Ustaw toolchain na 25 i uruchamiaj serwer na Javie 25.

Opis pluginu

plugin.yml

Nazwa, klasa główna, api-version.

  • Zatrzymuje się: Plugin się nie ładuje, w logu Invalid plugin.yml

    Brak name, version albo main, albo main wskazuje nieistniejącą klasę. Co zrobić: Sprawdź pełną nazwę klasy razem z pakietem.

Komenda i zdarzenie

Java

/powitanie i powitanie przy wejściu.

  • Zatrzymuje się: Nick gracza zmienia kolor wiadomości

    Nick wklejony do szablonu MiniMessage sklejaniem tekstu. Co zrobić: Wstawiaj dane graczy przez Placeholder.unparsed.

Serwer testowy

run-paper

Paper pobrany i uruchomiony jednym poleceniem.

  • Zatrzymuje się: You need to agree to the EULA

    Brak eula=true w eula.txt serwera testowego. Co zrobić: Przeczytaj EULA i wpisz eula=true w run/eula.txt.

Jak to robimy u siebie

Ten sam temat w kodzie, który utrzymujemy: Flaem (frozcraft).

Sklep serwera Minecraft w oknie ekwipunku z ceną, saldem i dziennym limitem

Plugin Paper w Flaem

Frozcraft to plugin Paper, który łączy serwer Minecraft z platformą Flaem: lista graczy ze skórkami, statystyki TPS i MSPT, akcje moderacji i nadawanie zakupów ze sklepu. Działa na Paper, Purpur i Folia, bo używa harmonogramów Paper (GlobalRegionScheduler i scheduler encji), które są takie same na wszystkich trzech.

Najważniejsza zasada z tego kodu przydaje się w każdym pluginie: sprawdzanie gracza i wykonanie komendy dzieje się na wątku serwera, nigdy w wątku, który czekał na odpowiedź z sieci.

frozcraft/src/main/java/com/vulcode/frozcraft/store/StoreClient.javajava, 25 linii
/** * Polls FrozCore's own delivery queue directly over outbound HTTPS. * A leased delivery for a player who is not online right now is left * un-acknowledged rather than retried locally: FrozCore's lease expires * on its own and offers the same delivery again. */private void handleOne(String leaseToken, Map<String, Object> delivery) {    String deliveryId = (String) delivery.get("id");    String playerIdentifier = (String) delivery.get("player_identifier");    String command = (String) delivery.get("command");    if (deliveryId == null || playerIdentifier == null || command == null) {        return;    }     Player player = schedulers.onGlobal(() -> resolvePlayer(playerIdentifier));    if (player == null) {        // Not online - leave it leased. FrozCore offers it again once the lease expires.        return;    }     String resolvedCommand = command.replace("{player}", player.getName());    boolean dispatched = schedulers.onGlobal(            () -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), resolvedCommand));    // ...}

Fragment pluginu frozcraft, skrócony.

Powiązane strony i źródła

Usługa, narzędzia i hasła, do których odsyła ten poradnik, i kolejne poradniki.

Oficjalna dokumentacja

Wolisz, żeby zrobił to ktoś, kto już to robił?

Opisz swój system. Odpiszemy w godzinę z planem integracji i wyceną.

Pytania