> ## Documentation Index
> Fetch the complete documentation index at: https://simplecloud.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Prefixes

> Erfahre, wie das Prefixes Plugin Ranggruppen, Chat-Formatierung, Tablist-Einträge, Name Tags und die Synchronisierung zwischen Servern verwaltet.

export const DependencySnippet = ({dependencies = [], repositories = [{
  id: "simplecloud",
  url: "https://repo.simplecloud.app/snapshots"
}, {
  id: "buf",
  url: "https://buf.build/gen/maven"
}], language = "en", type = "snapshot"}) => {
  const [versions, setVersions] = useState({});
  const [failed, setFailed] = useState({});
  const dependencyKey = dependencies.map(dep => `${dep.groupId}:${dep.artifactId}:${dep.version || ""}`).join("|");
  const copy = {
    en: {
      loading: "Loading the latest dependency version from the Maven repository...",
      failed: "The latest version could not be loaded. The examples below use dynamic version selectors that resolve the newest available artifact during dependency resolution."
    },
    de: {
      loading: "Die neueste Dependency-Version wird aus dem Maven-Repository geladen...",
      failed: "Die neueste Version konnte nicht geladen werden. Die Beispiele unten verwenden dynamische Versionsselektoren, die beim Auflösen der Dependency das neueste verfügbare Artefakt verwenden."
    }
  };
  const text = copy[language] || copy.en;
  useEffect(() => {
    let cancelled = false;
    dependencies.forEach(dep => {
      if (dep.version) return;
      const key = `${dep.groupId}:${dep.artifactId}`;
      const fetchVersion = async () => {
        try {
          const url = type === "snapshot" ? `https://repo.simplecloud.app/api/maven/latest/version/snapshots/${dep.groupId.replace(/\./g, "/")}/${dep.artifactId}?type=raw` : `https://search.maven.org/solrsearch/select?q=g:${dep.groupId}+AND+a:${dep.artifactId}&rows=1&wt=json`;
          const response = await fetch(url, {
            cache: "no-store"
          });
          if (!response.ok) throw new Error(`Version request failed: ${response.status}`);
          if (type === "snapshot") {
            const version = (await response.text()).trim();
            if (!cancelled && version) {
              setVersions(current => ({
                ...current,
                [key]: version
              }));
            }
            return;
          }
          const data = await response.json();
          const version = data.response?.docs?.[0]?.latestVersion;
          if (!cancelled && version) {
            setVersions(current => ({
              ...current,
              [key]: version
            }));
          }
        } catch (error) {
          if (!cancelled) {
            setFailed(current => ({
              ...current,
              [key]: true
            }));
          }
        }
      };
      fetchVersion();
    });
    return () => {
      cancelled = true;
    };
  }, [dependencyKey, type]);
  const keyFor = dep => `${dep.groupId}:${dep.artifactId}`;
  const hasPendingVersion = dependencies.some(dep => !dep.version && !versions[keyFor(dep)] && !failed[keyFor(dep)]);
  const hasFailedVersion = dependencies.some(dep => failed[keyFor(dep)]);
  const getVersion = (dep, buildTool) => {
    if (dep.version) return dep.version;
    const version = versions[keyFor(dep)];
    if (version) return version;
    return buildTool === "maven" ? "[0,)" : "latest.integration";
  };
  const generateKotlin = () => {
    const repoLines = repositories.map(repo => `    maven("${repo.url}")`).join("\n");
    const depLines = dependencies.map(dep => {
      const depType = dep.type || "implementation";
      return `    ${depType}("${dep.groupId}:${dep.artifactId}:${getVersion(dep, "gradle")}")`;
    }).join("\n");
    return `repositories {\n${repoLines}\n}\n\ndependencies {\n${depLines}\n}`;
  };
  const generateGroovy = () => {
    const repoLines = repositories.map(repo => `    maven { url '${repo.url}' }`).join("\n");
    const depLines = dependencies.map(dep => {
      const depType = dep.type || "implementation";
      return `    ${depType} '${dep.groupId}:${dep.artifactId}:${getVersion(dep, "gradle")}'`;
    }).join("\n");
    return `repositories {\n${repoLines}\n}\n\ndependencies {\n${depLines}\n}`;
  };
  const generateMaven = () => {
    const repoLines = repositories.map(repo => `    <repository>
      <id>${repo.id || "repo"}</id>
      <url>${repo.url}</url>
    </repository>`).join("\n");
    const depLines = dependencies.map(dep => {
      const scope = dep.type === "compileOnly" ? "provided" : "compile";
      return `    <dependency>
      <groupId>${dep.groupId}</groupId>
      <artifactId>${dep.artifactId}</artifactId>
      <version>${getVersion(dep, "maven")}</version>
      <scope>${scope}</scope>
    </dependency>`;
    }).join("\n");
    return `<repositories>\n${repoLines}\n</repositories>\n\n<dependencies>\n${depLines}\n</dependencies>`;
  };
  if (hasPendingVersion) {
    return <Info>{text.loading}</Info>;
  }
  return <>
      {hasFailedVersion && <Warning>
          {text.failed}
        </Warning>}

      <Tabs>
        <Tab title="Gradle (Kotlin)">
          <CodeBlock language="kotlin">{generateKotlin()}</CodeBlock>
        </Tab>
        <Tab title="Gradle (Groovy)">
          <CodeBlock language="groovy">{generateGroovy()}</CodeBlock>
        </Tab>
        <Tab title="Maven">
          <CodeBlock language="xml">{generateMaven()}</CodeBlock>
        </Tab>
      </Tabs>
    </>;
};

