💻 FOR DEVELOPERS
Create and manage GUIs programmatically with the Java API
GUIPlus provides a public API for developers to create and manage GUIs programmatically, register custom click events and conditions, and interact with the player data system.
Note: The GUIPlus API is designed around direct class usage. Since GUIPlus does not publish to a Maven repository, you will need to add the plugin JAR as a local dependency.
import com.infiniteplugins.guiplus.GUIPlus;
GUIPlus plugin = GUIPlus.getInstance();import com.infiniteplugins.guiplus.api.gui.GUI;
import com.infiniteplugins.guiplus.api.gui.data.Scene;
import com.infiniteplugins.guiplus.api.gui.data.GuiItem;
// Create a new chest GUI
GUI gui = new GUI("myCustomGui", GUI.Type.CHEST);
gui.setTitle("&6My Custom Menu");
gui.setRows(3);
// Register the GUI with the manager
GUIPlus.getGuiManager().addGui(gui);The GUI.Type enum provides these inventory types:
GUI.Type.CHEST // Standard chest (1-6 rows)
GUI.Type.DISPENSER // 3x3 dispenser
GUI.Type.DROPPER // 3x3 dropper
GUI.Type.HOPPER // 5-slot hopperimport com.infiniteplugins.guiplus.api.gui.GuiManager;
// Get the GUI manager
GuiManager manager = GUIPlus.getGuiManager();
// Get a specific GUI by ID
GUI gui = manager.getGui("bank");
// Get all registered GUIs
List<GUI> allGuis = manager.getAll();
// Check if a GUI exists
boolean exists = manager.getGui("myGui") != null;Player player = ...;
GUI gui = GUIPlus.getGuiManager().getGui("shop");
if (gui != null) {
// Open for the player themselves
gui.open(player);
// Or open with a target player (e.g., for admin commands)
gui.open(player, targetPlayer);
}Create custom click event types by extending the GUIClick class:
import com.infiniteplugins.guiplus.click.GUIClick;
import com.infiniteplugins.guiplus.click.Clickable;
import com.infiniteplugins.guiplus.utils.ClickableProcess;
import com.cryptomorin.xseries.XMaterial;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Function;
public class MyCustomClickEvent extends GUIClick {
public MyCustomClickEvent(Clickable clickable) {
super(clickable);
}
@Override
public String getId() {
return "my-custom-click";
}
@Override
public String getName() {
return "My Custom Click";
}
@Override
public String lore() {
return "Does something custom";
}
@Override
public XMaterial itemMat() {
return XMaterial.REDSTONE;
}
@Override
public void onClick(Player player, @NotNull Player target,
InventoryClickEvent event,
@Nullable Function<String, String> modifier,
@NotNull ClickableProcess clickableProcess) {
// Your custom logic here
player.sendMessage("Custom click event fired!");
// IMPORTANT: Always call this when done to allow subsequent events to proceed
clickableProcess.notifyProcess();
}
@Override
public void runSetup(Player player, Consumer<Player> onComplete) {
// Setup wizard logic for the in-game editor
// Call onComplete when setup is finished
onComplete.accept(player);
}
@Override
public void save(com.infiniteplugins.guiplus.api.config.FileConfiguration.ConfigurationSection section) {
// Save your custom properties to the YAML section
}
@Override
public void load(com.infiniteplugins.guiplus.api.config.FileConfiguration.ConfigurationSection section) {
// Load your custom properties from the YAML section
}
}Important: Always call
clickableProcess.notifyProcess()at the end of youronClickmethod. This signals the event processing pipeline to proceed with the next click event in the sequence.
Create custom condition types by extending the GUICondition class:
import com.infiniteplugins.guiplus.conditions.GUICondition;
import com.cryptomorin.xseries.XMaterial;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Consumer;
import java.util.function.Function;
public class MyCustomCondition extends GUICondition {
@Override
public String getId() {
return "my-custom-condition";
}
@Override
public String getName() {
return "My Custom Condition";
}
@Override
public String lore() {
return "Checks something custom";
}
@Override
public XMaterial itemMat() {
return XMaterial.BARRIER;
}
@Override
public boolean canDisplayInternal(Player player, Function<String, String> modifier) {
// Return true if the condition passes (item should be shown)
return player.getHealth() > 10;
}
@Override
public void runSetup(Player player, Consumer<Player> onComplete, InventoryClickEvent event) {
// Setup wizard logic for the in-game editor
onComplete.accept(player);
}
@Override
public void save(com.infiniteplugins.guiplus.api.config.FileConfiguration.ConfigurationSection section) {
// Save your custom properties
}
@Override
public void load(com.infiniteplugins.guiplus.api.config.FileConfiguration.ConfigurationSection section) {
// Load your custom properties
}
}Note: The
invertedproperty is handled automatically by the base class. YourcanDisplayInternalshould return the non-inverted result.
Access the persistent player data system:
import com.infiniteplugins.guiplus.data.PlayerDataManager;
import com.infiniteplugins.guiplus.data.PlayerData;
// Get a player's data record
PlayerData data = PlayerDataManager.getPlayerData(player.getUniqueId());
// Read a stored field (returns null if not set)
String kills = data.getValue("kills");
// Read with a default value
String points = data.getValueOrDefault("points", "0");
// Save a field
data.setValue("kills", "25");Note:
PlayerDatais a Java record. Each call togetPlayerData()creates a new instance, but all instances share the same underlying YAML config. Changes viasetValueare saved to disk immediately.
Programmatically prompt a player for chat input:
import com.infiniteplugins.guiplus.api.chat.ChatFetcher;
// Basic usage
ChatFetcher fetcher = new ChatFetcher(player.getUniqueId());
fetcher.onFetch(input -> {
player.sendMessage("You typed: " + input);
return true; // Return true to accept the input, false to reject
}).build();
// With input filtering and exit handling
new ChatFetcher(player, true) // true = disable the "type exit to cancel" message
.setFilter(input -> input.length() > 0) // Optional pre-filter
.setFetcherFilterFailMessage("&cInput cannot be empty!")
.onFetch(input -> {
player.sendMessage("Accepted: " + input);
return true;
})
.onExit(p -> {
p.sendMessage("&7Cancelled input.");
})
.build();Important: You must call
.build()at the end to register the chat fetcher with the manager. TheonFetchcallback must return aBoolean—trueto accept the input and complete,falseto reject and keep waiting.
GUIPlus provides a scheduler wrapper for running tasks:
// Run async task
GUIPlus.async().run(() -> {
// Heavy work off the main thread
});
// Run sync task
GUIPlus.sync().run(() -> {
// Bukkit API calls on the main thread
});
// Run with delay (in ticks, 20 ticks = 1 second)
GUIPlus.sync().after(20L).run(() -> {
// Runs 1 second later on the main thread
});GUIPlus registers the following Bukkit events in GuiManager:
| Event | Priority | Purpose |
|---|---|---|
InventoryClickEvent |
LOWEST | GUI item click handling |
InventoryCloseEvent |
LOWEST | GUI close handling and cleanup |
Note:
InventoryDragEventis handled separately in the internalSystemGuiManagerfor the editor, not in the publicGuiManager.
Since GUIPlus is not published to a remote Maven repository, add the plugin JAR as a local dependency:
<dependency>
<groupId>com.infiniteplugins</groupId>
<artifactId>GUIPlus</artifactId>
<version>3.4.10</version>
<scope>provided</scope>
</dependency>Add GUIPlus to your plugin.yml dependencies:
depend: [GUIPlus]
# or if GUIPlus features are optional:
softdepend: [GUIPlus]Tip: Looking for YAML-based configuration instead of the Java API? Most GUIPlus features can be achieved entirely through YAML files. See Creating GUIs for the full YAML reference.
| ← Previous |
|---|
| FAQ & Troubleshooting |