Skip to content
Select theme
  • Auto
  • Dark
  • Light

Notes, rollback, and modules

This page covers three small services: player notes, block rollback, and information about loaded modules. Get each one from the API facade in your module.

The examples on this page are methods in your PlexModule class, so api() and getLogger() come from the module.

Service Interface
api().notes() NotesApi
api().rollback() RollbackApi
api().modules() ModulesApi

Staff can attach notes to a player. Plex stores the notes in its database. NotesApi reads and writes the same notes that the in-game note commands use.

Method What it does
list(UUID player) Returns the notes of a player, sorted by ID.
add(UUID player, String content, UUID author) Adds a note. Pass null as the author for the console.
remove(UUID player, int id) Removes one note. The future gives true if the note existed.
clear(UUID player) Removes every note of a player. The future gives the number of notes removed.

Each method returns a CompletableFuture. Plex runs the database work on its own I/O thread and completes the future there.

Note IDs belong to one player. The first note of a player gets ID 1, and each new note gets the highest ID plus 1. add fails its future with IllegalArgumentException if the content is longer than 2000 characters.

PlayerNote is a record.

Method What it does
id() Returns the note ID for this player.
player() Returns the UUID of the player that the note is about.
content() Returns the note text.
author() Returns the UUID of the author, or null for the console.
timestamp() Returns the time of the note as a ZonedDateTime.
public void addNote(Player staff, UUID target, String text)
{
api().notes().add(target, text, staff.getUniqueId()).whenComplete((ignored, failure) ->
{
if (failure != null)
{
getLogger().error("Unable to add a note to {}", target, failure);
staff.sendMessage(Component.text("The note could not be saved."));
return;
}
staff.sendMessage(Component.text("Note added."));
});
}
public void showNotes(CommandSender sender, UUID target)
{
api().notes().list(target).thenAccept(notes ->
{
if (notes.isEmpty())
{
sender.sendMessage(Component.text("This player has no notes."));
return;
}
for (PlayerNote note : notes)
{
String author = note.author() == null ? "Console" : note.author().toString();
sender.sendMessage(Component.text("#" + note.id() + " by " + author + ": " + note.content()));
}
});
}
public void removeNote(CommandSender sender, UUID target, int id)
{
api().notes().remove(target, id).thenAccept(removed ->
sender.sendMessage(Component.text(removed ? "Note removed." : "No note has that ID.")));
}

The callbacks run on the Plex I/O thread. Sending a message from there is safe. Schedule any player or world change on the scheduler that owns it.

RollbackApi undoes the block changes of one player through a block-logging plugin. It uses Oasis if Oasis is enabled, and CoreProtect if only CoreProtect is enabled.

Method What it does
isAvailable() Returns true if Oasis or CoreProtect is ready to run rollbacks.
rollback(CommandSender sender, String playerName, int seconds) Rolls back the changes that the player made in the last seconds seconds. The future gives the number of changes rolled back.
rollbackLastDay(CommandSender sender, String playerName) Rolls back the last 24 hours. It is the same as rollback(sender, playerName, 86400).

Call isAvailable() first. If no rollback plugin is ready, rollback returns a failed future with IllegalStateException. A value of seconds that is 0 or less gives a failed future with IllegalArgumentException.

With Oasis, Plex looks up the player name in its own player data. If Plex does not know the name, the future gives 0. Plex records the sender as the staff member who asked for the rollback. If Oasis does not finish the rollback, the future fails with IllegalStateException.

With CoreProtect, Plex passes the player name to CoreProtect as you give it.

The future completes on a background thread. Send messages from the callback directly. Schedule any player or world change on the scheduler that owns it.

public void undoGrief(CommandSender sender, String playerName)
{
RollbackApi rollback = api().rollback();
if (!rollback.isAvailable())
{
sender.sendMessage(Component.text("Install Oasis or CoreProtect to use rollbacks."));
return;
}
rollback.rollbackLastDay(sender, playerName).whenComplete((changes, failure) ->
{
if (failure != null)
{
getLogger().error("Rollback of {} failed", playerName, failure);
sender.sendMessage(Component.text("The rollback failed."));
return;
}
sender.sendMessage(Component.text("Rolled back " + changes + " changes by " + playerName + "."));
});
}

ModulesApi tells you which Plex modules are loaded. Each module is a PlexModuleFile, the information from that module’s module.yml.

Method What it does
loadedModules() Returns an immutable list of every loaded module.
module(String name) Returns the module with this module.yml name, or an empty Optional. The name match ignores case.

PlexModuleFile has getName(), getVersion(), getDescription(), getMain(), getApiCompatibility(), getLibraries(), getRepositories(), isUpdaterEnabled(), and getUpdateUrls().

Plex updates the list after every module finishes enable(). While your own enable() runs, the list does not show the modules of the current load yet, and it does not show your module. Read it later, for example in a command. You can call both methods from any thread.

public void listModules(CommandSender sender)
{
for (PlexModuleFile file : api().modules().loadedModules())
{
sender.sendMessage(Component.text(file.getName() + " " + file.getVersion()));
}
}
public boolean guildsInstalled()
{
return api().modules().module("Module-Guilds").isPresent();
}

Server owners install modules as described on the Modules page.