## Übersicht

Das Prefixes Plugin verwaltet die Rang Anzeige auf deinen Servern. Jeder Spieler gehört zu einer Gruppe, und diese Gruppe bestimmt, wie der Spieler im Chat, in der Tablist und über seinem Kopf dargestellt wird.

Das Plugin:

* Wendet Prefixes, Suffixes, Farben und Anzeigenamen auf Chat, Tablist und Name Tags an
* Liest Gruppen entweder aus der `config.yml` oder aus LuckPerms
* Synchronisiert Chat Nachrichten und Tablist Einträge zwischen den Servern deines Netzwerks
* Bietet eine API, um Prefix Daten auszulesen und eigene Gruppen zu registrieren

## Unterstützte Software

| Software      | Support       |
| ------------- | ------------- |
| Paper & Forks | ✅ Vollständig |
| Minestom      | 🔄 Geplant    |
| Fabric        | 🔄 Geplant    |

<Note>
  Du möchtest eine andere Server-Software unterstützen? Erstelle einfach einen Pull Request auf
  [GitHub](https://github.com/simplecloudapp/prefixes-plugin/pulls)!
</Note>

## Schnelle Einrichtung

1. Lade das Plugin von [GitHub](https://github.com/simplecloudapp/prefixes-plugin/releases) herunter
2. Packe es in den Plugins Ordner deines Server Templates
3. Starte deinen Server
4. Passe die `config.yml` und `messages.yml` nach deinen Wünschen an

***

## Konfiguration

### config.yml

Die Hauptkonfigurationsdatei. Hierüber steuerst du die Gruppenquelle, die Gruppen selbst, einzelne Plugin-Funktionen und die Synchronisierung zwischen den Servern.

```yaml theme={null}
# Version des Konfigurationsformats. Ändere diesen Wert nicht.
version: 1

# ─────────────────────────────────────────────────────────────────────────────
# General
# Legt fest, wie Spielergruppen ermittelt werden.
# ─────────────────────────────────────────────────────────────────────────────
general:
  # Quelle, aus der die Gruppen gelesen werden.
  # Verfügbar: CONFIG, LUCKPERMS
  source: CONFIG

  # Gruppe für Spieler, die zu keiner anderen Gruppe passen.
  # Muss bei source: CONFIG auf eine unten definierte Gruppe verweisen.
  default-group: default

# ─────────────────────────────────────────────────────────────────────────────
# Features
# Jede Funktion des Plugins kann unabhängig aktiviert werden.
# ─────────────────────────────────────────────────────────────────────────────
features:
  # Formatiert Chat-Nachrichten mit dem chat-format der Gruppe.
  chat: true

  # Wendet Prefix, Suffix, Farbe und Sortierung auf die Tablist an.
  tablist: true

  # Wendet den konfigurierten display-name der Gruppe an.
  # Bei false wird der normale Minecraft-Name des Spielers verwendet.
  display-name: true

# ─────────────────────────────────────────────────────────────────────────────
# Sync
# Teilt Chat-Nachrichten und Tablist-Einträge im SimpleCloud-Netzwerk.
# Das Deaktivieren von Sync schaltet die lokale Formatierung nicht aus.
# ─────────────────────────────────────────────────────────────────────────────
sync:
  # Hauptschalter für die gesamte Kommunikation zwischen Servern.
  enabled: true

  # Legt fest, welche Funktionen synchronisiert werden.
  # Die zugehörige Funktion unter features muss ebenfalls aktiviert sein.
  channels:
    chat: true
    tablist: true

  # Gruppen und persistente Server, von denen Aktualisierungen empfangen werden.
  #
  # Besondere Werte:
  #   CURRENT - Aktuelle Servergruppe oder dieser persistente Server.
  #   ALL     - Jeder Server im Netzwerk. Muss allein verwendet werden.
  #
  # Du kannst auch SimpleCloud-Gruppennamen und persistente Server-IDs verwenden.
  # Mehrere Quellen lassen sich kombinieren, außer bei ALL.
  sources:
  - CURRENT

# ─────────────────────────────────────────────────────────────────────────────
# Groups
# Wird nur genutzt, wenn general.source auf CONFIG steht.
#
# Unterstützte Platzhalter:
#   <prefix>       Gruppen-Prefix
#   <suffix>       Gruppen-Suffix
#   <color>        Gruppenfarbe
#   <playername>   Normaler Minecraft-Spielername
#   <displayname>  Formatierter Anzeigename
#   <message>      Chat-Nachricht
# ─────────────────────────────────────────────────────────────────────────────
groups:
- name: owner
  # Höhere Prioritäten werden zuerst gewählt und in der Tablist weiter oben einsortiert.
  priority: 100
  # Permission, die ein Spieler für diese Gruppe benötigt.
  permission: 'simplecloud.prefix.group.owner'
  prefix: '<#DC2626><bold>Owner</bold> <#475569>| '
  suffix: ''
  # Wird vom <color>-Platzhalter und für die Spielerfarbe verwendet.
  color: '<#DC2626>'
  # Formatierter Spielername.
  display-name: '<color><playername>'
  # Format, das bei aktiviertem features.chat verwendet wird.
  chat-format: '<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>'

- name: admin
  priority: 90
  permission: 'simplecloud.prefix.group.admin'
  prefix: '<#F59E0B><bold>Admin</bold> <#475569>| '
  suffix: ''
  color: '<#F59E0B>'
  display-name: '<color><playername>'
  chat-format: '<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>'

- name: default
  priority: 0
  # Eine leere Permission trifft auf jeden Spieler zu.
  permission: ''
  prefix: '<#94A3B8>Player <#475569>| '
  suffix: ''
  color: '<#94A3B8>'
  display-name: '<color><playername>'
  chat-format: '<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>'
```

**LuckPerms Zuordnung**

Wenn `source` auf `LUCKPERMS` steht, kommt jeder Wert aus der LuckPerms-Gruppe:

| LuckPerms-Wert      | Prefixes-Wert  | Standard, wenn nicht gesetzt                                        |
| ------------------- | -------------- | ------------------------------------------------------------------- |
| Gruppenname         | `name`         | entfällt                                                            |
| Gruppengewicht      | `priority`     | `0`                                                                 |
| Gruppen-Prefix      | `prefix`       | leer                                                                |
| Gruppen-Suffix      | `suffix`       | leer                                                                |
| Meta `color`        | `color`        | `#FFFFFF`                                                           |
| Meta `display-name` | `display-name` | `<color><playername>`                                               |
| Meta `chat-format`  | `chat-format`  | `<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>` |

```bash theme={null}
/lp group admin setweight 90
/lp group admin meta addprefix 90 "<#F59E0B><bold>Admin</bold> <#475569>| "
/lp group admin meta set color "<#F59E0B>"
/lp group admin meta set display-name "<color><playername>"
/lp group admin meta set chat-format "<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>"
```

#### Platzhalter

Hier findest du alle verfügbaren Platzhalter.

| Platzhalter     | Verfügbar in                  | Beschreibung                                        |
| --------------- | ----------------------------- | --------------------------------------------------- |
| `<prefix>`      | `chat-format`                 | Prefix der Gruppe des Spielers                      |
| `<suffix>`      | `chat-format`                 | Suffix der Gruppe des Spielers                      |
| `<color>`       | `chat-format`, `display-name` | Teamfarbe der Gruppe, wird als Formatierung gesetzt |
| `<playername>`  | `chat-format`, `display-name` | Reiner Spielername                                  |
| `<name>`        | `chat-format`                 | Alias für `<playername>`                            |
| `<displayname>` | `chat-format`                 | Fertig gerenderter Anzeigename des Spielers         |
| `<message>`     | `chat-format`                 | Inhalt der Chat-Nachricht                           |

<Tip>
  Nutze `<color>`, statt denselben Hex Code in jeden Wert zu schreiben. Die Farbe wird als Style
  gesetzt, alles danach übernimmt sie automatisch.
</Tip>

#### Funktionsschalter

Unter `features` aktivierst oder deaktivierst du die lokalen Funktionen des Plugins:

* `chat` formatiert lokale Chat-Nachrichten mit dem `chat-format` der Gruppe
* `tablist` wendet Prefix, Suffix, Farbe und Priorität der Gruppe auf die Tablist an
* `display-name` wendet den konfigurierten `display-name` an; bei deaktivierter Funktion wird der normale Minecraft-Name verwendet

#### Synchronisierung zwischen Servern

Über das SimpleCloud-Netzwerk können zwei Dinge geteilt werden:

* **Chat**: formatierte Chat-Nachrichten von Spielern auf anderen Servern
* **Tablist**: Tablist-Einträge von Spielern auf anderen Servern

Mit `sync.enabled: false` deaktivierst du die gesamte Kommunikation zwischen Servern, ohne die lokale Formatierung auszuschalten. Unter `channels` wählst du, ob Chat, Tablist-Einträge oder beides synchronisiert werden. Ein Kanal funktioniert nur, wenn die zugehörige Option unter `features` ebenfalls aktiviert ist.

Die Liste `sources` legt fest, von welchen Servern dieser Server Aktualisierungen empfängt:

* `CURRENT` empfängt Aktualisierungen aus der aktuellen Servergruppe oder, bei einem persistenten Server, nur von diesem Server
* `ALL` empfängt Aktualisierungen von jedem Server im Netzwerk und muss der einzige Listeneintrag sein
* Ein Servergruppenname oder eine persistente Server-ID empfängt Aktualisierungen aus genau dieser Quelle

Du kannst mehrere Gruppennamen und persistente Server-IDs kombinieren. Damit zwei Server Aktualisierungen in beide Richtungen austauschen, muss jeder beteiligte Server Aktualisierungen aus der Gruppe oder von der ID des jeweils anderen empfangen.

***

## Befehle

| Befehl             | Permission                            | Beschreibung                |
| ------------------ | ------------------------------------- | --------------------------- |
| `/scprefix`        | `simplecloud.prefixes.command`        | Zeigt die Befehlsübersicht. |
| `/scprefix help`   | `simplecloud.prefixes.command`        | Zeigt die Befehlsübersicht. |
| `/scprefix reload` | `simplecloud.prefixes.command.reload` | Lädt die Konfiguration neu. |

***

## API

Über die API liest du die Prefix-Daten eines Spielers aus und registrierst eigene Gruppen.

### Dependency

<DependencySnippet
  language="de"
  dependencies={[
{
  groupId: "app.simplecloud.plugin",
  artifactId: "prefixes-api",
  type: "compileOnly",
},
]}
  repositories={[
{ id: "simplecloud", url: "https://repo.simplecloud.app/snapshots" },
]}
/>

### Zugriff auf die API

<Tabs>
  <Tab title="Paper">
    <CodeGroup>
      ```kotlin Kotlin theme={null}
      import app.simplecloud.prefixes.api.PrefixesApi
      import org.bukkit.Bukkit

      val api = Bukkit.getServicesManager().getRegistration(PrefixesApi::class.java)?.provider
          ?: throw IllegalStateException("PrefixesApi is not registered")
      ```

      ```java Java theme={null}
      import app.simplecloud.prefixes.api.PrefixesApi;
      import org.bukkit.Bukkit;
      import org.bukkit.plugin.RegisteredServiceProvider;

      RegisteredServiceProvider<PrefixesApi> registration =
          Bukkit.getServicesManager().getRegistration(PrefixesApi.class);

      if (registration == null) {
          throw new IllegalStateException("PrefixesApi is not registered");
      }

      PrefixesApi api = registration.getProvider();
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Spieler verwalten

Die Gruppe eines Spielers holen:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val group = api.getPrimaryGroup(player.uniqueId).await()

  player.sendMessage(Component.text("Deine Gruppe: ${group?.name ?: "keine"}"))
  ```

  ```java Java theme={null}
  api.getPrimaryGroup(player.getUniqueId()).thenAccept(group -> {
      String name = group != null ? group.getName() : "keine";

      player.sendMessage(Component.text("Deine Gruppe: " + name));
  });
  ```
</CodeGroup>

Die Anzeigewerte eines Spielers holen:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val data = api.getPrefixData(player.uniqueId).await()

  player.sendMessage(data.prefix.append(data.displayName).append(data.suffix))
  ```

  ```java Java theme={null}
  api.getPrefixData(player.getUniqueId()).thenAccept(data -> {
      player.sendMessage(data.getPrefix().append(data.getDisplayName()).append(data.getSuffix()));
  });
  ```
</CodeGroup>

Prüfen, ob ein Spieler in einer bestimmten Gruppe ist:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val vip = api.getGroups().find { it.name == "vip" } ?: return

  if (vip.containsPlayerAsync(player.uniqueId).await()) {
      player.sendMessage(Component.text("Willkommen zurück!"))
  }
  ```

  ```java Java theme={null}
  PrefixesGroup vip = api.getGroups().stream()
      .filter(group -> group.getName().equals("vip"))
      .findFirst()
      .orElse(null);

  if (vip == null) return;

  vip.containsPlayerAsync(player.getUniqueId()).thenAccept(isVip -> {
      if (isVip) {
          player.sendMessage(Component.text("Willkommen zurück!"));
      }
  });
  ```
</CodeGroup>

### Gruppen verwalten

Alle Gruppen auflisten, höchste Priorität zuerst:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  api.getGroups().forEach { group ->
      println("${group.priority} - ${group.name}")
  }
  ```

  ```java Java theme={null}
  api.getGroups().forEach(group ->
      System.out.println(group.getPriority() + " - " + group.getName()));
  ```
</CodeGroup>

Eigene Gruppe registrieren:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class VipGroup : PrefixesGroup {
      override val name = "vip"
      override val priority = 50
      override val permission = "simplecloud.prefix.group.vip"
      override val prefix = miniMessage.deserialize("<#A3E635>VIP <#475569>| ")
      override val suffix = Component.empty()
      override val color = TextColor.fromHexString("#A3E635")
      override val displayName = "<color><playername>"
      override val chatFormat = "<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>"

      override fun containsPlayer(id: UUID) =
          containsPlayerAsync(id).join()

      override fun containsPlayerAsync(id: UUID) =
          CompletableFuture.completedFuture(false)
  }

  if (!api.addGroup(VipGroup()).await()) {
      println("Es gibt bereits eine Gruppe namens vip")
  }
  ```

  ```java Java theme={null}
  public class VipGroup implements PrefixesGroup {

      @Override
      public String getName() {
          return "vip";
      }

      @Override
      public int getPriority() {
          return 50;
      }

      @Override
      public String getPermission() {
          return "simplecloud.prefix.group.vip";
      }

      @Override
      public Component getPrefix() {
          return MiniMessage.miniMessage().deserialize("<#A3E635>VIP <#475569>| ");
      }

      @Override
      public Component getSuffix() {
          return Component.empty();
      }

      @Override
      public TextColor getColor() {
          return TextColor.fromHexString("#A3E635");
      }

      @Override
      public String getDisplayName() {
          return "<color><playername>";
      }

      @Override
      public String getChatFormat() {
          return "<prefix><color><playername><suffix> <#475569>» <#F8FAFC><message>";
      }

      @Override
      public boolean containsPlayer(UUID id) {
          return containsPlayerAsync(id).join();
      }

      @Override
      public CompletableFuture<Boolean> containsPlayerAsync(UUID id) {
          return CompletableFuture.completedFuture(false);
      }
  }

  api.addGroup(new VipGroup()).thenAccept(created -> {
      if (!created) {
          System.out.println("Es gibt bereits eine Gruppe namens vip");
      }
  });
  ```
</CodeGroup>

***

## Best Practices

* Vergib eindeutige Prioritäten, damit die Reihenfolge in der Tablist vorhersehbar bleibt
* Nutze für alle Gruppen dasselbe `chat-format`, außer ein Rang soll im Chat bewusst auffallen
* Verwende `<color>` in `display-name` und `chat-format`, statt Hex Codes zu wiederholen
* Lass `default-group` auf eine Gruppe mit leerer `permission` zeigen, damit jeder Spieler eine Gruppe bekommt
