Diese Dokumentation richtet sich an alle, die am Code von MRchunks arbeiten. Was das Plugin für Server-Admins kann, steht in der README. Build, Config-Migration, Discord, Update-Checker, Menü-Grundgerüst und Release-Ablauf stammen aus MRsleep und funktionieren genauso. Die dortige Projektdoku beschreibt sie ausführlicher.
1. Überblick
| Plattform | Paper 26.3 (paper-api:26.3.build.200-stable), Java 25 |
| Build | Gradle 9.8.1 (Wrapper), JUnit 6, Minotaur für Modrinth |
| Paket | at.marcelr.mrchunks |
| Abhängigkeiten | keine Pflicht. Optional MRadmin (Modul-API, compileOnly, softdepend) - auch für die Karte der Spielerseite |
2. Projektstruktur
src/main/java/at/marcelr/mrchunks/
├─ MRChunks.java Hauptklasse: Lebenszyklus, Prüf-Task, verbindet alles
├─ Settings.java Record mit allen Config-Werten (validiert)
├─ ConfigUpdater.java Config laden (BOM) und migrieren ┐
├─ Messages.java Sprachdateien lang/*.yml (MiniMessage) │ aus MRsleep
├─ DiscordWebhook.java asynchrone Discord-Logs mit Diagnose │ übernommen
├─ UpdateChecker.java GitHub/Modrinth-Updateprüfung ┘
├─ Loader.java ein gesetzter Chunkloader
├─ LoaderManager.java Loader, Chunk-Tickets, Limits, Sperren, data.yml
├─ LoaderItem.java das markierte Chunkloader-Item
├─ LoaderListener.java Setzen, Unzerstörbarkeit, Rechtsklick, Welt laden
├─ BorderDisplay.java Grenze als leuchtende Block-Displays (nur für den Betrachter)
├─ AdminActions.java alle Aktionen (Befehle und Menüs nutzen dieselben)
├─ Texts.java gemeinsame Platzhalter (<name>, <number>, <world>, <x> …)
├─ command/ /chunkloader, /mrchunks
├─ dummy/ Dummies (Dummy-Spieler: Lebenszyklus, Events, Skins), DummyBackend (Schnittstelle)
│ └─ nms/ NmsDummyBackend, FakeConnection - einzige Stelle mit Server-Interna
├─ link/ LinkRequests (Anfrage + Zustimmungs-Dialog für verknüpfte Accounts)
├─ gui/ Menu, MenuListener, Items, Heads, Pages, LoaderIcons, PlayerIcons,
│ LoaderMenu, LoaderSettingsMenu, BlockMenu, MyLoadersMenu, AdminMenu, PlayerLoadersMenu, LinkMenu, LinkPickerMenu
└─ util/ DurationUtil, Versions (mit Tests)
src/main/resources/
├─ config.yml UTF-8 mit BOM!
└─ plugin.yml, build-info.properties
3. Chunks laden (LoaderManager)
Tickets
ticket(Standard):World#addPluginChunkTicket. In 26.3 heißt das Ticket-TypPLUGIN_TICKETauf Level 31 (Entity-Ticking) mit den Flags „lädt“ und „simuliert“. Es wird an das Plugin gebunden und beim Deaktivieren entfernt.forceload:World#setChunkForceLoaded, wie/forceload. Der Ticket-TypFORCEDhat zusätzlich keep dimension active. Das ist nur nötig, wenn in der Paper-Configunsupported-settings.disable-world-ticking-when-emptyaktiv ist (sonst gehtServerLevelnach 300 leeren Ticks schlafen). MRchunks merkt sich inforcedByUs, welche Chunks es selbst geforceloadet hat. Chunks, die vorher schon geforceloadet waren, werden nie entladen.
Überschneidungen
coverage: Welt → Chunk-Schlüssel → Anzahl aktiver Loader. Ein Ticket entsteht beim Wechsel von 0 auf 1 und verschwindet beim Wechsel von 1 auf 0. Radius und Lademethode, mit denen die Tickets erstellt wurden (appliedRadius, appliedMethod), werden gespeichert. Deshalb löst reload() zuerst alle Tickets mit den alten Werten (releaseAll) und legt sie danach mit den neuen an (applyAll).
Was läuft
Laut dekompiliertem ServerChunkCache ticken alle Entity-Ticking-Chunks, auch mit Zufalls-Ticks. Mob-Spawning läuft dagegen nur in spawningChunks, also in der Nähe von Spielern. Dazu kommt Papers Entity Activation Range: Mobs ohne Spieler in der Nähe bekommen nur seltene, reduzierte Ticks (keine KI, kaum Bewegung, keine Portale, siehe Issue #12). Beides lösen die Dummy-Spieler (Abschnitt 10).
Welten
Loader in einer Welt, die noch nicht geladen ist, bekommen ihre Tickets per WorldLoadEvent → applyWorld.
4. Schutz (LoaderListener)
| Ereignis | Reaktion |
|---|---|
BlockPlaceEvent (HIGH) |
Prüfung von Permission, Welt, Sperre, Limit und Überschneidung (loaders.overlap: deny-own/deny-all). Bei Erfolg folgt die Registrierung erst bei MONITOR, wenn kein Schutz-Plugin abgebrochen hat |
BlockBreakEvent |
abgebrochen, mit Hinweis |
EntityExplodeEvent, BlockExplodeEvent |
Loader-Blöcke werden aus der Blockliste entfernt |
BlockPistonExtend/RetractEvent |
abgebrochen, wenn ein Loader bewegt würde |
EntityChangeBlockEvent, BlockBurnEvent |
abgebrochen (Wither, Endermen, Feuer bei brennbarem Material) |
PrepareItemCraftEvent |
kein Ergebnis, wenn ein Loader-Item im Raster liegt (verhindert Emeraldblock → 9 Smaragde) |
Prüf-Task (check-interval-seconds) |
validate() stellt fehlende Blöcke wieder her oder löscht den Loader |
ChunkLoadEvent |
Loader im geladenen Chunk werden einen Tick später geprüft (validate(Collection)), auch deaktivierte, deren Chunks sonst nie geladen sind |
Die Block-Suche läuft über einen Positions-Index (byPosition), damit große Explosionen nicht alle Loader durchgehen müssen.
Bereich: Jeder Loader hat vier Reichweiten (Loader.Direction NORTH = −z, SOUTH, WEST = −x, EAST) ab dem Chunk seines Blocks; daraus ergeben sich minChunkX/maxChunkX/minChunkZ/maxChunkZ, width, length und area (= Kosten im Budget). Geändert wird nur über LoaderManager#resize (Tickets: erst uncover mit der alten, dann cover mit der neuen Fläche, danach Karte, Grenze, Lag-Index und Dummy aktualisieren). Die Prüfungen (Budget, max-reach, Überlappung nur für neu überlappte Loader, Dummy nur gleichmäßig und ≥ 2) stehen in AdminActions#resizeProblem - Spiel (gui/SizeMenu) und Web (webResize) nutzen dieselbe Methode.
Überschneidung: Zwei Bereiche überschneiden sich, wenn sich ihre Rechtecke einen Chunk teilen (Loader#overlaps, LoaderManager#overlapping; beim Setzen mit der Fläche, die der neue Loader bekäme). Tickets laufen über den Referenzzähler pro Chunk (coverage), deshalb ist Überlappen technisch immer sauber. loaders.overlap regelt nur, ob es erlaubt ist: allow, warn (Hinweis mit sharedChunks und Grenze nach dem Setzen), deny-own, deny-all.
Namen & Nummern: Loader#id ist intern (Schlüssel in data.yml, Marker-Keys). Spieler sehen Loader#number, die kleinste freie Nummer des Owners (nextNumber), oder den Namen aus dem Umbenennen-Dialog (gui/RenameDialog, Paper-Dialog-API mit Textfeld; AdminActions#rename entfernt Steuerzeichen und kürzt auf gui.max-name-length). Ältere data.yml ohne number werden beim Laden in Erstellungsreihenfolge durchnummeriert (assignMissingNumbers). Admin-Befehle nehmen <spieler> <nr>.
Grenze (BorderDisplay): pro Betrachter eine Sitzung mit BlockDisplay-Entities: waagrechte Balken je Chunk der Außenkante auf mehreren Höhen und Pfosten an jeder Chunk-Ecke. Die Entities sind persistent = false (werden nie gespeichert) und visibleByDefault = false (nur player.showEntity macht sie sichtbar), leuchten (setGlowing, Farbe aus border.color) und sind über ihre Mitte verankert, damit der Client sie lange anzeigt. Alle 10 Ticks folgen sie der Höhe des Spielers (setTeleportDuration glättet), fehlende Teile (Chunk entladen) werden neu erzeugt. Ende: Zeit abgelaufen, Button im Menü, Loader gelöscht (loaderRemoved), Quit, Weltwechsel, Plugin-Stopp. Beim An/Ausschalten wird sie neu eingefärbt (loaderChanged).
5. Item (LoaderItem)
Material, Name, Lore und Glanz kommen aus der Config. Markiert wird das Item im PersistentDataContainer (mrchunks:chunkloader).
Chunk-Budget (LoaderManager.LimitKind.BUDGET, daneben DUMMY): aufgelöst nach Admin-Override → höchste Permission mrchunks.budget.<n> (nur online, offline der gemerkte Wert aus known-limits) → loaders.default-budget. usedBudget = Summe der area aller aktiven Loader des Owners, freeBudget/fitsBudget prüfen davor. Einschalten (AdminActions#toggle, webToggle) und Vergrößern eines aktiven Loaders gehen nur im Budget - für alle, auch das Team. enforceBudget schaltet beim Senken die neuesten aktiven Loader aus. Neue Loader: LoaderListener#placementReach nimmt default-reach oder das größte Quadrat, das ins freie Budget passt; passt nichts, wird der Loader aus gesetzt.
Höchstzahl: loaders.max-loaders für alle gleich (keine Permission). Setzen erlaubt, wenn gesetzte < max-loaders. give gibt höchstens max-loaders − gesetzte − Items im Inventar aus. Damit kann niemand Items horten, denn beim Setzen wird immer neu gezählt.
Migration (config-version 12, lang-version 13): ConfigUpdater übernimmt loaders.radius als default-reach und limits-per-chunks (alte Fläche) und setzt default-budget = default-active-limit × alte Fläche. LoaderManager#load gibt Loadern ohne reach die default-reach und rechnet alte active-limits in budgets um (× Fläche von default-reach), danach wird sofort gespeichert. budgets wird immer geschrieben, damit die Umrechnung nur einmal passiert.
6. Daten (plugins/MRchunks/data.yml)
next-id: 3
loaders:
'1': { owner: <uuid>, world: <uuid oder Weltname>, world-name: world, x: 100, y: 80, z: 100, created: <ms>, number: 1, name: "Farm", active: true, dummy: true, block: DIAMOND_BLOCK, reach: { north: 3, south: 3, west: 1, east: 6 } }
# block nur, wenn der Besitzer einen anderen Block als item.material gewählt hat (Block ändern, item.block-choices)
# Chunk-Budget pro Spieler (Admin-Wert); alte active-limits werden beim ersten Laden hierher umgerechnet
budgets:
<spieler-uuid>: 300
dummy-limits: { <spieler-uuid>: 2 }
# Limits aus Permissions vom letzten Mal online (budget | dummy)
known-limits: { budget: { <spieler-uuid>: 300 } }
# Verknüpfte Accounts: Zweit-Account -> Haupt-Account
links:
<zweit-uuid>: <haupt-uuid>
bans:
<spieler-uuid>: { until: <ms | Long.MAX = dauerhaft>, reason: "...", by: "Admin" }
world darf in einer von Hand bearbeiteten Datei auch ein Weltname sein. Beim nächsten Speichern wird daraus die UUID.
7. Menüs (gui/*)
LoaderMenu: symmetrisch - Reihe 2 =. Mitglieder . An/Aus Grenze Dummy . Einstellungen .(Mitglieder nur mit Einladungen, Dummy nur mitdummies.enabled, sonst Glas); Reihe 3 Zurück links, Schließen in der Mitte, mitmrchunks.admin.teleportSchließen links und Kompass rechts der Mitte, mitmrchunks.admin.suspendlockdie Uhr auf der Mittelachse (ohne Teleport rechts der Mitte). Wird der Loader inzwischen gelöscht, springt das Menü zurück.LoaderSettingsMenu: Größe, Block ändern (nur mititem.block-choices), Umbenennen, Löschen mittig in Reihe 2 (4 oder 3 Knöpfe). Löschen mit zweitem Klick innerhalb vongui.confirm-seconds, danach zurück dorthin, wo das Loader-Menü herkam.RenameDialogöffnet danach wieder das aufrufende Menü.BlockMenu: Auswahl des Blocks in der Welt (item.material+item.block-choices, Namen perComponent.translatable= Sprache des Clients).LoaderManager#setBlockspeichert ihn am Loader (null=item.material) und tauscht den Block sofort;validatetauscht andere Loader-Blöcke still zum gewählten, statt den Loader als fehlend zu behandeln.Settingslässt nur feste, ganze Blöcke ohne Schwerkraft und ohne Block-Entity zu.MyLoadersMenu: die eigenen Loader.AdminMenu: Statistik, Info, Filter, Spielerköpfe (auch offline; das Limit offline kennt nur Admin-Limit und Standard, weil Permissions nur bei Online-Spielern abfragbar sind).SizeMenu: Größe eines Loaders - Pfeile N/S/W/O (Links +1, Rechts −1), Mitte = alle Richtungen, oben das Budget des Owners, unten Grenze anzeigen. Mit Dummy nur die Mitte. Ändern überAdminActions#resize.PlayerLoadersMenu: Budget (links +10, rechts −10, Shift zurücksetzen), Dummy-Limit (±1), alle löschen, Sperren und die Loader des Spielers.- Alle Aktionen laufen über
AdminActions, das jeweils selbst die Permission prüft.
7a. Lag-Schutz (lag/LagMonitor)
- Messung: alle
sample-secondspro aktivem (und gesperrtem) Loader über alle Chunks seines Rechtecks; Ereignisse (Redstone, Trichter, Kolben, Spawns) zählt ein Chunk-Index, der bei jeder Messung und nachresize(areaChanged) neu gebaut wird. - Grenzen pro Fläche:
LagSettings#limitsFor(chunks): bissmall-loaders.max-chunks(9) die festensmall-loaders.limits, darüberlag-protection.limits×chunks / limits-per-chunks(LagLimits#scaled), mindestens die kleinen (atLeast); immerrounded()(ganze Zahlen, geprüfte Grenzen ≥ 1). JedeActivityeines Loaders ist gegenlimitsFor(loader.area())gerechnet. - Bewertung pro Loader:
judge(loader, activity, …)zählt Treffer (strikes) pro Loader-ID; nachlock-after-samplesMessungen über einer Sperrgrenze (nur bei Server-Lag oder abforce-lock-multiplier) wird nur dieser Loader gesperrt. Auffällig (conspicuous(loader)) ist ein Loader über einer Warngrenze seiner Fläche.lockOffendersBeforePausesperrt vor einer Server-Pause jeden aktiven Loader über einer Sperrgrenze (außer im Cooldown nach dem Entsperren). Bis 2026-10-29 gab es einen Pool pro Owner (Summe aller Loader) - abgeschafft, weil eine Überschreitung nur den einen Loader betreffen soll.
7b. Gestaffelte Abschaltung (lag/LoadShedder)
- Messung: jede Sekunde
Bukkit.getAverageTickTime()in einen Ring mitaverage-secondsPlätzen; gehandelt wird erst mit vollem Ring (nach Start und Reload). Der Mittelwert entscheidet die Stufe:> warn-msptWarnung (Team mitmrchunks.admin.gui+ Discord), endet erst nachmin-minutesohne Überschreitung und ohne abgeschaltete Loader. - Abschalten:
> shutdown-msptundstep-secondsseit dem letzten Schritt →heaviest(): unter den Loadern, die gerade Chunks halten (LoaderManager#loading, nicht gesperrt), der mit der höchstenActivity#lockRatioaus demLagMonitor(bei Gleichstand der größere).LoaderManager#shed(loader, since)nimmt die Tickets weg und den Dummy (Dummies#wantedprüftshed),loader.active()bleibttrue. Nichts wird gespeichert - ein Neustart bringt alle zurück. - Zurück:
<= warn-msptundstep-seconds→restoreOne: unter den abgeschalteten, derenmin-minutesum sind, der zuletzt abgeschaltete (unshedlädt ihn wieder, wenn er noch an ist; ausgeschaltete oder gelöschte werden nur vergessen). Ist die Funktion aus (enabled: false), kommen beim (Re)Start alle zurück. - Zu oft: pro Loader eine Liste der Abschaltzeiten innerhalb
window-minutes; ist sie länger alsmax-shutdowns, ruft derLoadShedderstatt einer AbschaltungLagMonitor#lockRepeatedauf. Die Sperre (LagLock#shutdowns > 0, indata.ymlalsshutdowns) nutzt eigene Texte (lag-locked-repeat-*, Discordloader-lag-repeat-locked) und zeigt den schlechtesten Messwert der letzten Messung. - Tickets:
LoaderManagermerkt sich incovered, welche Loader gerade incoveragestehen.coverüberspringt pausierte, abgeschaltete und schon abgedeckte Loader,uncovernur abgedeckte - sonst würde das Ausschalten eines abgeschalteten Loaders Chunks überlappender Loader freigeben.LagMonitorbewertet abgeschaltete Loader nicht (sie halten nichts), ihre letzte Messung bleibt sichtbar. Während einer Server-Pause handelt derLoadSheddernicht. - Startverzögerung:
MRChunksmerkt sich den ersten Tick (serverReadyAt, perrunTaskausonEnable- läuft erst, wenn der Server fertig gestartet ist). Bislag-protection.startup-delay-secondsdanach liefertstartupGrace()true:LoadShedderverwirft seine Messungen (filled = 0),LagMonitorbewertet nichts (wie bei der Server-Pause),ServerPausestartet keine Pause. Ein/mrchunks reloadstartet die Verzögerung nicht neu. - Status:
LoaderStatus.SHED(oranger Beton) zwischenINACTIVEundPAUSED; Webstatus: "shed"undshed: {since, back}, Übersichtshed,mspt,msptWarning; MRadmin-Kachel und Hinweise. - Harte Grenze: jede Sekunde (vor dem vollen Fenster) prüft
checkden Mittelwert der letztenhard-cap-secondsWerte; abhard-cap-msptschaltethardCapalle Loader, die Chunks halten, auf einmal ab (ohne Eintrag in die Abschalt-Liste, also ohne Sperre). Rückkehr wie oben, einzeln. - Schalter im Web (gespeichert in
data.yml):LoaderManager#monitoringOff(monitoring-off: {by, since}) schaltet denLoadShedderab (active()= Config an und nicht abgeschaltet;setMonitoringstartet neu, aus = alle zurück).LoaderManager#adminPause(admin-pause: {by, since}) ist eine zweite Pause neben der Server-Pause:paused()=serverPaused || adminPause,ServerPausesieht nurserverPaused(). AktionenpauseAll/monitoringmitoninWebData#actionvor der Loader-Suche, Rechtepause/monitoring(mrchunks.admin.pause/.monitoring); Übersicht liefertadminPauseundmonitoring {configured, active, warn, shutdown, hardCap, offBy, offSince}. - Limit-Sperre aussetzen:
LoaderManager#lockSuspension(lock-suspensions.<id>: {until, by}indata.yml, abgelaufene zählen nicht mehr) hältLagMonitor#judgeundlockOffendersBeforePausedavon ab, zu sperren;lockRepeatedbleibt unberührt, ebenso Messung undLoadShedder.LagMonitor#sampleräumt abgelaufene Einträge weg (expireLockSuspensions, Hinweis an Owner und Discord).AdminActions#suspendLock/webSuspendLock(Dauer 0 = beenden) teilen sichapplySuspension; eine Sperre mitrepeated()wird abgelehnt (loader-lock-repeated), eine Limit-Sperre aufgehoben. Dauern auslag-protection.lock-suspension-durations, hart begrenzt auf 1 Minute bis 24 Stunden (Settings#suspensionDurations). Menügui/LockSuspensionMenu, Web-AktionsuspendLock, Recht-SchlüsselsuspendLock, Feldersuspension {until, by},suspendDurations,lock.repeated. - Verlauf:
lag/ActivityHistory(ohne Bukkit, getestet) hält pro Loader die Messungen der letzten 12 Stunden im Speicher (LagMonitor#sampleträgt ein, gelöschte Loader fallen überretainweg). Jeder Eintrag trägt seinePhase(ON,OFF,SHED,LOCKEDausLoaderManager#phase); ohne Messung bleibtvaluesnull.LoaderManagermeldet jeden Wechsel sofort anLagMonitor#phaseChanged, damit Phasen zwischen zwei Messungen und während der Startverzögerung die genaue Uhrzeit haben.phasesrechnet die Phasen aus allen Einträgen aus, nicht aus den zusammengefassten Punkten.lastliefert die letzte echte Messung für die Kacheln. Jede Messrunde geht als eine Textzeile (encode) überMRadminLink#saveHistoryanmoduleHistoryAdd(Modul-API 6), beim Start liestrestoreHistorysie zurück. Web-AbfrageloaderHistory {id, range}fasst je nach Zeitraum zusammen (HISTORY_STEPS,ActivityHistory#combined, an der Uhrzeit ausgerichtet), mit dem Höchstwert je Messwert, damit Spitzen gegen die Grenzen sichtbar bleiben. - Live-Ansicht: Web-Abfrage
loaderLive {id}(überrequests()anmrchunks.admin.livegebunden) ruftLagMonitor#live: Die Abfrage selbst hält den Loader inLiveWatch(ohne Bukkit, getestet; fällt nach 5 s ohne Abfrage heraus, höchstensmax-loaders). Der Takt läuft nur, solange etwas beobachtet wird. Ereignisse zähltcountzusätzlich inliveCounts, Messung über dasselbemeasure. Nichts davon geht anjudgeoder in den gespeicherten Verlauf;liveHistoryhält die Punkte 15 Minuten im Speicher,ActivityHistory#mergedfügt sie inloaderHistoryein (bis 1h, normale Punkte fallen nur innerhalb einer Live-Strecke weg). Beim Öffnen misstfirstLivesofort, damit die Linie ohne Spalt weiterläuft.
8. Karte über MRadmin (api/SelfPage.java)
- Die Karte gehört ganz zu MRadmin (Installation, Einrichtung und Kacheln von squaremap, Viewer auf der Spielerseite,
/mradmin map; siehe MRadminsdocs/DOKUMENTATION.md). MRchunks hat keine squaremap-Abhängigkeit und keine Karten-Einstellungen. - MRchunks liefert nur die Ebene der Spielerseite (Modul-API 8):
MRadminModule#selfMap→SelfPage.mapLayersbaut eineSelfMapLayerloadersmit den eigenen und geteilten Loadern des Betrachters (Fläche in Blockkoordinaten, Farbe nach Zustand). Rechtsklick →selfMapAction→SelfPage.mapAction, gleiche Prüfungen wie die Knöpfe der Seite. Die Ebenen der alten API 7 (mapLayers/mapIcons) liefert MRchunks nicht mehr. - Links: Knopf Webkarte und
sendMapLinkholen die Adresse überMRadminLink#mapUrl()(Tab „Karte“ der Spielerseite). Ohne MRadmin ist sie leer, der Knopf erscheint dann nicht. - Migration (config-version 27): der Abschnitt
map-layer(bzw.squaremapaus 1.1) fällt weg. Adresse und Einrichtung übernimmt MRadmin ausconfig.v25.backup.ymlbzw. der noch alten config.yml (MRadmin startet vorher).
9. Entwicklung
gradlew.bat build Kompilieren + Tests + JAR → build/libs/MRchunks-mc<minecraft>-v<version>.jar
Der Release-Ablauf entspricht MRsleep, siehe RELEASES.md: Versionen heißen mc<minecraft>-v<version> (z.B. mc26.3-v0.2.0), entwickelt wird auf beta, stabile Versionen kommen von main oder einem Wartungs-Branch mc/<version>, und release.yml lädt auf GitHub und Modrinth hoch (Secret MODRINTH_TOKEN). Die Konsole ist immer englisch (Messages#as, Messages#console), Spieler bekommen die Sprache aus language.
Manuelle Testcheckliste
| # | Test | Erwartung |
|---|---|---|
| 1 | /chunkloader give, platzieren |
Meldung „Chunkloader #1 gesetzt – 49 Chunks“, Grenze per Rechtsklick sichtbar |
| 2 | Abbauen, TNT daneben, Kolben dagegen | Block bleibt |
| 3 | Limit erreicht, /chunkloader give |
give-none-left. Weiteres Setzen über gehortete Items geht nicht (place-limit) |
| 4 | Loader-Item in die Werkbank | kein Ergebnis |
| 5 | Menü → Deaktivieren, /forceload query (bei forceload) |
Chunks frei, Block bleibt |
| 6 | Zwei überlappende Loader, einen löschen | gemeinsame Chunks bleiben geladen |
| 7 | Loader im Nether | Bereich geladen (execute in minecraft:the_nether if loaded …) |
| 8 | /mrchunks ban <spieler> |
alle seine Loader weg, Items aus dem Inventar entfernt, give verweigert |
| 9 | /mrchunks limit <spieler> budget 200 |
Kopf im Admin-Menü zeigt Budget 200 „(individuell)“, überzählige Loader gehen aus |
| 10 | Start mit MRadmin, Spielerseite /me/map öffnen |
Ebene „Meine Chunkloader“ zeigt eigene + geteilte Loader, Rechtsklick schaltet an/aus |
| 11 | Loader-Menü → Größe, Osten +1 bei aktivem Loader | neue Chunk-Spalte geladen, Grenze und Karte folgen, Budget steigt um die Länge |
| 12 | Vergrößern mit vollem Budget | abgelehnt mit Hinweis, Größe unverändert |
| 13 | Dummy auf 3×3 oder Rechteck einschalten | abgelehnt („nur quadratisch ab 5×5“) |
Einladungen & Ausschluss
- Daten:
LoaderManagerhältloaderMembers(Loader-ID → Spieler → gültig bis) undownerMembers(Owner → Spieler → gültig bis; gilt für alle Loader des Owners, auch künftige).Long.MAX_VALUE= dauerhaft. Indata.ymluntermembers.loaders.<id>.<uuid>bzw.members.owners.<owner>.<uuid>. Abgelaufene Einträge fallen beim Lesen (members) und beim Speichern weg, die eines gelöschten Loaders mit dem Loader. - Rechte (
AdminActions):canToggle=canManage(Owner/Admin) oderisMember. Umbenennen, Löschen undcanManageInvitesbleiben Owner (mrchunks.invite) und Admins (mrchunks.admin.manage) vorbehalten. Beim Aktivieren durch Eingeladene gilt das Budget des Owners, der Owner wird benachrichtigt. Eingeladene dürfen das Menü öffnen (canView). - Menüs:
InvitePickerMenu(Köpfe der Online-Spieler, Dauer per Uhr wählbar, „Namen eingeben“ öffnetInviteDialog),MembersMenu(Liste, Klick = entfernen, bei einem Loader werden die Einladungen für alle Loader grau mit angezeigt),InviteDialog(Paper-Dialog mit Namensfeld und Dauer-Auswahl ausinvites.durations),SharedLoadersMenu(„Mit mir geteilt“). Einstieg: Button Mitglieder imLoaderMenu, Mitglieder für alle Loader und Mit mir geteilt inMyLoadersMenu. mrchunks.exempt(streng): blockiertgive, Setzen, Einladen des Spielers und seine Rechte als Eingeladener.enforceExemptlöscht beim Join und bei/mrchunks reloadseine Loader und Items. Permissions sind nur online bekannt, Offline-Spieler werden beim nächsten Join bereinigt.
10. Dummy-Spieler (dummy/*)
- Warum Server-Interna: Paper hat keine API für künstliche Spieler.
NmsDummyBackend(Paketdummy.nms, gebaut mit paperweight-userdev gegen das Dev-Bundle auspaperApi) erzeugt einen echtenServerPlayerund loggt ihn überPlayerList.placeNewPlayermit einerFakeConnectionein (offenerEmbeddedChannel, verwirft jedes Paket). Abmelden wie bei einem Disconnect:ServerPlayer#disconnect+PlayerList#remove.Dummieslädt das Backend perClass.forName- passt es nicht zur Server-Version (LinkageErroro.ä.), sind Dummies aus und nur das wird gemeldet. Nach jedem MC-Update hier zuerst prüfen. - Lebenszyklus:
Loader#dummy(indata.yml) ist der Wunsch des Owners;Dummies#sync(loader)sorgt dafür, dass der Dummy genau dann da ist, wenn der Loader aktiv, nicht gesperrt oder pausiert, seine Welt geladen unddummies.enabledan ist.LoaderManagerruftsyncbei an/aus, Löschen, Pause, Welt laden undapplyAll. Gespawnt wird einen Tick später (nicht mitten in fremden Events), ein Wächter alle 5 s holt verschwundene, getötete oder abgesetzte Dummies zurück. - Eigenschaften: Kreativmodus (unverwundbar, Mobs ignorieren ihn, keine Phantome), nie mit
doTickgetickt (bewegt sich nicht, isst nicht, sammelt nichts, keine Statistiken),setPersistent(false)(Paper speichert keine Spielerdaten),setSleepingIgnored(true), Simulationsdistanz = Reichweite des Loaders (mind. 2), Sichtdistanz eins mehr, Sende-Distanz 2. Ein Dummy sitzt nur auf Loadern, dieDummies#fitserfüllen (zentriertes Quadrat, Reichweite ≥ 2), sonst würde er Chunks außerhalb des Bereichs simulieren. Sitzt auf einem unsichtbarenItemDisplay(dummies.sit). UUID = Namens-UUID aus der Loader-ID, Name[D<nr>]<owner>(Klammern gibt es in echten Namen nicht, also nie eine Namensgleichheit). - Für den Rest unsichtbar: Join/Quit/Advancement/Tod ohne Meldung, Tab-Liste per
Player#unlistPlayer, Serverliste (PaperServerListPingEvent) und „Server voll“ (PlayerServerFullCheckEvent) ohne Dummies. Kick, Teleport, Absetzen, Spielmodus-Wechsel und Schaden durch andere werden abgebrochen. Markierung für andere Plugins: MetadataNPC(Citizens-Konvention) und PDC-Schlüsselmrchunks:dummy; MRsleep und MRpvp prüfen beides (jeweils KlasseNpcs), MRadmin fragt über die Modul-API (MRadminModule#isNpc). - Skin: Eigenschaft
texturesdes Owners - beim Join des Owners aktualisiert, sonst einmal perPlayerProfile#updategeholt, gespeichert inskins.yml.
11. Verknüpfte Accounts
- Daten:
LoaderManager.links(Zweit → Haupt, keine Ketten).main(uuid)liefert den Haupt-Account,group(uuid)alle Accounts. Loader gehören immer dem Haupt-Account:AdminActions.accountOf(player)ist überall maßgeblich, wo früher die UUID des Spielers stand (Owner-Prüfung, Setzen, Zählen, Einladungen, Menüs). Limits werden immer für den Haupt-Account berechnet,groupBanprüft die ganze Gruppe,notifyOwnererreicht alle Online-Accounts der Gruppe. - Ablauf:
LinkRequests#request(Haupt-Account fragt) → Dialog und Chat-Buttons beim anderen (läuft nachlinked-accounts.request-secondsab) →AdminActions#linkprüft alles erneut (linkProblem) →LoaderManager#linkübernimmt Loader (mit neuen Nummern) und Einladungen für alle Loader und entfernt Einladungen innerhalb der Gruppe. - Limit offline: Weil der Zweit-Account das Budget des (vielleicht offline) Haupt-Accounts nutzt, merkt sich
limit()Permission-Limits inknown-limitsund verwendet sie, solange der Account offline ist.
12. MRadmin (api/MRadminLink, api/MRadminModule)
MRadminLinkhat keine MRadmin-Typen und fängtLinkageErrorab - ohne MRadmin läuft MRchunks normal.MRadminModulewird nur geladen, wenn MRadmin läuft, und baut gegenmradminApiausgradle.properties(zur Zeitapi-1.2.0).- Was MRchunks liefert: Dashboard-Knopf und Menüs (
summary,openMenu,playerSummary,openPlayerMenu), auffällige Loader im Serverzustand (health,openHealth), Web-Daten und -Aktionen (WebData), Hinweise und Markierungen (alerts,flags), seit API 3 auch die Startseiten-Kachel (tile), die Push-Artchunks-locked(pushKinds), den Namen der Ticket-Kategoriechunkloader-lock(reportCategories), die geladenen Chunks (loadedChunks) und die Dummies als NPCs (isNpc). Die Texte dafür kommen aus den Sprachdateien inserver-language. - Tickets:
reportLock/resolveLockbei Lag-Sperre und Entsperren. - Ein älteres MRadmin (API < 3) ruft die neuen Methoden nie auf; die Web-Anfrage
chunksbleibt für solche Versionen. - Konfiguration im Browser (API 12):
webConfig()gibtconfig.yml,src/main/resources/web-config.yml(Formular, Namen DE/EN) und den Reload (plugin.reload()+ Discordadmin-reload) an MRadmin. Neue oder umbenannte Config-Schlüssel immer auch dort eintragen -WebConfigFormTestprüft das. Die Grenzen des Lag-Schutzes sind Tabellen (Zeilen = Messwerte, Spaltenwarn/lock). Umbenennungen inConfigUpdater.RENAMED_KEYS(v30:general.*,updates.*). - Protokoll von MRadmin:
log()schreibt jedes Ereignis mit Text aus der Sprachdatei vor dem Discord-Teil überMRadminLink#logins Protokoll der Weboberfläche (nur ab MRadmin-API 13, Platzhalter unescaped) - auch wenn Discord aus ist. Deshalb werdendiscord.events(an/aus, Farbe) immer geladen, nicht nur bei eingeschaltetem Discord. - Rückfall auf MRadmins Webhook:
enabled: true, leerewebhook-urlunduse-mradmin-webhook: true→Config.viaMradmin. Dann geht der Text statt an Discord anMRadminLink#adminLog(MRadmin-Ereignis „plugin-alert“, das MRadmin nicht noch einmal ins Protokoll schreibt).test()meldet einen Fehler, wenn MRadmin nicht läuft.